/* Copyright (c) 2022, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sql/xa/sql_xa_second_phase.h" // Sql_cmd_xa_second_phase #include "mysqld_error.h" // Error codes #include "sql/debug_sync.h" // DEBUG_SYNC #include "sql/mdl_context_backup.h" // MDL_context_backup_manager #include "sql/raii/sentry.h" // raii::Sentry<> #include "sql/rpl_gtid.h" // gtid_state_commit_or_rollback #include "sql/rpl_replica_commit_order_manager.h" // Commit_order_manager #include "sql/sql_class.h" // THD #include "sql/transaction_info.h" // Transaction_ctx Sql_cmd_xa_second_phase::Sql_cmd_xa_second_phase(xid_t *xid_arg) : m_xid(xid_arg) {} bool Sql_cmd_xa_second_phase::find_and_initialize_xa_context(THD *thd) { // XID_STATE object used in the TC_LOG/handler execution stack auto thd_xs = thd->get_transaction()->xid_state(); this->m_detached_trx_context = find_trn_for_recover_and_check_its_state(thd, this->m_xid, thd_xs); if (this->m_detached_trx_context == nullptr) return (this->m_result = true); assert(*this->m_detached_trx_context->xid_state()->get_xid() == *this->m_xid); return false; } bool Sql_cmd_xa_second_phase::acquire_locks(THD *thd) { assert(this->m_detached_trx_context != nullptr); auto thd_xs = thd->get_transaction()->xid_state(); auto detached_xs = this->m_detached_trx_context->xid_state(); /* Metadata locks taken during XA COMMIT should be released when there is error in commit order execution, so we take a savepoint and rollback to it in case of error. The error during commit order execution can be temporary like commit order deadlock (check header comment for check_and_report_deadlock()) and can be recovered after retrying unlike other commit errors. And to do so we need to restore status of metadata locks i.e. rollback to savepoint, before the retry attempt to ensure order for applier threads. */ this->m_mdl_savepoint = thd->mdl_context.mdl_savepoint(); DEBUG_SYNC(thd, "detached_xa_commit_before_acquire_xa_lock"); /* Acquire XID_STATE::m_xa_lock to prevent concurrent running of two XA COMMIT/XA ROLLBACK statements. Without acquiring this lock an attempt to run two XA COMMIT/XA ROLLBACK statement for the same xid value may lead to writing two events for the same xid into the binlog (e.g. twice XA COMMIT event, that is an event for XA COMMIT some_xid_value followed by an another event XA COMMIT with the same xid value). As a consequences, presence of two XA COMMIT/XA ROLLACK statements for the same xid value in binlog would break replication. */ detached_xs->get_xa_lock().lock(); raii::Sentry<> mutex_guard_on_error{[detached_xs, this]() -> void { if (this->m_result) detached_xs->get_xa_lock().unlock(); }}; /* Double check that the XA transaction still does exist since the transaction could be removed from the cache by another XA COMMIT/XA ROLLBACK statement being executed concurrently from parallel session with the same xid value. */ if (find_trn_for_recover_and_check_its_state(thd, this->m_xid, thd_xs) == nullptr) return (this->m_result = true); if (acquire_mandatory_metadata_locks(thd, this->m_xid)) { /* We can't rollback an XA transaction on lock failure due to Innodb redo log and bin log update is involved in rollback. Return error to user for a retry. */ my_error(ER_XA_RETRY, MYF(0)); return (this->m_result = true); } DEBUG_SYNC(thd, "detached_xa_commit_after_acquire_commit_lock"); return false; } void Sql_cmd_xa_second_phase::release_locks() const { assert(this->m_detached_trx_context != nullptr); auto detached_xs = this->m_detached_trx_context->xid_state(); detached_xs->get_xa_lock().unlock(); } void Sql_cmd_xa_second_phase::setup_thd_context(THD *thd) { assert(this->m_detached_trx_context != nullptr); auto thd_xs = thd->get_transaction()->xid_state(); auto detached_xs = this->m_detached_trx_context->xid_state(); std::tie(this->m_gtid_error, this->m_need_clear_owned_gtid) = commit_owned_gtids(thd, true); if (this->m_gtid_error) my_error(ER_XA_RBROLLBACK, MYF(0)); this->m_result = detached_xs->xa_trans_rolled_back() || this->m_gtid_error; assert(thd_xs->is_binlogged() == false); /* Detached transaction XID_STATE::is_binlogged() is passed through THD XID_STATE class member to low-level logging routines for deciding how to log. */ if (detached_xs->is_binlogged()) thd_xs->set_binlogged(); else thd_xs->unset_binlogged(); } bool Sql_cmd_xa_second_phase::enter_commit_order(THD *thd) { auto thd_xs = thd->get_transaction()->xid_state(); if (Commit_order_manager::wait( thd)) { // Ensure externalization order for applier threads // (no-op for non-applier threads). Commit_order_manager::wait_and_finish( thd, true); // Remove from commit order queue and allow for a retry gtid_state_commit_or_rollback(thd, /* needs_to */ true, /* do_commit */ false); thd->mdl_context.rollback_to_savepoint(this->m_mdl_savepoint); thd_xs->unset_binlogged(); return (this->m_result = true); } return false; } void Sql_cmd_xa_second_phase::assign_xid_to_thd(THD *thd) const { assert(this->m_detached_trx_context != nullptr); auto thd_xs = thd->get_transaction()->xid_state(); thd_xs->start_detached_xa(this->m_xid, thd_xs->is_binlogged()); } void Sql_cmd_xa_second_phase::exit_commit_order(THD *thd) const { Commit_order_manager::wait_and_finish( thd, this->m_result); // Remove from the commit order queue (no-op for // non-applier threads). } void Sql_cmd_xa_second_phase::cleanup_context(THD *thd) const { auto thd_xs = thd->get_transaction()->xid_state(); // Restoring the binlogged status of the thd_xid_state after borrowing it // to pass the binlogged flag to binlog_xa_commit(). thd_xs->unset_binlogged(); MDL_context_backup_manager::instance().delete_backup( this->m_xid->key(), this->m_xid->key_length()); gtid_state_commit_or_rollback(thd, this->m_need_clear_owned_gtid, !this->m_result); } void Sql_cmd_xa_second_phase::dispose() { this->m_detached_trx_context.reset(); }