diff --git a/src/box/index.cc b/src/box/index.cc index 4e48671182ff967454ee7573d6a2eb2942640b8e..c2fc0086752693fec75ba97459a049af98dbc6f4 100644 --- a/src/box/index.cc +++ b/src/box/index.cc @@ -733,6 +733,13 @@ int generic_index_build_next(struct index *index, struct tuple *tuple) { struct tuple *unused; + /* + * Note this is not no-op call in case of rtee index: + * reserving 0 bytes is required during rtree recovery. + * For details see memtx_rtree_index_reserve(). + */ + if (index_reserve(index, 0) != 0) + return -1; return index_replace(index, NULL, tuple, DUP_INSERT, &unused); } diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h index f562c66df4fb70dfd13c793bdf5f2177aaa6b348..8b380bf3ccc5f7335d7d94e74403899ac005e8cf 100644 --- a/src/box/memtx_engine.h +++ b/src/box/memtx_engine.h @@ -87,6 +87,18 @@ enum memtx_recovery_state { /** Memtx extents pool, available to statistics. */ extern struct mempool memtx_index_extent_pool; +enum memtx_reserve_extents_num { + /** + * This number is calculated based on the + * max (realistic) number of insertions + * a deletion from a B-tree or an R-tree + * can lead to, and, as a result, the max + * number of new block allocations. + */ + RESERVE_EXTENTS_BEFORE_DELETE = 8, + RESERVE_EXTENTS_BEFORE_REPLACE = 16 +}; + /** * The size of the biggest memtx iterator. Used with * mempool_create. This is the size of the block that will be diff --git a/src/box/memtx_rtree.c b/src/box/memtx_rtree.c index 8badad797b4ab251824364ebc7ad14adbd43d4ea..612fcb2a9f97d2f644624f86cda10a041da85ba1 100644 --- a/src/box/memtx_rtree.c +++ b/src/box/memtx_rtree.c @@ -242,6 +242,24 @@ memtx_rtree_index_replace(struct index *base, struct tuple *old_tuple, return 0; } +static int +memtx_rtree_index_reserve(struct index *base, uint32_t size_hint) +{ + /* + * In case of rtree we use reserve to make sure that + * memory allocation will not fail during any operation + * on rtree, because there is no error handling in the + * rtree lib. + */ + (void)size_hint; + ERROR_INJECT(ERRINJ_INDEX_RESERVE, { + diag_set(OutOfMemory, MEMTX_EXTENT_SIZE, "mempool", "new slab"); + return -1; + }); + struct memtx_engine *memtx = (struct memtx_engine *)base->engine; + return memtx_index_extent_reserve(memtx, RESERVE_EXTENTS_BEFORE_REPLACE); +} + static struct iterator * memtx_rtree_index_create_iterator(struct index *base, enum iterator_type type, const char *key, uint32_t part_count) @@ -333,7 +351,7 @@ static const struct index_vtab memtx_rtree_index_vtab = { /* .compact = */ generic_index_compact, /* .reset_stat = */ generic_index_reset_stat, /* .begin_build = */ generic_index_begin_build, - /* .reserve = */ generic_index_reserve, + /* .reserve = */ memtx_rtree_index_reserve, /* .build_next = */ generic_index_build_next, /* .end_build = */ generic_index_end_build, }; diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c index 7c28b7d7b3e62b10c49d325b590bb2f44abc2b29..09f0de4cecbb0ad4f6d821618ed2e3771b92a768 100644 --- a/src/box/memtx_space.c +++ b/src/box/memtx_space.c @@ -103,18 +103,6 @@ memtx_space_replace_no_keys(struct space *space, struct tuple *old_tuple, return -1; } -enum { - /** - * This number is calculated based on the - * max (realistic) number of insertions - * a deletion from a B-tree or an R-tree - * can lead to, and, as a result, the max - * number of new block allocations. - */ - RESERVE_EXTENTS_BEFORE_DELETE = 8, - RESERVE_EXTENTS_BEFORE_REPLACE = 16 -}; - /** * A short-cut version of replace() used during bulk load * from snapshot. diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h index d8cdf3f27b08400c585b411086980bd63caa0967..ee6c57a0d01db3fd572e54ac1c460eb7ead17e10 100644 --- a/src/lib/core/errinj.h +++ b/src/lib/core/errinj.h @@ -138,6 +138,7 @@ struct errinj { _(ERRINJ_FIBER_MADVISE, ERRINJ_BOOL, {.bparam = false}) \ _(ERRINJ_FIBER_MPROTECT, ERRINJ_INT, {.iparam = -1}) \ _(ERRINJ_RELAY_FASTER_THAN_TX, ERRINJ_BOOL, {.bparam = false}) \ + _(ERRINJ_INDEX_RESERVE, ERRINJ_BOOL, {.bparam = false})\ ENUM0(errinj_id, ERRINJ_LIST); extern struct errinj errinjs[]; diff --git a/test/box/errinj.result b/test/box/errinj.result index 4ad24d0c1bc9d347b907823f0461edc2547343e3..0d3fedeb312a3bb53c3b1b46f1f66f5c97bae950 100644 --- a/test/box/errinj.result +++ b/test/box/errinj.result @@ -53,6 +53,7 @@ evals - ERRINJ_HTTPC_EXECUTE: false - ERRINJ_HTTP_RESPONSE_ADD_WAIT: false - ERRINJ_INDEX_ALLOC: false + - ERRINJ_INDEX_RESERVE: false - ERRINJ_IPROTO_TX_DELAY: false - ERRINJ_LOG_ROTATE: false - ERRINJ_MEMTX_DELAY_GC: false @@ -1742,3 +1743,101 @@ box.error.injection.get('ERRINJ_RELAY_TIMEOUT') --- - 0 ... +-- +-- gh-4619: make sure that if OOM takes place during rtree recovery, +-- Tarantool instance will fail gracefully. +-- +test_run:cmd('create server rtree with script = "box/lua/cfg_rtree.lua"') +--- +- true +... +test_run:cmd("start server rtree") +--- +- true +... +test_run:cmd('switch rtree') +--- +- true +... +math = require("math") +--- +... +rtreespace = box.schema.create_space('rtree', {if_not_exists = true}) +--- +... +rtreespace:create_index('pk', {if_not_exists = true}) +--- +- unique: true + parts: + - type: unsigned + is_nullable: false + fieldno: 1 + id: 0 + space_id: 512 + type: TREE + name: pk +... +rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,}) +--- +- parts: + - type: array + is_nullable: false + fieldno: 2 + dimension: 3 + id: 1 + type: RTREE + space_id: 512 + name: target +... +count = 10 +--- +... +for i = 1, count do box.space.rtree:insert{i, {(i + 1) -\ + math.floor((i + 1)/7000) * 7000, (i + 2) - math.floor((i + 2)/7000) * 7000,\ + (i + 3) - math.floor((i + 3)/7000) * 7000}} end +--- +... +rtreespace:count() +--- +- 10 +... +box.snapshot() +--- +- ok +... +test_run:cmd('switch default') +--- +- true +... +test_run:cmd("stop server rtree") +--- +- true +... +test_run:cmd("start server rtree with crash_expected=True") +--- +- false +... +fio = require('fio') +--- +... +fh = fio.open(fio.pathjoin(fio.cwd(), 'cfg_rtree.log'), {'O_RDONLY'}) +--- +... +size = fh:seek(0, 'SEEK_END') +--- +... +fh:seek(-256, 'SEEK_END') ~= nil +--- +- true +... +line = fh:read(256) +--- +... +fh:close() +--- +- true +... +string.match(line, 'Failed to allocate') ~= nil +--- +- true +... diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua index 01d2b68df1aaed0bee967090a3159ee36121baa0..5d8f4c63551b2b57b528e0b2b30455135fe5d928 100644 --- a/test/box/errinj.test.lua +++ b/test/box/errinj.test.lua @@ -633,3 +633,32 @@ box.error.injection.set('ERRINJ_RELAY_TIMEOUT', 0.5) box.error.injection.get('ERRINJ_RELAY_TIMEOUT') box.error.injection.set('ERRINJ_RELAY_TIMEOUT', 0) box.error.injection.get('ERRINJ_RELAY_TIMEOUT') + + +-- +-- gh-4619: make sure that if OOM takes place during rtree recovery, +-- Tarantool instance will fail gracefully. +-- +test_run:cmd('create server rtree with script = "box/lua/cfg_rtree.lua"') +test_run:cmd("start server rtree") +test_run:cmd('switch rtree') +math = require("math") +rtreespace = box.schema.create_space('rtree', {if_not_exists = true}) +rtreespace:create_index('pk', {if_not_exists = true}) +rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,}) +count = 10 +for i = 1, count do box.space.rtree:insert{i, {(i + 1) -\ + math.floor((i + 1)/7000) * 7000, (i + 2) - math.floor((i + 2)/7000) * 7000,\ + (i + 3) - math.floor((i + 3)/7000) * 7000}} end +rtreespace:count() +box.snapshot() +test_run:cmd('switch default') +test_run:cmd("stop server rtree") +test_run:cmd("start server rtree with crash_expected=True") +fio = require('fio') +fh = fio.open(fio.pathjoin(fio.cwd(), 'cfg_rtree.log'), {'O_RDONLY'}) +size = fh:seek(0, 'SEEK_END') +fh:seek(-256, 'SEEK_END') ~= nil +line = fh:read(256) +fh:close() +string.match(line, 'Failed to allocate') ~= nil diff --git a/test/box/lua/cfg_rtree.lua b/test/box/lua/cfg_rtree.lua new file mode 100644 index 0000000000000000000000000000000000000000..f2d32ef7d760c5851df8d894746efa3599992895 --- /dev/null +++ b/test/box/lua/cfg_rtree.lua @@ -0,0 +1,8 @@ +#!/usr/bin/env tarantool +os = require('os') +box.error.injection.set("ERRINJ_INDEX_RESERVE", true) +box.cfg{ + listen = os.getenv("LISTEN"), +} +require('console').listen(os.getenv('ADMIN')) +box.schema.user.grant('guest', 'read,write,execute', 'universe')