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