From 7203b9487038689e2dc820bbf5072d4be1cb47fa Mon Sep 17 00:00:00 2001
From: Alexandr Lyapunov <a.lyapunov@corp.mail.ru>
Date: Thu, 9 Jul 2015 19:01:15 +0300
Subject: [PATCH] fixed gh-727 : RTREE index safety if out of memory by adding
 preallocated number of pages

---
 src/box/memtx_engine.cc        | 44 ++++++++++++++-
 src/box/memtx_engine.h         |  7 +++
 test/box/errinj_index.result   | 99 ++++++++++++++--------------------
 test/box/errinj_index.test.lua | 19 ++-----
 4 files changed, 96 insertions(+), 73 deletions(-)

diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index 8647dadd43..885b663aa4 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -58,6 +58,12 @@ static bool memtx_index_arena_initialized = false;
 static struct slab_arena memtx_index_arena;
 static struct slab_cache memtx_index_arena_slab_cache;
 static struct mempool memtx_index_extent_pool;
+static int memtx_index_num_reserved_extents;
+static void *memtx_index_reserved_extents;
+
+enum {
+	RESERVE_EXTENTS_BEFORE_REPLACE = 16
+};
 
 /**
  * A version of space_replace for a space which has
@@ -158,6 +164,7 @@ memtx_replace_all_keys(struct txn *txn, struct space *space,
 		       struct tuple *old_tuple, struct tuple *new_tuple,
 		       enum dup_replace_mode mode)
 {
+	memtx_index_extent_reserve(RESERVE_EXTENTS_BEFORE_REPLACE);
 	uint32_t i = 0;
 	try {
 		/* Update the primary key */
@@ -828,6 +835,9 @@ memtx_index_arena_init()
 	mempool_create(&memtx_index_extent_pool,
 		       &memtx_index_arena_slab_cache,
 		       MEMTX_EXTENT_SIZE);
+	/* Empty reserved list */
+	memtx_index_num_reserved_extents = 0;
+	memtx_index_reserved_extents = 0;
 	/* Done */
 	memtx_index_arena_initialized = true;
 }
@@ -838,7 +848,19 @@ memtx_index_arena_init()
 void *
 memtx_index_extent_alloc()
 {
-	ERROR_INJECT(ERRINJ_INDEX_ALLOC, return 0);
+	if (memtx_index_reserved_extents) {
+		assert(memtx_index_num_reserved_extents > 0);
+		memtx_index_num_reserved_extents--;
+		void *result = memtx_index_reserved_extents;
+		memtx_index_reserved_extents = *(void **)
+			memtx_index_reserved_extents;
+		return result;
+	}
+	ERROR_INJECT(ERRINJ_INDEX_ALLOC,
+		     /* same error as in mempool_alloc */
+		     tnt_raise(OutOfMemory, MEMTX_EXTENT_SIZE,
+			       "mempool", "new slab")
+	);
 	return mempool_alloc(&memtx_index_extent_pool);
 }
 
@@ -850,3 +872,23 @@ memtx_index_extent_free(void *extent)
 {
 	return mempool_free(&memtx_index_extent_pool, extent);
 }
+
+/**
+ * Reserve num extents in pool.
+ * Ensure that next num extent_alloc will succeed w/o an error
+ */
+void
+memtx_index_extent_reserve(int num)
+{
+	ERROR_INJECT(ERRINJ_INDEX_ALLOC,
+	/* same error as in mempool_alloc */
+		     tnt_raise(OutOfMemory, MEMTX_EXTENT_SIZE,
+			       "mempool", "new slab")
+	);
+	while (memtx_index_num_reserved_extents < num) {
+		void *ext = mempool_alloc(&memtx_index_extent_pool);
+		*(void **)ext = memtx_index_reserved_extents;
+		memtx_index_reserved_extents = ext;
+		memtx_index_num_reserved_extents++;
+	}
+}
\ No newline at end of file
diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index b8e771687c..ed0561f1c8 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -95,4 +95,11 @@ memtx_index_extent_alloc();
 void
 memtx_index_extent_free(void *extent);
 
+/**
+ * Reserve num extents in pool.
+ * Ensure that next num extent_alloc will succeed w/o an error
+ */
+void
+memtx_index_extent_reserve(int num);
+
 #endif /* TARANTOOL_BOX_MEMTX_ENGINE_H_INCLUDED */
diff --git a/test/box/errinj_index.result b/test/box/errinj_index.result
index dc640cc3d8..f3943dccac 100644
--- a/test/box/errinj_index.result
+++ b/test/box/errinj_index.result
@@ -93,11 +93,11 @@ res
 ...
 for i = 501,2500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 s:delete{1}
 ---
-- [1, 1, 'test1']
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 res = {}
 ---
@@ -107,7 +107,8 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [2, 2, 'test2']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
   - [3, 3, 'test3']
   - [4, 4, 'test4']
   - [5, 5, 'test5']
@@ -125,29 +126,10 @@ for i = 501,510 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [501, 501]
-  - [502, 502]
-  - [503, 503]
-  - [504, 504]
-  - [505, 505]
-  - [506, 506]
-  - [507, 507]
-  - [508, 508]
-  - [509, 509]
-  - [510, 510]
-...
-res = {}
----
-...
-for i = 2001,2010 do table.insert(res, (s:get{i})) end
----
-...
-res
----
 - []
 ...
---count must be greater that 1000 but less than 2000
-function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 1000 and "fail 1" or count >= 2000 and "fail 2" or "ok" end
+--count must be exactly 10
+function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count == 10 and "ok" or "fail" end
 ---
 ...
 check_iter_and_size()
@@ -156,11 +138,11 @@ check_iter_and_size()
 ...
 for i = 2501,3500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 s:delete{2}
 ---
-- [2, 2, 'test2']
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 check_iter_and_size()
 ---
@@ -174,7 +156,9 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [3, 3, 'test3']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
   - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
@@ -183,18 +167,6 @@ res
   - [9, 9, 'test9']
   - [10, 10, 'test10']
 ...
-for i = 3501,4500 do s:insert{i, i} end
----
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
-...
-s:delete{3}
----
-- [3, 3, 'test3']
-...
-check_iter_and_size()
----
-- ok
-...
 errinj.set("ERRINJ_INDEX_ALLOC", false)
 ---
 - ok
@@ -210,7 +182,10 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [4, 4, 'test4']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
+  - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
   - [7, 7, 'test7']
@@ -230,7 +205,10 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [4, 4, 'test4']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
+  - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
   - [7, 7, 'test7']
@@ -352,11 +330,11 @@ res
 ...
 for i = 501,2500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 10 bytes in hash_table for key
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 s:delete{1}
 ---
-- [1, 1, 'test1']
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 res = {}
 ---
@@ -366,7 +344,8 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [2, 2, 'test2']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
   - [3, 3, 'test3']
   - [4, 4, 'test4']
   - [5, 5, 'test5']
@@ -396,23 +375,19 @@ res
 ---
 - []
 ...
---since every insertion is rejected, count must be (10 - number of deletions)
-function check_iter_and_size(size_must_be) local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end print (count) return count ~= size_must_be and "fail 1" or "ok" end
----
-...
-check_iter_and_size(9)
+check_iter_and_size()
 ---
 - ok
 ...
 for i = 2501,3500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 9 bytes in hash_table for key
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 s:delete{2}
 ---
-- [2, 2, 'test2']
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
-check_iter_and_size(8)
+check_iter_and_size()
 ---
 - ok
 ...
@@ -424,7 +399,9 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [3, 3, 'test3']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
   - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
@@ -435,13 +412,13 @@ res
 ...
 for i = 3501,4500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 8 bytes in hash_table for key
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
 s:delete{3}
 ---
-- [3, 3, 'test3']
+- error: Failed to allocate 16384 bytes in mempool for new slab
 ...
-check_iter_and_size(7)
+check_iter_and_size()
 ---
 - ok
 ...
@@ -460,7 +437,10 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [4, 4, 'test4']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
+  - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
   - [7, 7, 'test7']
@@ -480,7 +460,10 @@ for i = 1,10 do table.insert(res, (s:get{i})) end
 ...
 res
 ---
-- - [4, 4, 'test4']
+- - [1, 1, 'test1']
+  - [2, 2, 'test2']
+  - [3, 3, 'test3']
+  - [4, 4, 'test4']
   - [5, 5, 'test5']
   - [6, 6, 'test6']
   - [7, 7, 'test7']
diff --git a/test/box/errinj_index.test.lua b/test/box/errinj_index.test.lua
index c725b490fe..6068965ee2 100644
--- a/test/box/errinj_index.test.lua
+++ b/test/box/errinj_index.test.lua
@@ -31,12 +31,9 @@ res
 res = {}
 for i = 501,510 do table.insert(res, (s:get{i})) end
 res
-res = {}
-for i = 2001,2010 do table.insert(res, (s:get{i})) end
-res
 
---count must be greater that 1000 but less than 2000
-function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 1000 and "fail 1" or count >= 2000 and "fail 2" or "ok" end
+--count must be exactly 10
+function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count == 10 and "ok" or "fail" end
 check_iter_and_size()
 
 for i = 2501,3500 do s:insert{i, i} end
@@ -46,10 +43,6 @@ res = {}
 for i = 1,10 do table.insert(res, (s:get{i})) end
 res
 
-for i = 3501,4500 do s:insert{i, i} end
-s:delete{3}
-check_iter_and_size()
-
 errinj.set("ERRINJ_INDEX_ALLOC", false)
 
 for i = 4501,5500 do s:insert{i, i} end
@@ -102,20 +95,18 @@ res = {}
 for i = 2001,2010 do table.insert(res, (s:get{i})) end
 res
 
---since every insertion is rejected, count must be (10 - number of deletions)
-function check_iter_and_size(size_must_be) local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end print (count) return count ~= size_must_be and "fail 1" or "ok" end
-check_iter_and_size(9)
+check_iter_and_size()
 
 for i = 2501,3500 do s:insert{i, i} end
 s:delete{2}
-check_iter_and_size(8)
+check_iter_and_size()
 res = {}
 for i = 1,10 do table.insert(res, (s:get{i})) end
 res
 
 for i = 3501,4500 do s:insert{i, i} end
 s:delete{3}
-check_iter_and_size(7)
+check_iter_and_size()
 
 errinj.set("ERRINJ_INDEX_ALLOC", false)
 
-- 
GitLab