mysql-server/sql/xa/sql_xa_second_phase.cc
2025-03-05 14:31:37 +07:00

184 lines
7.4 KiB
C++

/* 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();
}