1 /* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software Foundation,
21 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22
23 #include "xa.h"
24
25 #include "hash.h" // HASH
26 #include "sql_class.h" // THD
27 #include "transaction.h" // trans_begin, trans_rollback
28 #include "debug_sync.h" // DEBUG_SYNC
29 #include "log.h" // tc_log
30 #include "sql_plugin.h" // plugin_foreach
31 #include <pfs_transaction_provider.h>
32 #include <mysql/psi/mysql_transaction.h>
33 #include "binlog.h"
34
35 const char *XID_STATE::xa_state_names[]={
36 "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY"
37 };
38
39 /* for recover() handlerton call */
40 static const int MIN_XID_LIST_SIZE= 128;
41 static const int MAX_XID_LIST_SIZE= 1024*128;
42
43 static mysql_mutex_t LOCK_transaction_cache;
44 static HASH transaction_cache;
45
xacommit_handlerton(THD * unused1,plugin_ref plugin,void * arg)46 static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin,
47 void *arg)
48 {
49 handlerton *hton= plugin_data<handlerton*>(plugin);
50 if (hton->state == SHOW_OPTION_YES && hton->recover)
51 hton->commit_by_xid(hton, (XID *)arg);
52
53 return FALSE;
54 }
55
56
xarollback_handlerton(THD * unused1,plugin_ref plugin,void * arg)57 static my_bool xarollback_handlerton(THD *unused1, plugin_ref plugin,
58 void *arg)
59 {
60 handlerton *hton= plugin_data<handlerton*>(plugin);
61 if (hton->state == SHOW_OPTION_YES && hton->recover)
62 hton->rollback_by_xid(hton, (XID *)arg);
63
64 return FALSE;
65 }
66
67
ha_commit_or_rollback_by_xid(THD * thd,XID * xid,bool commit)68 static void ha_commit_or_rollback_by_xid(THD *thd, XID *xid, bool commit)
69 {
70 plugin_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton,
71 MYSQL_STORAGE_ENGINE_PLUGIN, xid);
72 }
73
74
75 struct xarecover_st
76 {
77 int len, found_foreign_xids, found_my_xids;
78 XID *list;
79 HASH *commit_list;
80 bool dry_run;
81 };
82
83
xarecover_handlerton(THD * unused,plugin_ref plugin,void * arg)84 static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
85 void *arg)
86 {
87 handlerton *hton= plugin_data<handlerton*>(plugin);
88 struct xarecover_st *info= (struct xarecover_st *) arg;
89 int got;
90
91 if (hton->state == SHOW_OPTION_YES && hton->recover)
92 {
93 while ((got= hton->recover(hton, info->list, info->len)) > 0)
94 {
95 sql_print_information("Found %d prepared transaction(s) in %s",
96 got, ha_resolve_storage_engine_name(hton));
97 for (int i= 0; i < got; i++)
98 {
99 my_xid x= info->list[i].get_my_xid();
100 if (!x) // not "mine" - that is generated by external TM
101 {
102 #ifndef NDEBUG
103 char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str
104 XID *xid= info->list + i;
105 sql_print_information("ignore xid %s", xid->xid_to_str(buf));
106 #endif
107 transaction_cache_insert_recovery(info->list + i);
108 info->found_foreign_xids++;
109 continue;
110 }
111 if (info->dry_run)
112 {
113 info->found_my_xids++;
114 continue;
115 }
116 // recovery mode
117 if (info->commit_list ?
118 my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 :
119 tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT)
120 {
121 #ifndef NDEBUG
122 char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str
123 XID *xid= info->list + i;
124 sql_print_information("commit xid %s", xid->xid_to_str(buf));
125 #endif
126 hton->commit_by_xid(hton, info->list + i);
127 }
128 else
129 {
130 #ifndef NDEBUG
131 char buf[XIDDATASIZE * 4 + 6]; // see xid_to_str
132 XID *xid= info->list + i;
133 sql_print_information("rollback xid %s", xid->xid_to_str(buf));
134 #endif
135 hton->rollback_by_xid(hton, info->list + i);
136 }
137 }
138 if (got < info->len)
139 break;
140 }
141 }
142 return false;
143 }
144
145
ha_recover(HASH * commit_list)146 int ha_recover(HASH *commit_list)
147 {
148 struct xarecover_st info;
149 DBUG_ENTER("ha_recover");
150 info.found_foreign_xids= info.found_my_xids= 0;
151 info.commit_list= commit_list;
152 info.dry_run= (info.commit_list == 0 &&
153 tc_heuristic_recover == TC_HEURISTIC_NOT_USED);
154 info.list= NULL;
155
156 /* commit_list and tc_heuristic_recover cannot be set both */
157 assert(info.commit_list == 0 ||
158 tc_heuristic_recover == TC_HEURISTIC_NOT_USED);
159 /* if either is set, total_ha_2pc must be set too */
160 assert(info.dry_run || total_ha_2pc>(ulong)opt_bin_log);
161
162 if (total_ha_2pc <= (ulong)opt_bin_log)
163 DBUG_RETURN(0);
164
165 if (info.commit_list)
166 sql_print_information("Starting crash recovery...");
167
168 if (total_ha_2pc > (ulong)opt_bin_log + 1)
169 {
170 if (tc_heuristic_recover == TC_HEURISTIC_RECOVER_ROLLBACK)
171 {
172 sql_print_error("--tc-heuristic-recover rollback strategy is not safe "
173 "on systems with more than one 2-phase-commit-capable "
174 "storage engine. Aborting crash recovery.");
175 DBUG_RETURN(1);
176 }
177 }
178 else
179 {
180 /*
181 If there is only one 2pc capable storage engine it is always safe
182 to rollback. This setting will be ignored if we are in automatic
183 recovery mode.
184 */
185 tc_heuristic_recover= TC_HEURISTIC_RECOVER_ROLLBACK; // forcing ROLLBACK
186 info.dry_run= false;
187 }
188
189 for (info.len= MAX_XID_LIST_SIZE ;
190 info.list == 0 && info.len > MIN_XID_LIST_SIZE; info.len/= 2)
191 {
192 info.list= (XID *)my_malloc(key_memory_XID,
193 info.len * sizeof(XID), MYF(0));
194 }
195 if (!info.list)
196 {
197 sql_print_error(ER(ER_OUTOFMEMORY),
198 static_cast<int>(info.len * sizeof(XID)));
199 DBUG_RETURN(1);
200 }
201
202 plugin_foreach(NULL, xarecover_handlerton,
203 MYSQL_STORAGE_ENGINE_PLUGIN, &info);
204
205 my_free(info.list);
206 if (info.found_foreign_xids)
207 sql_print_warning("Found %d prepared XA transactions",
208 info.found_foreign_xids);
209 if (info.dry_run && info.found_my_xids)
210 {
211 sql_print_error("Found %d prepared transactions! It means that mysqld was "
212 "not shut down properly last time and critical recovery "
213 "information (last binlog or %s file) was manually deleted"
214 " after a crash. You have to start mysqld with "
215 "--tc-heuristic-recover switch to commit or rollback "
216 "pending transactions.",
217 info.found_my_xids, opt_tc_log_file);
218 DBUG_RETURN(1);
219 }
220 if (info.commit_list)
221 sql_print_information("Crash recovery finished.");
222 DBUG_RETURN(0);
223 }
224
225
xa_trans_force_rollback(THD * thd)226 bool xa_trans_force_rollback(THD *thd)
227 {
228 /*
229 We must reset rm_error before calling ha_rollback(),
230 so thd->transaction.xid structure gets reset
231 by ha_rollback()/THD::transaction::cleanup().
232 */
233 thd->get_transaction()->xid_state()->reset_error();
234 if (ha_rollback_trans(thd, true))
235 {
236 my_error(ER_XAER_RMERR, MYF(0));
237 return true;
238 }
239 return false;
240 }
241
242
cleanup_trans_state(THD * thd)243 void cleanup_trans_state(THD *thd)
244 {
245 thd->variables.option_bits&= ~OPTION_BEGIN;
246 thd->server_status&=
247 ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
248 thd->get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::SESSION);
249 DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
250 transaction_cache_delete(thd->get_transaction());
251 }
252
253
254 /**
255 Commit and terminate a XA transaction.
256
257 @param thd Current thread
258
259 @retval false Success
260 @retval true Failure
261 */
262
trans_xa_commit(THD * thd)263 bool Sql_cmd_xa_commit::trans_xa_commit(THD *thd)
264 {
265 bool res= true;
266 XID_STATE *xid_state= thd->get_transaction()->xid_state();
267 bool gtid_error= false, need_clear_owned_gtid= false;
268
269 DBUG_ENTER("trans_xa_commit");
270
271 assert(!thd->slave_thread || xid_state->get_xid()->is_null() ||
272 m_xa_opt == XA_ONE_PHASE);
273
274 if (!xid_state->has_same_xid(m_xid))
275 {
276 if (!xid_state->has_state(XID_STATE::XA_NOTR))
277 {
278 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
279
280 DBUG_RETURN(true);
281 }
282 /*
283 Note, that there is no race condition here between
284 transaction_cache_search and transaction_cache_delete,
285 since we always delete our own XID
286 (m_xid == thd->transaction().xid_state().m_xid).
287 The only case when m_xid != thd->transaction.xid_state.m_xid
288 and xid_state->in_thd == 0 is in the function
289 transaction_cache_insert_recovery(XID), which is called before starting
290 client connections, and thus is always single-threaded.
291 */
292 Transaction_ctx *transaction= transaction_cache_search(m_xid);
293 XID_STATE *xs= (transaction ? transaction->xid_state() : NULL);
294 res= !xs || !xs->is_in_recovery();
295 if (res) // todo: fix transaction cleanup, BUG#20451386
296 {
297 my_error(ER_XAER_NOTA, MYF(0));
298 DBUG_RETURN(true);
299 }
300 else if (thd->in_multi_stmt_transaction_mode())
301 {
302 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
303 DBUG_RETURN(true);
304 }
305
306 assert(xs->is_in_recovery());
307 /*
308 Resumed transaction XA-commit.
309 The case deals with the "external" XA-commit by either a slave applier
310 or a different than XA-prepared transaction session.
311 */
312 res= xs->xa_trans_rolled_back();
313
314 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
315 /*
316 If the original transaction is not rolled back then initiate a new PSI
317 transaction to update performance schema related information.
318 */
319 if (!res)
320 {
321 thd->m_transaction_psi= MYSQL_START_TRANSACTION(&thd->m_transaction_state,
322 NULL, NULL, thd->tx_isolation,
323 thd->tx_read_only, false);
324 gtid_set_performance_schema_values(thd);
325 MYSQL_SET_TRANSACTION_XID(thd->m_transaction_psi,
326 (const void *)xs->get_xid(),
327 (int)xs->get_state());
328 }
329 #endif
330 /*
331 xs' is_binlogged() is passed through xid_state's member to low-level
332 logging routines for deciding how to log. The same applies to
333 Rollback case.
334 */
335 if (xs->is_binlogged())
336 xid_state->set_binlogged();
337 else
338 xid_state->unset_binlogged();
339
340 /*
341 Acquire metadata lock which will ensure that COMMIT is blocked
342 by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in
343 progress blocks FTWRL).
344
345 We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
346 */
347 MDL_request mdl_request;
348 MDL_REQUEST_INIT(&mdl_request,
349 MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
350 MDL_STATEMENT);
351 if (thd->mdl_context.acquire_lock(&mdl_request,
352 thd->variables.lock_wait_timeout))
353 {
354 /*
355 We can't rollback an XA transaction on lock failure due to
356 Innodb redo log and bin log update is involved in rollback.
357 Return error to user for a retry.
358 */
359 my_error(ER_XA_RETRY, MYF(0));
360 DBUG_RETURN(true);
361 }
362
363 /* Do not execute gtid wrapper whenever 'res' is true (rm error) */
364 gtid_error= MY_TEST(commit_owned_gtids(thd,
365 true, &need_clear_owned_gtid));
366 if (gtid_error)
367 my_error(ER_XA_RBROLLBACK, MYF(0));
368 res= res || gtid_error;
369
370 // todo xa framework: return an error
371 ha_commit_or_rollback_by_xid(thd, m_xid, !res);
372 xid_state->unset_binlogged();
373
374 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
375 if (!res)
376 {
377 if (thd->m_transaction_psi)
378 {
379 /*
380 Set the COMMITTED state in PSI context at the end of committing the
381 XA transaction.
382 */
383 MYSQL_COMMIT_TRANSACTION(thd->m_transaction_psi);
384 thd->m_transaction_psi= NULL;
385 }
386 }
387 #endif
388
389 transaction_cache_delete(transaction);
390 gtid_state_commit_or_rollback(thd, need_clear_owned_gtid, !gtid_error);
391 DBUG_RETURN(res);
392 }
393
394 if (xid_state->xa_trans_rolled_back())
395 {
396 xa_trans_force_rollback(thd);
397 res= thd->is_error();
398 }
399 else if (xid_state->has_state(XID_STATE::XA_IDLE) &&
400 m_xa_opt == XA_ONE_PHASE)
401 {
402 int r= ha_commit_trans(thd, true);
403 if ((res= MY_TEST(r)))
404 my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0));
405 }
406 else if (xid_state->has_state(XID_STATE::XA_PREPARED) &&
407 m_xa_opt == XA_NONE)
408 {
409 MDL_request mdl_request;
410
411 /*
412 Acquire metadata lock which will ensure that COMMIT is blocked
413 by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in
414 progress blocks FTWRL).
415
416 We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
417 */
418 MDL_REQUEST_INIT(&mdl_request,
419 MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
420 MDL_STATEMENT);
421 if (thd->mdl_context.acquire_lock(&mdl_request,
422 thd->variables.lock_wait_timeout))
423 {
424 /*
425 We can't rollback an XA transaction on lock failure due to
426 Innodb redo log and bin log update are involved in rollback.
427 Return error to user for a retry.
428 */
429 my_error(ER_XA_RETRY, MYF(0));
430 DBUG_RETURN(true);
431 }
432
433 gtid_error= MY_TEST(commit_owned_gtids(thd, true, &need_clear_owned_gtid));
434 if (gtid_error)
435 {
436 res= true;
437 /*
438 Failure to store gtid is regarded as a unilateral one of the
439 resource manager therefore the transaction is to be rolled back.
440 The specified error is the same as @c xa_trans_force_rollback.
441 The prepared XA will be rolled back along and so will do Gtid state,
442 see ha_rollback_trans().
443
444 Todo/fixme: fix binlogging, "XA rollback" event could be missed out.
445 Todo/fixme: as to XAER_RMERR, should not it be XA_RBROLLBACK?
446 Rationale: there's no consistency concern after rollback,
447 unlike what XAER_RMERR suggests.
448 */
449 ha_rollback_trans(thd, true);
450 my_error(ER_XAER_RMERR, MYF(0));
451 }
452 else
453 {
454 DBUG_EXECUTE_IF("simulate_crash_on_commit_xa_trx", DBUG_SUICIDE(););
455 DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock");
456
457 if (tc_log)
458 res= MY_TEST(tc_log->commit(thd, /* all */ true));
459 else
460 res= MY_TEST(ha_commit_low(thd, /* all */ true));
461
462 DBUG_EXECUTE_IF("simulate_xa_commit_log_failure", { res= true; });
463
464 if (res)
465 my_error(ER_XAER_RMERR, MYF(0)); // todo/fixme: consider to rollback it
466 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
467 else
468 {
469 /*
470 Since we don't call ha_commit_trans() for prepared transactions,
471 we need to explicitly mark the transaction as committed.
472 */
473 MYSQL_COMMIT_TRANSACTION(thd->m_transaction_psi);
474 }
475
476 thd->m_transaction_psi= NULL;
477 #endif
478 }
479 }
480 else
481 {
482 assert(!need_clear_owned_gtid);
483
484 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
485 DBUG_RETURN(true);
486 }
487 gtid_state_commit_or_rollback(thd, need_clear_owned_gtid, !gtid_error);
488 cleanup_trans_state(thd);
489
490 xid_state->set_state(XID_STATE::XA_NOTR);
491 xid_state->unset_binlogged();
492 trans_track_end_trx(thd);
493 /* The transaction should be marked as complete in P_S. */
494 assert(thd->m_transaction_psi == NULL || res);
495 DBUG_RETURN(res);
496 }
497
498
execute(THD * thd)499 bool Sql_cmd_xa_commit::execute(THD *thd)
500 {
501 bool st= trans_xa_commit(thd);
502
503 if (!st)
504 {
505 thd->mdl_context.release_transactional_locks();
506 /*
507 We've just done a commit, reset transaction
508 isolation level and access mode to the session default.
509 */
510 trans_reset_one_shot_chistics(thd);
511
512 my_ok(thd);
513 }
514 return st;
515 }
516
517
518 /**
519 Roll back and terminate a XA transaction.
520
521 @param thd Current thread
522
523 @retval false Success
524 @retval true Failure
525 */
526
trans_xa_rollback(THD * thd)527 bool Sql_cmd_xa_rollback::trans_xa_rollback(THD *thd)
528 {
529 XID_STATE *xid_state= thd->get_transaction()->xid_state();
530 bool need_clear_owned_gtid= false;
531
532 DBUG_ENTER("trans_xa_rollback");
533
534 if (!xid_state->has_same_xid(m_xid))
535 {
536 if (!xid_state->has_state(XID_STATE::XA_NOTR))
537 {
538 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
539
540 DBUG_RETURN(true);
541 }
542
543 Transaction_ctx *transaction= transaction_cache_search(m_xid);
544 XID_STATE *xs= (transaction ? transaction->xid_state() : NULL);
545 if (!xs || !xs->is_in_recovery())
546 {
547 my_error(ER_XAER_NOTA, MYF(0));
548 DBUG_RETURN(true);
549 }
550 else if (thd->in_multi_stmt_transaction_mode())
551 {
552 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
553 DBUG_RETURN(true);
554 }
555
556 bool gtid_error= false;
557
558 assert(xs->is_in_recovery());
559
560 /*
561 Acquire metadata lock which will ensure that XA ROLLBACK is blocked
562 by active FLUSH TABLES WITH READ LOCK (and vice versa ROLLBACK in
563 progress blocks FTWRL). This is to avoid binlog and redo entries
564 while a backup is in progress.
565 */
566 MDL_request mdl_request;
567 MDL_REQUEST_INIT(&mdl_request,
568 MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
569 MDL_STATEMENT);
570 if (thd->mdl_context.acquire_lock(&mdl_request,
571 thd->variables.lock_wait_timeout))
572 {
573 /*
574 We can't rollback an XA transaction on lock failure due to
575 Innodb redo log and bin log update is involved in rollback.
576 Return error to user for a retry.
577 */
578 my_error(ER_XAER_RMERR, MYF(0));
579 DBUG_RETURN(true);
580 }
581
582 /*
583 Like in the commit case a failure to store gtid is regarded
584 as the resource manager issue.
585 */
586 if ((gtid_error= commit_owned_gtids(thd, true, &need_clear_owned_gtid)))
587 my_error(ER_XA_RBROLLBACK, MYF(0));
588 xs->xa_trans_rolled_back();
589 if (xs->is_binlogged())
590 xid_state->set_binlogged();
591 else
592 xid_state->unset_binlogged();
593 ha_commit_or_rollback_by_xid(thd, m_xid, false);
594 xid_state->unset_binlogged();
595 transaction_cache_delete(transaction);
596 gtid_state_commit_or_rollback(thd, need_clear_owned_gtid, !gtid_error);
597 DBUG_RETURN(thd->is_error());
598 }
599
600 if (xid_state->has_state(XID_STATE::XA_NOTR) ||
601 xid_state->has_state(XID_STATE::XA_ACTIVE))
602 {
603 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
604 DBUG_RETURN(true);
605 }
606
607 /*
608 Acquire metadata lock which will ensure that XA ROLLBACK is blocked
609 by active FLUSH TABLES WITH READ LOCK (and vice versa ROLLBACK in
610 progress blocks FTWRL). This is to avoid binlog and redo entries
611 while a backup is in progress.
612 */
613 MDL_request mdl_request;
614 MDL_REQUEST_INIT(&mdl_request,
615 MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
616 MDL_STATEMENT);
617 if (thd->mdl_context.acquire_lock(&mdl_request,
618 thd->variables.lock_wait_timeout))
619 {
620 /*
621 We can't rollback an XA transaction on lock failure due to
622 Innodb redo log and bin log update is involved in rollback.
623 Return error to user for a retry.
624 */
625 my_error(ER_XAER_RMERR, MYF(0));
626 DBUG_RETURN(true);
627 }
628
629 bool gtid_error= MY_TEST(commit_owned_gtids(thd, true,
630 &need_clear_owned_gtid));
631 bool res= xa_trans_force_rollback(thd) || gtid_error;
632 gtid_state_commit_or_rollback(thd, need_clear_owned_gtid, !gtid_error);
633 // todo: report a bug in that the raised rm_error in this branch
634 // is masked unlike the "external" rollback branch above.
635 DBUG_EXECUTE_IF("simulate_xa_rm_error",
636 {
637 my_error(ER_XA_RBROLLBACK, MYF(0));
638 res= true;
639 });
640
641 cleanup_trans_state(thd);
642
643 xid_state->set_state(XID_STATE::XA_NOTR);
644 xid_state->unset_binlogged();
645 trans_track_end_trx(thd);
646 /* The transaction should be marked as complete in P_S. */
647 assert(thd->m_transaction_psi == NULL);
648 DBUG_RETURN(res);
649 }
650
651
execute(THD * thd)652 bool Sql_cmd_xa_rollback::execute(THD *thd)
653 {
654 bool st= trans_xa_rollback(thd);
655
656 if (!st)
657 {
658 thd->mdl_context.release_transactional_locks();
659 /*
660 We've just done a rollback, reset transaction
661 isolation level and access mode to the session default.
662 */
663 trans_reset_one_shot_chistics(thd);
664 my_ok(thd);
665 }
666
667 DBUG_EXECUTE_IF("crash_after_xa_rollback", DBUG_SUICIDE(););
668
669 return st;
670 }
671
672
673 /**
674 Start a XA transaction with the given xid value.
675
676 @param thd Current thread
677
678 @retval false Success
679 @retval true Failure
680 */
681
trans_xa_start(THD * thd)682 bool Sql_cmd_xa_start::trans_xa_start(THD *thd)
683 {
684 XID_STATE *xid_state= thd->get_transaction()->xid_state();
685 DBUG_ENTER("trans_xa_start");
686
687 if (xid_state->has_state(XID_STATE::XA_IDLE) &&
688 m_xa_opt == XA_RESUME)
689 {
690 bool not_equal= !xid_state->has_same_xid(m_xid);
691 if (not_equal)
692 my_error(ER_XAER_NOTA, MYF(0));
693 else
694 {
695 xid_state->set_state(XID_STATE::XA_ACTIVE);
696 MYSQL_SET_TRANSACTION_XA_STATE(thd->m_transaction_psi,
697 (int)thd->get_transaction()->xid_state()->get_state());
698 }
699 DBUG_RETURN(not_equal);
700 }
701
702 /* TODO: JOIN is not supported yet. */
703 if (m_xa_opt != XA_NONE)
704 my_error(ER_XAER_INVAL, MYF(0));
705 else if (!xid_state->has_state(XID_STATE::XA_NOTR))
706 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
707 else if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction())
708 my_error(ER_XAER_OUTSIDE, MYF(0));
709 else if (!trans_begin(thd))
710 {
711 xid_state->start_normal_xa(m_xid);
712 MYSQL_SET_TRANSACTION_XID(thd->m_transaction_psi,
713 (const void *)xid_state->get_xid(),
714 (int)xid_state->get_state());
715 if (transaction_cache_insert(m_xid, thd->get_transaction()))
716 {
717 xid_state->reset();
718 trans_rollback(thd);
719 }
720 }
721
722 DBUG_RETURN(thd->is_error() ||
723 !xid_state->has_state(XID_STATE::XA_ACTIVE));
724 }
725
726
execute(THD * thd)727 bool Sql_cmd_xa_start::execute(THD *thd)
728 {
729 bool st= trans_xa_start(thd);
730
731 if (!st)
732 {
733 thd->rpl_detach_engine_ha_data();
734 my_ok(thd);
735 }
736
737 return st;
738 }
739
740
741 /**
742 Put a XA transaction in the IDLE state.
743
744 @param thd Current thread
745
746 @retval false Success
747 @retval true Failure
748 */
749
trans_xa_end(THD * thd)750 bool Sql_cmd_xa_end::trans_xa_end(THD *thd)
751 {
752 XID_STATE *xid_state= thd->get_transaction()->xid_state();
753 DBUG_ENTER("trans_xa_end");
754
755 /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */
756 if (m_xa_opt != XA_NONE)
757 my_error(ER_XAER_INVAL, MYF(0));
758 else if (!xid_state->has_state(XID_STATE::XA_ACTIVE) &&
759 !xid_state->has_state(XID_STATE::XA_ROLLBACK_ONLY))
760 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
761 else if (!xid_state->has_same_xid(m_xid))
762 my_error(ER_XAER_NOTA, MYF(0));
763 else if (!xid_state->xa_trans_rolled_back())
764 {
765 xid_state->set_state(XID_STATE::XA_IDLE);
766 MYSQL_SET_TRANSACTION_XA_STATE(thd->m_transaction_psi,
767 (int)xid_state->get_state());
768 }
769 else
770 {
771 MYSQL_SET_TRANSACTION_XA_STATE(thd->m_transaction_psi,
772 (int)xid_state->get_state());
773 }
774
775 DBUG_RETURN(thd->is_error() ||
776 !xid_state->has_state(XID_STATE::XA_IDLE));
777 }
778
779
execute(THD * thd)780 bool Sql_cmd_xa_end::execute(THD *thd)
781 {
782 bool st= trans_xa_end(thd);
783
784 if (!st)
785 my_ok(thd);
786
787 return st;
788 }
789
790
791 /**
792 Put a XA transaction in the PREPARED state.
793
794 @param thd Current thread
795
796 @retval false Success
797 @retval true Failure
798 */
799
trans_xa_prepare(THD * thd)800 bool Sql_cmd_xa_prepare::trans_xa_prepare(THD *thd)
801 {
802 XID_STATE *xid_state= thd->get_transaction()->xid_state();
803 DBUG_ENTER("trans_xa_prepare");
804
805 if (!xid_state->has_state(XID_STATE::XA_IDLE))
806 my_error(ER_XAER_RMFAIL, MYF(0), xid_state->state_name());
807 else if (!xid_state->has_same_xid(m_xid))
808 my_error(ER_XAER_NOTA, MYF(0));
809 else if (thd->slave_thread &&
810 is_transaction_empty(
811 thd)) // No changes in none of the storage engine
812 // means, filtered statements in the slave
813 my_error(ER_XA_REPLICATION_FILTERS,
814 MYF(0)); // Empty XA transactions not allowed
815 else {
816 /*
817 Acquire metadata lock which will ensure that XA PREPARE is blocked
818 by active FLUSH TABLES WITH READ LOCK (and vice versa PREPARE in
819 progress blocks FTWRL). This is to avoid binlog and redo entries
820 while a backup is in progress.
821 */
822 MDL_request mdl_request;
823 MDL_REQUEST_INIT(&mdl_request,
824 MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
825 MDL_STATEMENT);
826 if (thd->mdl_context.acquire_lock(&mdl_request,
827 thd->variables.lock_wait_timeout) ||
828 ha_prepare(thd))
829 {
830 /*
831 Rollback the transaction if lock failed. For ha_prepare() failure
832 scenarios, transaction is already rolled back by ha_prepare().
833 */
834 if (!mdl_request.ticket)
835 ha_rollback_trans(thd, true);
836
837 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
838 assert(thd->m_transaction_psi == NULL);
839 #endif
840
841 /*
842 Reset rm_error in case ha_prepare() returned error,
843 so thd->transaction.xid structure gets reset
844 by THD::transaction::cleanup().
845 */
846 thd->get_transaction()->xid_state()->reset_error();
847 cleanup_trans_state(thd);
848 xid_state->set_state(XID_STATE::XA_NOTR);
849 thd->get_transaction()->cleanup();
850 my_error(ER_XA_RBROLLBACK, MYF(0));
851 }
852 else
853 {
854 xid_state->set_state(XID_STATE::XA_PREPARED);
855 MYSQL_SET_TRANSACTION_XA_STATE(thd->m_transaction_psi,
856 (int)xid_state->get_state());
857 if (thd->rpl_thd_ctx.session_gtids_ctx().notify_after_xa_prepare(thd))
858 sql_print_warning("Failed to collect GTID to send in the response packet!");
859 }
860 }
861
862 DBUG_RETURN(thd->is_error() ||
863 !xid_state->has_state(XID_STATE::XA_PREPARED));
864 }
865
866
execute(THD * thd)867 bool Sql_cmd_xa_prepare::execute(THD *thd)
868 {
869 bool st= trans_xa_prepare(thd);
870
871 if (!st)
872 {
873 if (!thd->rpl_unflag_detached_engine_ha_data() ||
874 !(st= applier_reset_xa_trans(thd)))
875 my_ok(thd);
876 }
877
878 return st;
879 }
880
881
882 /**
883 Return the list of XID's to a client, the same way SHOW commands do.
884
885 @param thd Current thread
886
887 @retval false Success
888 @retval true Failure
889
890 @note
891 I didn't find in XA specs that an RM cannot return the same XID twice,
892 so trans_xa_recover does not filter XID's to ensure uniqueness.
893 It can be easily fixed later, if necessary.
894 */
895
trans_xa_recover(THD * thd)896 bool Sql_cmd_xa_recover::trans_xa_recover(THD *thd)
897 {
898 List<Item> field_list;
899 Protocol *protocol= thd->get_protocol();
900 int i= 0;
901 Transaction_ctx *transaction;
902
903 DBUG_ENTER("trans_xa_recover");
904
905 field_list.push_back(new Item_int(NAME_STRING("formatID"), 0,
906 MY_INT32_NUM_DECIMAL_DIGITS));
907 field_list.push_back(new Item_int(NAME_STRING("gtrid_length"), 0,
908 MY_INT32_NUM_DECIMAL_DIGITS));
909 field_list.push_back(new Item_int(NAME_STRING("bqual_length"), 0,
910 MY_INT32_NUM_DECIMAL_DIGITS));
911 field_list.push_back(new Item_empty_string("data", XIDDATASIZE*2+2));
912
913 if (thd->send_result_metadata(&field_list,
914 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
915 DBUG_RETURN(true);
916
917 mysql_mutex_lock(&LOCK_transaction_cache);
918
919 while ((transaction= (Transaction_ctx*) my_hash_element(&transaction_cache,
920 i++)))
921 {
922 XID_STATE *xs= transaction->xid_state();
923 if (xs->has_state(XID_STATE::XA_PREPARED))
924 {
925 protocol->start_row();
926 xs->store_xid_info(protocol, m_print_xid_as_hex);
927
928 if (protocol->end_row())
929 {
930 mysql_mutex_unlock(&LOCK_transaction_cache);
931 DBUG_RETURN(true);
932 }
933 }
934 }
935
936 mysql_mutex_unlock(&LOCK_transaction_cache);
937 my_eof(thd);
938 DBUG_RETURN(false);
939 }
940
941
execute(THD * thd)942 bool Sql_cmd_xa_recover::execute(THD *thd)
943 {
944 bool st= trans_xa_recover(thd);
945 DBUG_EXECUTE_IF("crash_after_xa_recover", {DBUG_SUICIDE();});
946
947 return st;
948 }
949
950
xa_trans_rolled_back()951 bool XID_STATE::xa_trans_rolled_back()
952 {
953 DBUG_EXECUTE_IF("simulate_xa_rm_error", rm_error= true;);
954 if (rm_error)
955 {
956 switch (rm_error)
957 {
958 case ER_LOCK_WAIT_TIMEOUT:
959 my_error(ER_XA_RBTIMEOUT, MYF(0));
960 break;
961 case ER_LOCK_DEADLOCK:
962 my_error(ER_XA_RBDEADLOCK, MYF(0));
963 break;
964 default:
965 my_error(ER_XA_RBROLLBACK, MYF(0));
966 }
967 xa_state= XID_STATE::XA_ROLLBACK_ONLY;
968 }
969
970 return (xa_state == XID_STATE::XA_ROLLBACK_ONLY);
971 }
972
973
check_xa_idle_or_prepared(bool report_error) const974 bool XID_STATE::check_xa_idle_or_prepared(bool report_error) const
975 {
976 if (xa_state == XA_IDLE ||
977 xa_state == XA_PREPARED)
978 {
979 if (report_error)
980 my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
981
982 return true;
983 }
984
985 return false;
986 }
987
988
check_has_uncommitted_xa() const989 bool XID_STATE::check_has_uncommitted_xa() const
990 {
991 if (xa_state == XA_IDLE ||
992 xa_state == XA_PREPARED ||
993 xa_state == XA_ROLLBACK_ONLY)
994 {
995 my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
996 return true;
997 }
998
999 return false;
1000 }
1001
1002
check_in_xa(bool report_error) const1003 bool XID_STATE::check_in_xa(bool report_error) const
1004 {
1005 if (xa_state != XA_NOTR)
1006 {
1007 if (report_error)
1008 my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
1009 return true;
1010 }
1011
1012 return false;
1013 }
1014
1015
set_error(THD * thd)1016 void XID_STATE::set_error(THD *thd)
1017 {
1018 if (xa_state != XA_NOTR)
1019 rm_error= thd->get_stmt_da()->mysql_errno();
1020 }
1021
1022
store_xid_info(Protocol * protocol,bool print_xid_as_hex) const1023 void XID_STATE::store_xid_info(Protocol *protocol, bool print_xid_as_hex) const
1024 {
1025 protocol->store_longlong(static_cast<longlong>(m_xid.formatID), false);
1026 protocol->store_longlong(static_cast<longlong>(m_xid.gtrid_length), false);
1027 protocol->store_longlong(static_cast<longlong>(m_xid.bqual_length), false);
1028
1029 if (print_xid_as_hex)
1030 {
1031 /*
1032 xid_buf contains enough space for 0x followed by HEX representation
1033 of the binary XID data and one null termination character.
1034 */
1035 char xid_buf[XIDDATASIZE * 2 + 2 + 1];
1036
1037 xid_buf[0]= '0';
1038 xid_buf[1]= 'x';
1039
1040 size_t xid_str_len= bin_to_hex_str(xid_buf + 2, sizeof(xid_buf) - 2,
1041 const_cast<char*>(m_xid.data),
1042 m_xid.gtrid_length +
1043 m_xid.bqual_length) + 2;
1044 protocol->store(xid_buf, xid_str_len, &my_charset_bin);
1045 }
1046 else
1047 {
1048 protocol->store(m_xid.data, m_xid.gtrid_length + m_xid.bqual_length,
1049 &my_charset_bin);
1050 }
1051
1052
1053 }
1054
1055
1056 #ifndef NDEBUG
xid_to_str(char * buf) const1057 char* XID::xid_to_str(char *buf) const
1058 {
1059 char *s= buf;
1060 *s++= '\'';
1061
1062 for (int i= 0; i < gtrid_length + bqual_length; i++)
1063 {
1064 /* is_next_dig is set if next character is a number */
1065 bool is_next_dig= false;
1066 if (i < XIDDATASIZE)
1067 {
1068 char ch= data[i+1];
1069 is_next_dig= (ch >= '0' && ch <='9');
1070 }
1071 if (i == gtrid_length)
1072 {
1073 *s++= '\'';
1074 if (bqual_length)
1075 {
1076 *s++= '.';
1077 *s++= '\'';
1078 }
1079 }
1080 uchar c= static_cast<uchar>(data[i]);
1081 if (c < 32 || c > 126)
1082 {
1083 *s++= '\\';
1084 /*
1085 If next character is a number, write current character with
1086 3 octal numbers to ensure that the next number is not seen
1087 as part of the octal number
1088 */
1089 if (c > 077 || is_next_dig)
1090 *s++= _dig_vec_lower[c >> 6];
1091 if (c > 007 || is_next_dig)
1092 *s++=_dig_vec_lower[(c >> 3) & 7];
1093 *s++= _dig_vec_lower[c & 7];
1094 }
1095 else
1096 {
1097 if (c == '\'' || c == '\\')
1098 *s++= '\\';
1099 *s++= c;
1100 }
1101 }
1102 *s++= '\'';
1103 *s= 0;
1104 return buf;
1105 }
1106 #endif
1107
1108
1109 extern "C" uchar *transaction_get_hash_key(const uchar *, size_t *, my_bool);
1110 extern "C" void transaction_free_hash(void *);
1111
1112
1113 /**
1114 Callback that is called to get the key for a hash.
1115
1116 @param ptr pointer to the record
1117 @param length length of the record
1118
1119 @return pointer to a record stored in cache
1120 */
1121
transaction_get_hash_key(const uchar * ptr,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))1122 extern "C" uchar *transaction_get_hash_key(const uchar *ptr, size_t *length,
1123 my_bool not_used MY_ATTRIBUTE((unused)))
1124 {
1125 *length= ((Transaction_ctx*)ptr)->xid_state()->get_xid()->key_length();
1126 return ((Transaction_ctx*)ptr)->xid_state()->get_xid()->key();
1127 }
1128
1129
1130 /**
1131 Callback that is called to do cleanup.
1132
1133 @param ptr pointer to free
1134 */
1135
transaction_free_hash(void * ptr)1136 void transaction_free_hash(void *ptr)
1137 {
1138 Transaction_ctx *transaction= (Transaction_ctx*)ptr;
1139 // Only time it's allocated is during recovery process.
1140 if (transaction->xid_state()->is_in_recovery())
1141 delete transaction;
1142 }
1143
1144
1145 #ifdef HAVE_PSI_INTERFACE
1146 static PSI_mutex_key key_LOCK_transaction_cache;
1147
1148 static PSI_mutex_info transaction_cache_mutexes[]=
1149 {
1150 { &key_LOCK_transaction_cache, "LOCK_transaction_cache", PSI_FLAG_GLOBAL}
1151 };
1152
init_transaction_cache_psi_keys(void)1153 static void init_transaction_cache_psi_keys(void)
1154 {
1155 const char* category= "sql";
1156 int count;
1157
1158 count= array_elements(transaction_cache_mutexes);
1159 mysql_mutex_register(category, transaction_cache_mutexes, count);
1160 }
1161 #endif /* HAVE_PSI_INTERFACE */
1162
1163
transaction_cache_init()1164 bool transaction_cache_init()
1165 {
1166 #ifdef HAVE_PSI_INTERFACE
1167 init_transaction_cache_psi_keys();
1168 #endif
1169
1170 mysql_mutex_init(key_LOCK_transaction_cache, &LOCK_transaction_cache,
1171 MY_MUTEX_INIT_FAST);
1172 return my_hash_init(&transaction_cache, &my_charset_bin, 100, 0, 0,
1173 transaction_get_hash_key, transaction_free_hash, 0,
1174 key_memory_XID) != 0;
1175 }
1176
transaction_cache_free()1177 void transaction_cache_free()
1178 {
1179 if (my_hash_inited(&transaction_cache))
1180 {
1181 my_hash_free(&transaction_cache);
1182 mysql_mutex_destroy(&LOCK_transaction_cache);
1183 }
1184 }
1185
1186
transaction_cache_search(XID * xid)1187 Transaction_ctx *transaction_cache_search(XID *xid)
1188 {
1189 mysql_mutex_lock(&LOCK_transaction_cache);
1190
1191 Transaction_ctx *res=
1192 (Transaction_ctx *)my_hash_search(&transaction_cache,
1193 xid->key(),
1194 xid->key_length());
1195 mysql_mutex_unlock(&LOCK_transaction_cache);
1196 return res;
1197 }
1198
1199
transaction_cache_insert(XID * xid,Transaction_ctx * transaction)1200 bool transaction_cache_insert(XID *xid, Transaction_ctx *transaction)
1201 {
1202 mysql_mutex_lock(&LOCK_transaction_cache);
1203 if (my_hash_search(&transaction_cache, xid->key(),
1204 xid->key_length()))
1205 {
1206 mysql_mutex_unlock(&LOCK_transaction_cache);
1207 my_error(ER_XAER_DUPID, MYF(0));
1208 return true;
1209 }
1210 bool res= my_hash_insert(&transaction_cache, (uchar*)transaction);
1211 mysql_mutex_unlock(&LOCK_transaction_cache);
1212 return res;
1213 }
1214
1215
create_and_insert_new_transaction(XID * xid,bool is_binlogged_arg)1216 inline bool create_and_insert_new_transaction(XID *xid, bool is_binlogged_arg)
1217 {
1218 Transaction_ctx *transaction= new (std::nothrow) Transaction_ctx();
1219 XID_STATE *xs;
1220
1221 if (!transaction)
1222 {
1223 my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), sizeof(Transaction_ctx));
1224 return true;
1225 }
1226 xs= transaction->xid_state();
1227 xs->start_recovery_xa(xid, is_binlogged_arg);
1228
1229 return my_hash_insert(&transaction_cache, (uchar*)transaction);
1230 }
1231
1232
transaction_cache_detach(Transaction_ctx * transaction)1233 bool transaction_cache_detach(Transaction_ctx *transaction)
1234 {
1235 bool res= false;
1236 XID_STATE *xs= transaction->xid_state();
1237 XID xid= *(xs->get_xid());
1238 bool was_logged= xs->is_binlogged();
1239
1240
1241 assert(xs->has_state(XID_STATE::XA_PREPARED));
1242
1243 mysql_mutex_lock(&LOCK_transaction_cache);
1244
1245 assert(my_hash_search(&transaction_cache, xid.key(),
1246 xid.key_length()));
1247
1248 my_hash_delete(&transaction_cache, (uchar *)transaction);
1249 res= create_and_insert_new_transaction(&xid, was_logged);
1250
1251 mysql_mutex_unlock(&LOCK_transaction_cache);
1252
1253 return res;
1254 }
1255
1256
transaction_cache_insert_recovery(XID * xid)1257 bool transaction_cache_insert_recovery(XID *xid)
1258 {
1259 mysql_mutex_lock(&LOCK_transaction_cache);
1260
1261 if (my_hash_search(&transaction_cache, xid->key(),
1262 xid->key_length()))
1263 {
1264 mysql_mutex_unlock(&LOCK_transaction_cache);
1265 return false;
1266 }
1267
1268 /*
1269 It's assumed that XA transaction was binlogged before the server
1270 shutdown. If --log-bin has changed since that from OFF to ON, XA
1271 COMMIT or XA ROLLBACK of this transaction may be logged alone into
1272 the binary log.
1273 */
1274 bool res= create_and_insert_new_transaction(xid, true);
1275
1276 mysql_mutex_unlock(&LOCK_transaction_cache);
1277
1278 return res;
1279 }
1280
1281
transaction_cache_delete(Transaction_ctx * transaction)1282 void transaction_cache_delete(Transaction_ctx *transaction)
1283 {
1284 mysql_mutex_lock(&LOCK_transaction_cache);
1285 my_hash_delete(&transaction_cache, (uchar *)transaction);
1286 mysql_mutex_unlock(&LOCK_transaction_cache);
1287 }
1288
1289
1290 /**
1291 The function restores previously saved storage engine transaction context.
1292
1293 @param thd Thread context
1294 */
attach_native_trx(THD * thd)1295 static void attach_native_trx(THD *thd)
1296 {
1297 Ha_trx_info *ha_info=
1298 thd->get_transaction()->ha_trx_info(Transaction_ctx::SESSION);
1299 Ha_trx_info *ha_info_next;
1300
1301 if (ha_info)
1302 {
1303 for (; ha_info; ha_info= ha_info_next)
1304 {
1305 handlerton *hton= ha_info->ht();
1306 reattach_engine_ha_data_to_thd(thd, hton);
1307 ha_info_next= ha_info->next();
1308 ha_info->reset();
1309 }
1310 }
1311 else
1312 {
1313 /*
1314 Although the current `Ha_trx_info` object is null, we need to make sure
1315 that the data engine plugins have the oportunity to attach their internal
1316 transactions and clean up the session.
1317 */
1318 thd->rpl_reattach_engine_ha_data();
1319 }
1320 }
1321
1322
1323 /**
1324 This is a specific to "slave" applier collection of standard cleanup
1325 actions to reset XA transaction states at the end of XA prepare rather than
1326 to do it at the transaction commit, see @c ha_commit_one_phase.
1327 THD of the slave applier is dissociated from a transaction object in engine
1328 that continues to exist there.
1329
1330 @param THD current thread
1331 @return the value of is_error()
1332 */
1333
applier_reset_xa_trans(THD * thd)1334 bool applier_reset_xa_trans(THD *thd)
1335 {
1336 Transaction_ctx *trn_ctx= thd->get_transaction();
1337 XID_STATE *xid_state= trn_ctx->xid_state();
1338 /*
1339 In the following the server transaction state gets reset for
1340 a slave applier thread similarly to xa_commit logics
1341 except commit does not run.
1342 */
1343 thd->variables.option_bits&= ~OPTION_BEGIN;
1344 trn_ctx->reset_unsafe_rollback_flags(Transaction_ctx::STMT);
1345 thd->server_status&= ~SERVER_STATUS_IN_TRANS;
1346 /* Server transaction ctx is detached from THD */
1347 transaction_cache_detach(trn_ctx);
1348 xid_state->reset();
1349 /*
1350 The current engine transactions is detached from THD, and
1351 previously saved is restored.
1352 */
1353 attach_native_trx(thd);
1354 trn_ctx->set_ha_trx_info(Transaction_ctx::SESSION, NULL);
1355 trn_ctx->set_no_2pc(Transaction_ctx::SESSION, false);
1356 trn_ctx->cleanup();
1357 #ifdef HAVE_PSI_TRANSACTION_INTERFACE
1358 thd->m_transaction_psi= NULL;
1359 #endif
1360 thd->mdl_context.release_transactional_locks();
1361 /*
1362 On client sessions a XA PREPARE will always be followed by a XA COMMIT
1363 or a XA ROLLBACK, and both statements will reset the tx isolation level
1364 and access mode when the statement is finishing a transaction.
1365
1366 For replicated workload it is possible to have other transactions between
1367 the XA PREPARE and the XA [COMMIT|ROLLBACK].
1368
1369 So, if the slave applier changed the current transaction isolation level,
1370 it needs to be restored to the session default value after having the
1371 XA transaction prepared.
1372 */
1373 trans_reset_one_shot_chistics(thd);
1374
1375 return thd->is_error();
1376 }
1377
1378
1379 /**
1380 The function detaches existing storage engines transaction
1381 context from thd. Backup area to save it is provided to low level
1382 storage engine function.
1383
1384 is invoked by plugin_foreach() after
1385 trans_xa_start() for each storage engine.
1386
1387 @param[in,out] thd Thread context
1388 @param plugin Reference to handlerton
1389
1390 @return FALSE on success, TRUE otherwise.
1391 */
1392
detach_native_trx(THD * thd,plugin_ref plugin,void * unused)1393 my_bool detach_native_trx(THD *thd, plugin_ref plugin, void *unused)
1394 {
1395 handlerton *hton= plugin_data<handlerton *>(plugin);
1396
1397 if (hton->replace_native_transaction_in_thd)
1398 {
1399 /* Ensure any active backup engine ha_data won't be overwritten */
1400 assert(!thd->ha_data[hton->slot].ha_ptr_backup);
1401
1402 hton->replace_native_transaction_in_thd(thd, NULL,
1403 thd_ha_data_backup(thd, hton));
1404 }
1405
1406 return FALSE;
1407 }
1408
reattach_native_trx(THD * thd,plugin_ref plugin,void *)1409 my_bool reattach_native_trx(THD *thd, plugin_ref plugin, void *)
1410 {
1411 DBUG_ENTER("reattach_native_trx");
1412 handlerton *hton = plugin_data<handlerton *>(plugin);
1413
1414 if (hton->replace_native_transaction_in_thd)
1415 {
1416 /* restore the saved original engine transaction's link with thd */
1417 void **trx_backup = &thd->ha_data[hton->slot].ha_ptr_backup;
1418
1419 hton->replace_native_transaction_in_thd(thd, *trx_backup, NULL);
1420 *trx_backup = NULL;
1421 }
1422 DBUG_RETURN(FALSE);
1423 }
1424