1
0
mirror of https://github.com/codership/wsrep-lib.git synced 2026-10-23 09:48:57 +03:00

MGL-52 Handle BF abort during XA commit/rollback by XID

Although XA commit/rollback by XID does not have active transaction
with changes and does not become BF aborted by lock conflict, there
are other reasons why it can become BF aborted (e.g. session becoming
killed).

Add handling of `s_must_abort` in `commit_or_rollback_by_xid()` and
unit tests for testing correct behavior.
This commit is contained in:
Teemu Ollakka
2026-02-16 13:46:15 +02:00
parent ece092ba53
commit 93bf4e5b7c
3 changed files with 113 additions and 2 deletions

View File

@@ -1206,9 +1206,20 @@ int wsrep::transaction::commit_or_rollback_by_xid(const wsrep::xid& xid,
ws_handle_,
flags(),
meta, nullptr));
client_service_.debug_sync("wsrep_commit_or_rollback_by_xid_after_certify");
lock.lock();
int ret;
if (cert_ret == wsrep::provider::success)
if (state_ == s_must_abort) {
/* This is hyphotetical situation: Commit or rollback by XID
* typically happens from a client context which does not have an active
* transaction, so there should not a possibility of BF abort. There
* may however be other corner cases (e.g. client session becomes killed
* during commit/rollback by XID), so this case must be handled
* neverteless. */
client_state_.override_error(wsrep::e_error_during_commit);
ret = 1;
}
else if (cert_ret == wsrep::provider::success)
{
if (commit)
{
@@ -1225,6 +1236,7 @@ int wsrep::transaction::commit_or_rollback_by_xid(const wsrep::xid& xid,
}
else
{
state(lock, s_must_abort);
client_state_.override_error(wsrep::e_error_during_commit,
cert_ret);
wsrep::log_error() << "Failed to commit_or_rollback_by_xid,"

View File

@@ -36,7 +36,7 @@ namespace wsrep_test
// Simple BF abort method to BF abort unordered transasctions
void bf_abort_unordered(wsrep::client_state& cc);
// Simple BF abort method to BF abort unordered transasctions
// Simple BF abort method to BF abort ordered transasctions
void bf_abort_ordered(wsrep::client_state& cc);
// BF abort method to abort transactions via provider

View File

@@ -17,6 +17,7 @@
* along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
*/
#include "client_state_fixture.hpp"
#include "test_utils.hpp"
#include <iostream>
//
@@ -135,6 +136,104 @@ BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_rollback_by_xid,
server_service.release_high_priority_service(hps);
}
//
// Test BF abort during commit_by_xid certify
//
BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_commit_by_xid_bf_abort,
replicating_two_clients_fixture_sync_rm)
{
wsrep::xid xid(1, 1, 1, "id");
cc1.start_transaction(wsrep::transaction_id(1));
cc1.assign_xid(xid);
cc1.before_prepare();
cc1.after_prepare();
BOOST_REQUIRE(sc.provider().fragments() == 1);
BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
cc1.xa_detach();
BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
BOOST_REQUIRE(cc1.after_statement() == 0);
cc2.start_transaction(wsrep::transaction_id(2));
cc2.assign_xid(xid);
// Inject BF abort for cc2's transaction during certify
wsrep_test::bf_abort_provider(sc, cc2, wsrep::seqno(100));
BOOST_REQUIRE(cc2.client_state::commit_by_xid(xid) == 1);
BOOST_REQUIRE(cc2.current_error() == wsrep::e_error_during_commit);
// Rollback cc2's transaction after failed commit_by_xid
BOOST_REQUIRE(cc2.before_rollback() == 0);
BOOST_REQUIRE(cc2.after_rollback() == 0);
BOOST_REQUIRE(cc2.transaction().state() == wsrep::transaction::s_aborted);
// Note that by convention `after_statement()` returns an error only
// when the statement can be retried. Currently that is only for
// deadlock errors. In this case the error is e_error_during_commit.
BOOST_REQUIRE(cc2.after_statement() == 0);
// xa_detach() creates a streaming applier, clean it up
wsrep::mock_high_priority_service* hps(
static_cast<wsrep::mock_high_priority_service*>(
sc.find_streaming_applier(xid)));
BOOST_REQUIRE(hps);
hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
hps->after_apply();
sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
server_service.release_high_priority_service(hps);
}
//
// Test BF abort after commit_by_xid certify
//
BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_commit_by_xid_bf_abort_after_cert,
replicating_two_clients_fixture_sync_rm)
{
wsrep::xid xid(1, 1, 1, "id");
cc1.start_transaction(wsrep::transaction_id(1));
cc1.assign_xid(xid);
cc1.before_prepare();
cc1.after_prepare();
BOOST_REQUIRE(sc.provider().fragments() == 1);
BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
cc1.xa_detach();
BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
BOOST_REQUIRE(cc1.after_statement() == 0);
cc2.start_transaction(wsrep::transaction_id(2));
cc2.assign_xid(xid);
cc2.sync_point_enabled_ = "wsrep_commit_or_rollback_by_xid_after_certify";
cc2.sync_point_action_ = wsrep::mock_client_service::spa_bf_abort_unordered;
BOOST_REQUIRE(cc2.client_state::commit_by_xid(xid) == 1);
BOOST_REQUIRE(cc2.current_error() == wsrep::e_error_during_commit);
// Rollback cc2's transaction after failed commit_by_xid
BOOST_REQUIRE(cc2.before_rollback() == 0);
BOOST_REQUIRE(cc2.after_rollback() == 0);
BOOST_REQUIRE(cc2.transaction().state() == wsrep::transaction::s_aborted);
// Note that by convention `after_statement()` returns an error only
// when the statement can be retried. Currently that is only for
// deadlock errors. In this case the error is e_error_during_commit.
BOOST_REQUIRE(cc2.after_statement() == 0);
// xa_detach() creates a streaming applier, clean it up
wsrep::mock_high_priority_service* hps(
static_cast<wsrep::mock_high_priority_service*>(
sc.find_streaming_applier(xid)));
BOOST_REQUIRE(hps);
hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
hps->after_apply();
sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
server_service.release_high_priority_service(hps);
}
//
// Test XA replay
//