diff --git a/changelogs/unreleased/gh-10109-vy-read-iterator-fix.md b/changelogs/unreleased/gh-10109-vy-read-iterator-fix.md new file mode 100644 index 0000000000000000000000000000000000000000..c52eea7512d727676173ff13282f197cc58c502c --- /dev/null +++ b/changelogs/unreleased/gh-10109-vy-read-iterator-fix.md @@ -0,0 +1,4 @@ +## bugfix/vinyl + +* Fixed a bug when a tuple was not returned by range `select`. The bug could + also trigger a crash in the read iterator (gh-10109). diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c index 0604d6ab96ed50bf0952dae33ddced19f6480a10..0ebb49229f1f9dcc7efebcd276f01a7d6b02afcc 100644 --- a/src/box/vy_cache.c +++ b/src/box/vy_cache.c @@ -826,10 +826,13 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr, struct vy_entry last, struct vy_cache_node *node = *vy_cache_tree_iterator_get_elem(tree, &pos); int cmp = dir * vy_entry_compare(node->entry, key, def); + bool is_visible = vy_cache_iterator_stmt_is_visible( + itr, node->entry.stmt); + if (!is_visible) + *stop = false; if (cmp < 0 || (cmp == 0 && !key_belongs)) break; - if (vy_cache_iterator_stmt_is_visible( - itr, node->entry.stmt)) { + if (is_visible) { itr->curr_pos = pos; if (itr->curr.stmt != NULL) tuple_unref(itr->curr.stmt); diff --git a/test/vinyl-luatest/gh_10109_read_iterator_test.lua b/test/vinyl-luatest/gh_10109_read_iterator_test.lua index 6e0296e5c037cd8089ae59ad07bdafe9ae9c034a..3a99e69361e48cfbd630776241bcacdb650773d9 100644 --- a/test/vinyl-luatest/gh_10109_read_iterator_test.lua +++ b/test/vinyl-luatest/gh_10109_read_iterator_test.lua @@ -37,3 +37,44 @@ g.test_read_upsert = function(cg) {{400}, {300}, {200}, {100}}) end) end + +g.test_read_cache = function(cg) + cg.server:exec(function() + local fiber = require('fiber') + local timeout = 30 + box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', true) + local s = box.schema.create_space('test', {engine = 'vinyl'}) + s:create_index('pk') + s:replace({500}) + box.snapshot() + s:upsert({200}, {}) + box.snapshot() + s:replace({100}) + s:replace({300}) + s:replace({400}) + box.snapshot() + local c = fiber.channel() + fiber.new(function() + local _ + local result = {} + local gen, param, state = s:pairs({400}, {iterator = 'lt'}) + _, result[#result + 1] = gen(param, state) -- {300} + c:put(true) + c:get() + _, result[#result + 1] = gen(param, state) -- {200} + _, result[#result + 1] = gen(param, state) -- {100} + _, result[#result + 1] = gen(param, state) -- eof + c:put(result) + end) + t.assert(c:get(timeout)) + s:replace({350, 'x'}) -- send the iterator to a read view + s:replace({150, 'x'}) -- must be invisible to the iterator + -- Add the interval connecting the tuple {100} visible from the read + -- view with the tuple {150, 'x'} invisible from the read view to + -- the cache. + t.assert_equals(s:select({100}, {iterator = 'ge', limit = 2}), + {{100}, {150, 'x'}}) + c:put(true, timeout) + t.assert_equals(c:get(timeout), {{300}, {200}, {100}}) + end) +end