diff --git a/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md b/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md new file mode 100644 index 0000000000000000000000000000000000000000..5eb560ed1e683f7fe9c72f6b29132b28b50a978f --- /dev/null +++ b/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md @@ -0,0 +1,4 @@ +## bugfix/vinyl + +* Fixed a bug when a compaction task could crash on a disk read error + (gh-10555). diff --git a/src/box/vy_run.c b/src/box/vy_run.c index 159e72b6a20bf775a06422a324943ce2352c07a1..f0ac7ff97c31adfd7f0f77c781219e47a1dc32cd 100644 --- a/src/box/vy_run.c +++ b/src/box/vy_run.c @@ -2668,11 +2668,12 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct vy_entry *ret) { assert(virt_stream->iface->next == vy_slice_stream_next); struct vy_slice_stream *stream = (struct vy_slice_stream *)virt_stream; - *ret = vy_entry_none(); /* If the slice is ended, return EOF */ - if (stream->page_no > stream->slice->last_page_no) + if (stream->page_no > stream->slice->last_page_no) { + *ret = vy_entry_none(); return 0; + } /* If current page is not already read, read it */ if (stream->page == NULL && vy_slice_stream_read_page(stream) != 0) @@ -2689,6 +2690,7 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct vy_entry *ret) stream->page_no >= stream->slice->last_page_no && vy_entry_compare(entry, stream->slice->end, stream->cmp_def) >= 0) { tuple_unref(entry.stmt); + *ret = vy_entry_none(); return 0; } diff --git a/src/box/vy_stmt.c b/src/box/vy_stmt.c index f4adf263bc3e60dc3e7f9ee918b2f0f2bd56a333..bf0331ec00e892261c5c3458c84fe2291c71ec5c 100644 --- a/src/box/vy_stmt.c +++ b/src/box/vy_stmt.c @@ -729,6 +729,11 @@ vy_stmt_encode_secondary(struct tuple *value, struct key_def *cmp_def, struct tuple * vy_stmt_decode(struct xrow_header *xrow, struct tuple_format *format) { + ERROR_INJECT_COUNTDOWN(ERRINJ_VY_STMT_DECODE_COUNTDOWN, { + diag_set(ClientError, ER_INJECTION, + "vinyl statement decode"); + return NULL; + }); struct vy_stmt_env *env = format->engine; struct request request; uint64_t key_map = dml_request_key_map(xrow->type); diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h index 903de158a050f3e40a0113651baa282c141aa26f..4c51d1c383bf8343ff3fa19d21a17e0223cf03fa 100644 --- a/src/lib/core/errinj.h +++ b/src/lib/core/errinj.h @@ -172,6 +172,7 @@ struct errinj { _(ERRINJ_VY_SCHED_TIMEOUT, ERRINJ_DOUBLE, {.dparam = 0}) \ _(ERRINJ_VY_SQUASH_TIMEOUT, ERRINJ_DOUBLE, {.dparam = 0}) \ _(ERRINJ_VY_STMT_ALLOC_COUNTDOWN, ERRINJ_INT, {.iparam = -1})\ + _(ERRINJ_VY_STMT_DECODE_COUNTDOWN, ERRINJ_INT, {.iparam = -1})\ _(ERRINJ_VY_TASK_COMPLETE, ERRINJ_BOOL, {.bparam = false}) \ _(ERRINJ_VY_WRITE_ITERATOR_START_FAIL, ERRINJ_BOOL, {.bparam = false})\ _(ERRINJ_WAIT_QUORUM_COUNT, ERRINJ_INT, {.iparam = 0}) \ diff --git a/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua b/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..94ba163e53b425b8f0eb16b928f44f3c028bc9ed --- /dev/null +++ b/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua @@ -0,0 +1,55 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function(cg) + t.tarantool.skip_if_not_debug() + cg.server = server:new() + cg.server:start() +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +g.after_each(function(cg) + cg.server:exec(function() + if box.space.test ~= nil then + box.space.test:drop() + end + box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', false) + box.error.injection.set('ERRINJ_VY_STMT_DECODE_COUNTDOWN', -1) + end) +end) + +g.test_compaction_read_error = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test', {engine = 'vinyl'}) + s:create_index('pk') + + -- Delay compaction and create a few runs. + box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', true) + for _ = 1, 5 do + box.begin() + for k = 1, 100 do + s:replace({k}) + end + box.commit() + box.snapshot() + end + + -- Make compaction fail while decoding a random statement. + box.error.injection.set('ERRINJ_VY_STMT_DECODE_COUNTDOWN', + math.random(500)) + + -- Resume compaction and wait for it to fail. + box.stat.reset() + t.assert_equals(box.stat.vinyl().scheduler.tasks_failed, 0) + s.index.pk:compact() + box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', false) + t.helpers.retrying({}, function() + t.assert_ge(box.stat.vinyl().scheduler.tasks_failed, 1) + end) + end) +end