diff --git a/changelogs/unreleased/gh-8104-memtx-mvcc-assertion-failure-during-stmt-preparation.md b/changelogs/unreleased/gh-8104-memtx-mvcc-assertion-failure-during-stmt-preparation.md new file mode 100644 index 0000000000000000000000000000000000000000..2f2c6878ae1fc48b44dc1d40fba97f3da8e17eb2 --- /dev/null +++ b/changelogs/unreleased/gh-8104-memtx-mvcc-assertion-failure-during-stmt-preparation.md @@ -0,0 +1,3 @@ +## bugfix/memtx + +* Fixed assertion failure in MVCC during statement preparation (gh-8104). diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c index 183a725c72047c6212b044a57572fbb3449aa6e1..e309cfea92246c85a66a73a8ccc08461a8d6643c 100644 --- a/src/box/memtx_tx.c +++ b/src/box/memtx_tx.c @@ -2409,14 +2409,6 @@ memtx_tx_history_prepare_insert_stmt(struct txn_stmt *stmt) uint32_t index_count = story->index_count; memtx_tx_history_sink_story(story); - struct memtx_story *old_story = story->link[0].older_story; - if (stmt->del_story == NULL) - assert(old_story == NULL || old_story->rollbacked || - old_story->del_psn != 0); - else - assert(old_story != NULL && - (old_story->rollbacked || stmt->del_story == old_story)); - if (stmt->del_story == NULL) { /* * This statement replaced nothing. That means that before @@ -2449,15 +2441,26 @@ memtx_tx_history_prepare_insert_stmt(struct txn_stmt *stmt) } } - if (old_story != NULL && old_story->rollbacked) { - assert(old_story->link[0].older_story == NULL); - old_story = NULL; + struct memtx_story *old_story = story->link[0].older_story; + if (stmt->del_story == NULL) + assert(old_story == NULL || old_story->rollbacked || + old_story->del_psn != 0); + else + assert(old_story != NULL && + (old_story->rollbacked || stmt->del_story == old_story)); + if (old_story != NULL) { + if (old_story->rollbacked) { + assert(old_story->link[0].older_story == NULL); + old_story = NULL; + } else if (old_story->del_psn != 0) { + assert(stmt->del_story == NULL); + old_story = NULL; + } } - if (old_story != NULL) { /* * There can be some transactions that want to delete old_story. - * It can be this transaction, or some other prepared TX. + * It can be this transaction. * All other transactions must be aborted or relinked to delete * this tuple. */ diff --git a/test/box-luatest/gh_8104_memtx_mvcc_assertion_failure_during_stmt_preparation_test.lua b/test/box-luatest/gh_8104_memtx_mvcc_assertion_failure_during_stmt_preparation_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..1d8859669eed9272a08956f5bb4a976e3075013e --- /dev/null +++ b/test/box-luatest/gh_8104_memtx_mvcc_assertion_failure_during_stmt_preparation_test.lua @@ -0,0 +1,49 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function(cg) + cg.server = server:new { + alias = 'dflt', + box_cfg = {memtx_use_mvcc_engine = true} + } + cg.server:start() + cg.server:exec(function() + local s = box.schema.create_space('s') + s:create_index('pk') + end) +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +-- Checks that preparation of an insert statement with an older story deleted by +-- a prepared transaction does not fail assertion. +g.test_preparation_with_deleted_older_story_assertion = function(cg) + cg.server:exec(function() + local fiber = require('fiber') + local t = require('luatest') + + box.space.s:replace{0} + + local first_replace = fiber.create(function() + fiber.self():set_joinable(true) + box.atomic(function() + box.space.s:delete{0} + end) + end) + local second_replace = fiber.create(function() + fiber.self():set_joinable(true) + box.atomic(function() + box.space.s:insert{0} + end) + end) + + first_replace:join() + second_replace:join() + + t.assert_equals(box.space.s:select{}, {{0}}) + end) +end