diff --git a/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md b/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md new file mode 100644 index 0000000000000000000000000000000000000000..2ec6a52d1ebbe08712f4f8c5e96792b7fcb23457 --- /dev/null +++ b/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md @@ -0,0 +1,4 @@ +## bugfix/replication + +* Now transactions are not rolled back if the transaction fiber is + cancelled when waiting for quorum from replicas (gh-9480). diff --git a/src/box/txn.c b/src/box/txn.c index a8d81dacd9cb123f84689518c41f91bf1b5df9d3..0a304fab861cf47850d7da0159556c8cd72fa6cd 100644 --- a/src/box/txn.c +++ b/src/box/txn.c @@ -1105,8 +1105,15 @@ txn_commit(struct txn *txn) txn_limbo_ack(&txn_limbo, txn_limbo.owner_id, limbo_entry->lsn); } - if (txn_limbo_wait_complete(&txn_limbo, limbo_entry) < 0) - goto rollback; + int rc = txn_limbo_wait_complete(&txn_limbo, limbo_entry); + if (rc < 0) { + if (fiber_is_cancelled()) { + txn->fiber = NULL; + return -1; + } else { + goto rollback; + } + } } assert(txn_has_flag(txn, TXN_IS_DONE)); assert(txn->signature >= 0); diff --git a/src/box/txn_limbo.c b/src/box/txn_limbo.c index e4b77ba7e4a40a864d9c540eb02bdf9ef829e287..00618bb94424dbef7b34077cc452634c0ba041a0 100644 --- a/src/box/txn_limbo.c +++ b/src/box/txn_limbo.c @@ -283,6 +283,8 @@ txn_limbo_wait_complete(struct txn_limbo *limbo, struct txn_limbo_entry *entry) int rc = fiber_cond_wait_timeout(&limbo->wait_cond, timeout); if (txn_limbo_entry_is_complete(entry)) goto complete; + if (rc != 0 && fiber_is_cancelled()) + return -1; if (txn_limbo_is_frozen(limbo)) goto wait; if (rc != 0) diff --git a/src/box/txn_limbo.h b/src/box/txn_limbo.h index b81b71645127c5ef8ce56b650e43b9a617dc059a..ce93c9d10c100476addf324a474d81ae07423085 100644 --- a/src/box/txn_limbo.h +++ b/src/box/txn_limbo.h @@ -343,6 +343,7 @@ txn_limbo_ack(struct txn_limbo *limbo, uint32_t replica_id, int64_t lsn); * entry is either committed or rolled back. * If timeout is reached before acks are collected, the tx is * rolled back as well as all the txs in the limbo following it. + * If fiber is cancelled before acks are collected, the tx is left in limbo. * Returns -1 when rollback was performed and tx has to be freed. * 0 when tx processing can go on. */ diff --git a/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua b/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..ad9c8703b290c21e38f6414e8e3d1d8ec784d2f1 --- /dev/null +++ b/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua @@ -0,0 +1,46 @@ +local t = require('luatest') +local server = require('luatest.server') + +local g = t.group() + +g.before_each(function(cg) + local box_cfg = { + election_mode='off', + replication_synchro_timeout=1000, + replication_synchro_quorum = 2, + memtx_use_mvcc_engine = false, + } + cg.server = server:new({box_cfg = box_cfg}) + cg.server:start() +end) + +g.after_each(function(cg) + cg.server:drop() +end) + +-- Test that if we cancel TX while it is waiting quorum in limbo it is +-- not rolled back. +g.test_cancel_tx_waiting_in_limbo = function(cg) + cg.server:exec(function() + local fiber = require('fiber') + + local space = box.schema.create_space('test', {is_sync = true}) + space:create_index('pk') + box.ctl.promote() + + local f = fiber.new(function() + space:insert({1}) + end) + f:set_joinable(true) + f:wakeup() + + t.helpers.retrying({timeout = 3}, function() + t.assert(box.info.synchro.queue.len == 1) + end) + f:cancel() + local ret, err = f:join() + t.assert_equals(ret, false) + t.assert_equals(err:unpack().type, 'FiberIsCancelled') + t.assert_not_equals(space:get({1}), nil) + end) +end