184 lines
7.4 KiB
C++
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();
|
|
}
|