From 9d42ad47720f4a7f1ec2d7e90889ded4bd9c5f1c Mon Sep 17 00:00:00 2001
From: Aleksandr Lyapunov <alyapunov@tarantool.org>
Date: Mon, 2 Aug 2021 10:56:05 +0300
Subject: [PATCH] txm: avoid excess conflict while reading gaps

During iteration a memtx tree index must write gap records to TX
manager. It is done in order to detect the further writes to that
gaps and execute some logic preventing phantom reads.

There are two cases when that gap is stores:
 * Iterator reads the next tuple, the gap is between two tuples.
 * Iterator finished reading, the gap is between the previous
tuple and the key boundary.

By a mistake these two cases were not distinguished correctly and
that led to excess conflicts.

This patch fixes it.

Part of #6206
---
 src/box/memtx_tree.cc              | 17 +++++---
 test/box/tx_man_intervals.result   | 68 ++++++++++++++++++++++++++++++
 test/box/tx_man_intervals.test.lua | 22 ++++++++++
 3 files changed, 102 insertions(+), 5 deletions(-)

diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
index 2fe526aa7a..aafe1ad04e 100644
--- a/src/box/memtx_tree.cc
+++ b/src/box/memtx_tree.cc
@@ -385,9 +385,13 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
 	}
 	struct index *idx = iterator->index;
 	struct space *space = space_by_id(iterator->space_id);
-	if (res != NULL && *ret == NULL) {
-		/** Got end of key. */
-		memtx_tx_track_gap(in_txn(), space, idx, *ret, ITER_EQ,
+	if (*ret == NULL) {
+		/*
+		 * Got end of key. Store gap from the previous tuple to the
+		 * key boundary in nearby tuple.
+		 */
+		struct tuple *nearby_tuple = res == NULL ? NULL : res->tuple;
+		memtx_tx_track_gap(in_txn(), space, idx, nearby_tuple, ITER_EQ,
 				   it->key_data.key, it->key_data.part_count);
 	} else {
 		/*
@@ -435,8 +439,11 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
 	}
 	struct index *idx = iterator->index;
 	struct space *space = space_by_id(iterator->space_id);
-	if (res != NULL && *ret == NULL) {
-		/** Got end of key. */
+	if (*ret == NULL) {
+		/*
+		 * Got end of key. Store gap from the key boundary to the
+		 * previous tuple in nearby tuple.
+		 */
 		memtx_tx_track_gap(in_txn(), space, idx, successor, ITER_REQ,
 				   it->key_data.key, it->key_data.part_count);
 	} else {
diff --git a/test/box/tx_man_intervals.result b/test/box/tx_man_intervals.result
index b098151c57..b2e8b61bed 100644
--- a/test/box/tx_man_intervals.result
+++ b/test/box/tx_man_intervals.result
@@ -402,6 +402,74 @@ s:truncate()
 s:drop()
 ---
 ...
+-- Excess conflict
+s = box.schema.create_space('test')
+---
+...
+pk = s:create_index('pk', {parts={{1, 'uint'},{2, 'uint'}}})
+---
+...
+s:replace{2, 2, 2}
+---
+- [2, 2, 2]
+...
+tx1:begin()
+---
+- 
+...
+tx1('s:select{2}')
+---
+- - [[2, 2, 2]]
+...
+s:replace{3, 3, 3}
+---
+- [3, 3, 3]
+...
+tx1('s:replace{6, 6, 6}')
+---
+- - [6, 6, 6]
+...
+tx1:commit()
+---
+- 
+...
+s:drop()
+---
+...
+-- Excess conflict (reverse)
+s = box.schema.create_space('test')
+---
+...
+pk = s:create_index('pk', {parts={{1, 'uint'},{2, 'uint'}}})
+---
+...
+s:replace{2, 2, 2}
+---
+- [2, 2, 2]
+...
+tx1:begin()
+---
+- 
+...
+tx1('s:select({2}, {iterator=\'REQ\'})')
+---
+- - [[2, 2, 2]]
+...
+s:replace{1, 1, 1}
+---
+- [1, 1, 1]
+...
+tx1('s:replace{6, 6, 6}')
+---
+- - [6, 6, 6]
+...
+tx1:commit()
+---
+- 
+...
+s:drop()
+---
+...
 test_run:cmd("switch default")
 ---
 - true
diff --git a/test/box/tx_man_intervals.test.lua b/test/box/tx_man_intervals.test.lua
index 2080d33eae..bcc7894923 100644
--- a/test/box/tx_man_intervals.test.lua
+++ b/test/box/tx_man_intervals.test.lua
@@ -141,6 +141,28 @@ s:truncate()
 
 s:drop()
 
+-- Excess conflict
+s = box.schema.create_space('test')
+pk = s:create_index('pk', {parts={{1, 'uint'},{2, 'uint'}}})
+s:replace{2, 2, 2}
+tx1:begin()
+tx1('s:select{2}')
+s:replace{3, 3, 3}
+tx1('s:replace{6, 6, 6}')
+tx1:commit()
+s:drop()
+
+-- Excess conflict (reverse)
+s = box.schema.create_space('test')
+pk = s:create_index('pk', {parts={{1, 'uint'},{2, 'uint'}}})
+s:replace{2, 2, 2}
+tx1:begin()
+tx1('s:select({2}, {iterator=\'REQ\'})')
+s:replace{1, 1, 1}
+tx1('s:replace{6, 6, 6}')
+tx1:commit()
+s:drop()
+
 test_run:cmd("switch default")
 test_run:cmd("stop server tx_man")
 test_run:cmd("cleanup server tx_man")
-- 
GitLab