From 59e4cf614baadaf9131f3bda9dd66e050277d7aa Mon Sep 17 00:00:00 2001
From: Andrey Saranchin <Andrey22102001@gmail.com>
Date: Wed, 28 Aug 2024 12:47:34 +0300
Subject: [PATCH] memtx: do not rollback prepared statements on DDL

Currently, when DDL is being committed, we delete all the stories and
rollback prepared delete statements. The problem is such rollback is
likely to fail because of assertion. When transaction is rolled back,
all the statements are rolled back in reversed order, but when rollback
happened because of DDL, order is not specified and some invariants are
violated. Let's simply unlink delete statement instead of rollback.

Rollback does two things: unlink delete statement and abort readers
(including gaps) of prepared stories. The commit actually drops the
second part - it's safe because after the previous commits we delete
stories right after aborting all concurrent transactions so there is no
need to abort anything anymore.

Part of #10146
Closes #10474

NO_CHANGELOG=later
NO_DOC=bugfix

(cherry picked from commit 6a11224c85c1be28e7d1570cd4ba01efc033c34f)
---
 src/box/memtx_tx.c                 |  2 +-
 test/box-luatest/mvcc_ddl_test.lua | 56 ++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 1 deletion(-)

diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c
index 4c0980598c..97b0ee2088 100644
--- a/src/box/memtx_tx.c
+++ b/src/box/memtx_tx.c
@@ -2353,7 +2353,7 @@ memtx_tx_history_remove_added_story(struct txn_stmt *stmt)
 static void
 memtx_tx_history_remove_deleted_story(struct txn_stmt *stmt)
 {
-	memtx_tx_history_rollback_deleted_story(stmt);
+	memtx_tx_story_unlink_deleted_by(stmt->del_story, stmt);
 }
 
 /*
diff --git a/test/box-luatest/mvcc_ddl_test.lua b/test/box-luatest/mvcc_ddl_test.lua
index 0c5c8f7afd..0279172c55 100644
--- a/test/box-luatest/mvcc_ddl_test.lua
+++ b/test/box-luatest/mvcc_ddl_test.lua
@@ -416,3 +416,59 @@ g.test_rollback_prepared_dml_and_ddl = function(cg)
         t.assert_equals(s:select{}, saved_select)
     end)
 end
+
+-- The test checks if DDL correctly handles stories of prepared statements
+-- on DDL
+-- gh-10474, case 2
+g.test_ddl_handles_prepared = function(cg)
+    t.tarantool.skip_if_not_debug()
+    cg.server:exec(function()
+        local fiber = require('fiber')
+        box.schema.space.create('test')
+        box.space.test:create_index('pk')
+
+        box.space.test:replace{1}
+
+        local teardown = false
+
+        local function writer(do_not_commit)
+            box.begin{txn_isolation = 'read-committed'}
+            box.space.test:replace{1}
+            if do_not_commit then
+                while not teardown do
+                    fiber.sleep(0)
+                end
+                box.rollback()
+            else
+                box.commit()
+            end
+        end
+
+        box.error.injection.set('ERRINJ_WAL_DELAY', true)
+        -- Start preparing writers and then update the space
+        local fibers = {}
+        for _ = 1, 5 do
+            local f = fiber.create(writer)
+            f:set_joinable(true)
+            table.insert(fibers, f)
+            f = fiber.create(writer, true)
+            f:set_joinable(true)
+            table.insert(fibers, f)
+        end
+        local f = fiber.create(function()
+            box.space.test:format{{name = 'field1', type = 'scalar'}}
+        end)
+        f:set_joinable(true)
+
+        box.error.injection.set('ERRINJ_WAL_DELAY', false)
+
+        -- Wait for DDL and DML are over and successful
+        local ok = f:join()
+        t.assert(ok)
+        teardown = true
+        for _, f in pairs(fibers) do
+            ok = f:join()
+            t.assert(ok)
+        end
+    end)
+end
-- 
GitLab