[Tarantool-patches] [PATCH 2/2] memtx: fix a bug in TX that caused deletion of a durty tuple

Aleksandr Lyapunov alyapunov at tarantool.org
Thu Oct 22 19:02:33 MSK 2020


There was a mess in tuple refernce in TX history.
Now it was remade in the following asumptions:
 * a clean tuple belongs to space, and the space implicitly holds
a reference to the tuple.
 * a dirty tuple belongs to TX manager and a reference is held
in the corresponding story.

Closes #5423
---
 src/box/memtx_space.c    |  1 +
 src/box/memtx_tx.c       | 54 ++++++++++++++++++++++++++++++++++++++----------
 test/box/tx_man.result   | 43 ++++++++++++++++++++++++++++++++++++++
 test/box/tx_man.test.lua | 16 ++++++++++++++
 4 files changed, 103 insertions(+), 11 deletions(-)

diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index d4b18d9..2a43e64 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -78,6 +78,7 @@ void
 memtx_space_update_bsize(struct space *space, struct tuple *old_tuple,
 			 struct tuple *new_tuple)
 {
+	assert(space->vtab->destroy == &memtx_space_destroy);
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
 	ssize_t old_bsize = old_tuple ? box_tuple_bsize(old_tuple) : 0;
 	ssize_t new_bsize = new_tuple ? box_tuple_bsize(new_tuple) : 0;
diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c
index e378054..bcd4a37 100644
--- a/src/box/memtx_tx.c
+++ b/src/box/memtx_tx.c
@@ -418,8 +418,8 @@ memtx_tx_story_gc_step()
 		if (link->newer_story == NULL) {
 			/*
 			 * We are at the top of the chain. That means
-			 * that story->tuple is in index. If the story is
-			 * actually delete the tuple, it must be deleted from
+			 * that story->tuple is in index. If the story
+			 * actually deletes the tuple, it must be deleted from
 			 * index.
 			 */
 			if (story->del_psn > 0 && story->space != NULL) {
@@ -432,6 +432,12 @@ memtx_tx_story_gc_step()
 					panic("failed to rollback change");
 				}
 				assert(story->tuple == unused);
+			} else if (i == 0) {
+				/*
+				 * The tuple is left clean in the space.
+				 * It now belongs to the space and must be referenced.
+				 */
+				tuple_ref(story->tuple);
 			}
 
 			if (link->older.is_story) {
@@ -655,6 +661,14 @@ memtx_tx_history_add_stmt(struct txn_stmt *stmt, struct tuple *old_tuple,
 					  &replaced) != 0)
 				goto fail;
 			memtx_tx_story_link_tuple(add_story, replaced, i);
+			if (i == 0 && replaced != NULL && !replaced->is_dirty) {
+				/*
+				 * The tuple was clean and thus belonged to
+				 * the space. Now tx manager takes ownership
+				 * of it.
+				 */
+				tuple_unref(replaced);
+			}
 			add_story_linked++;
 
 			struct tuple *visible_replaced = NULL;
@@ -729,15 +743,16 @@ memtx_tx_history_add_stmt(struct txn_stmt *stmt, struct tuple *old_tuple,
 		collected_conflicts = collected_conflicts->next;
 	}
 
-	/*
-	 * We now reference both new and old tuple because the stmt holds
-	 * pointers to them.
-	 */
-	if (stmt->new_tuple != NULL)
-		tuple_ref(stmt->new_tuple);
 	*result = old_tuple;
-	if (*result != NULL)
+	if (*result != NULL) {
+		/*
+		 * The result must be a referenced pointer. The caller must
+		 * unreference it by itself.
+		 * Actually now it goes only to stmt->old_tuple, and
+		 * stmt->old_tuple is unreferenced when stmt is destroyed.
+		 */
 		tuple_ref(*result);
+	}
 	return 0;
 
 	fail:
@@ -756,6 +771,10 @@ memtx_tx_history_add_stmt(struct txn_stmt *stmt, struct tuple *old_tuple,
 				unreachable();
 				panic("failed to rollback change");
 			}
+			if (i == 0 && was != NULL && !was->is_dirty) {
+				/* Just rollback previous tuple_unref. */
+				tuple_ref(was);
+			}
 
 			memtx_tx_story_unlink(stmt->add_story, i);
 
@@ -787,6 +806,10 @@ memtx_tx_history_rollback_stmt(struct txn_stmt *stmt)
 		for (uint32_t i = 0; i < story->index_count; i++) {
 			struct memtx_story_link *link = &story->link[i];
 			if (link->newer_story == NULL) {
+				/*
+				 * We are at top of story list and thus
+				 * story->tuple is in index directly.
+				 */
 				struct tuple *unused;
 				struct index *index = stmt->space->index[i];
 				struct tuple *was = memtx_tx_story_older_tuple(link);
@@ -796,6 +819,17 @@ memtx_tx_history_rollback_stmt(struct txn_stmt *stmt)
 					unreachable();
 					panic("failed to rollback change");
 				}
+				if (i == 0 && was != NULL &&
+				    !link->older.is_story) {
+					/*
+					 * That was the last story in history.
+					 * The last tuple now belongs to space
+					 * and the space must hold a reference
+					 * to it.
+					 */
+					tuple_ref(was);
+				}
+
 			} else {
 				struct memtx_story *newer = link->newer_story;
 				assert(newer->link[i].older.is_story);
@@ -813,7 +847,6 @@ memtx_tx_history_rollback_stmt(struct txn_stmt *stmt)
 		}
 		stmt->add_story->add_stmt = NULL;
 		stmt->add_story = NULL;
-		tuple_unref(stmt->new_tuple);
 	}
 
 	if (stmt->del_story != NULL) {
@@ -987,7 +1020,6 @@ memtx_tx_history_commit_stmt(struct txn_stmt *stmt)
 		assert(stmt->del_story->del_stmt == stmt);
 		assert(stmt->next_in_del_list == NULL);
 		res -= stmt->del_story->tuple->bsize;
-		tuple_unref(stmt->del_story->tuple);
 		stmt->del_story->del_stmt = NULL;
 		stmt->del_story = NULL;
 	}
diff --git a/test/box/tx_man.result b/test/box/tx_man.result
index 3a6375d..a294165 100644
--- a/test/box/tx_man.result
+++ b/test/box/tx_man.result
@@ -826,6 +826,49 @@ s:select{}
  | - - [1, 3]
  | ...
 
+s:drop()
+ | ---
+ | ...
+s2:drop()
+ | ---
+ | ...
+
+-- https://github.com/tarantool/tarantool/issues/5423
+s = box.schema.space.create('test')
+ | ---
+ | ...
+i1 = s:create_index('pk', {parts={{1, 'uint'}}})
+ | ---
+ | ...
+i2 = s:create_index('sec', {parts={{2, 'uint'}}})
+ | ---
+ | ...
+
+s:replace{1, 0}
+ | ---
+ | - [1, 0]
+ | ...
+s:delete{1}
+ | ---
+ | - [1, 0]
+ | ...
+collectgarbage()
+ | ---
+ | - 0
+ | ...
+s:replace{1, 1}
+ | ---
+ | - [1, 1]
+ | ...
+s:replace{1, 2 }
+ | ---
+ | - [1, 2]
+ | ...
+
+s:drop()
+ | ---
+ | ...
+
 test_run:cmd("switch default")
  | ---
  | - true
diff --git a/test/box/tx_man.test.lua b/test/box/tx_man.test.lua
index 60fa52b..53b0773 100644
--- a/test/box/tx_man.test.lua
+++ b/test/box/tx_man.test.lua
@@ -236,6 +236,22 @@ s:select{}
 tx3:commit()
 s:select{}
 
+s:drop()
+s2:drop()
+
+-- https://github.com/tarantool/tarantool/issues/5423
+s = box.schema.space.create('test')
+i1 = s:create_index('pk', {parts={{1, 'uint'}}})
+i2 = s:create_index('sec', {parts={{2, 'uint'}}})
+
+s:replace{1, 0}
+s:delete{1}
+collectgarbage()
+s:replace{1, 1}
+s:replace{1, 2 }
+
+s:drop()
+
 test_run:cmd("switch default")
 test_run:cmd("stop server tx_man")
 test_run:cmd("cleanup server tx_man")
-- 
2.7.4



More information about the Tarantool-patches mailing list