From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladimir Davydov Subject: [PATCH 02/12] test: split vinyl/errinj Date: Tue, 15 Jan 2019 17:17:11 +0300 Message-Id: <0a7dbedb00c22c46fd2feb8f5f18dd88cf457564.1547558871.git.vdavydov.dev@gmail.com> In-Reply-To: References: In-Reply-To: References: To: tarantool-patches@freelists.org List-ID: This test is huge and takes long to complete. Let's move ddl, tx, and stat related stuff to separate files. --- test/vinyl/errinj.result | 1158 -------------------------------------- test/vinyl/errinj.test.lua | 494 ---------------- test/vinyl/errinj_ddl.result | 595 ++++++++++++++++++++ test/vinyl/errinj_ddl.test.lua | 260 +++++++++ test/vinyl/errinj_stat.result | 152 +++++ test/vinyl/errinj_stat.test.lua | 48 ++ test/vinyl/errinj_tx.result | 435 ++++++++++++++ test/vinyl/errinj_tx.test.lua | 194 +++++++ test/vinyl/errinj_vylog.result | 35 ++ test/vinyl/errinj_vylog.test.lua | 18 + test/vinyl/suite.ini | 2 +- 11 files changed, 1738 insertions(+), 1653 deletions(-) create mode 100644 test/vinyl/errinj_ddl.result create mode 100644 test/vinyl/errinj_ddl.test.lua create mode 100644 test/vinyl/errinj_stat.result create mode 100644 test/vinyl/errinj_stat.test.lua create mode 100644 test/vinyl/errinj_tx.result create mode 100644 test/vinyl/errinj_tx.test.lua diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result index 23ab845b..a16475f5 100644 --- a/test/vinyl/errinj.result +++ b/test/vinyl/errinj.result @@ -1,6 +1,3 @@ --- --- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE --- test_run = require('test_run').new() --- ... @@ -17,42 +14,6 @@ errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.040) --- - ok ... -s = box.schema.space.create('test', {engine='vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end ---- -... -errinj.set("ERRINJ_WAL_WRITE", true) ---- -- ok -... -f() ---- -- error: Failed to write to disk -... -s:select{} ---- -- [] -... -errinj.set("ERRINJ_WAL_WRITE", false) ---- -- ok -... -f() ---- -... -s:select{} ---- -- - [1, 'hi'] - - [2, 'bye'] -... -s:drop() ---- -... -- -- Lost data in case of dump error -- @@ -474,399 +435,6 @@ errinj.set("ERRINJ_VY_SQUASH_TIMEOUT", 0) --- - ok ... ---https://github.com/tarantool/tarantool/issues/1842 ---test error injection -s = box.schema.space.create('test', {engine='vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -s:replace{0, 0} ---- -- [0, 0] -... -s:replace{1, 0} ---- -- [1, 0] -... -s:replace{2, 0} ---- -- [2, 0] -... -errinj.set("ERRINJ_WAL_WRITE", true) ---- -- ok -... -s:replace{3, 0} ---- -- error: Failed to write to disk -... -s:replace{4, 0} ---- -- error: Failed to write to disk -... -s:replace{5, 0} ---- -- error: Failed to write to disk -... -s:replace{6, 0} ---- -- error: Failed to write to disk -... -errinj.set("ERRINJ_WAL_WRITE", false) ---- -- ok -... -s:replace{7, 0} ---- -- [7, 0] -... -s:replace{8, 0} ---- -- [8, 0] -... -s:select{} ---- -- - [0, 0] - - [1, 0] - - [2, 0] - - [7, 0] - - [8, 0] -... -s:drop() ---- -... -create_iterator = require('utils').create_iterator ---- -... ---iterator test -test_run:cmd("setopt delimiter ';'") ---- -- true -... -fiber_status = 0 - -function fiber_func() - box.begin() - s:replace{5, 5} - fiber_status = 1 - local res = {pcall(box.commit) } - fiber_status = 2 - return unpack(res) -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -s = box.schema.space.create('test', {engine='vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -fiber = require('fiber') ---- -... -_ = s:replace{0, 0} ---- -... -_ = s:replace{10, 0} ---- -... -_ = s:replace{20, 0} ---- -... -test_run:cmd("setopt delimiter ';'"); ---- -- true -... -faced_trash = false -for i = 1,100 do - errinj.set("ERRINJ_WAL_WRITE", true) - local f = fiber.create(fiber_func) - local itr = create_iterator(s, {0}, {iterator='GE'}) - local first = itr.next() - local second = itr.next() - if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end - while fiber_status <= 1 do fiber.sleep(0.001) end - local _,next = pcall(itr.next) - _,next = pcall(itr.next) - _,next = pcall(itr.next) - errinj.set("ERRINJ_WAL_WRITE", false) - s:delete{5} -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -faced_trash ---- -- false -... -s:drop() ---- -... --- TX in prepared but not committed state -s = box.schema.space.create('test', {engine='vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -fiber = require('fiber') ---- -... -txn_proxy = require('txn_proxy') ---- -... -s:replace{1, "original"} ---- -- [1, 'original'] -... -s:replace{2, "original"} ---- -- [2, 'original'] -... -s:replace{3, "original"} ---- -- [3, 'original'] -... -c0 = txn_proxy.new() ---- -... -c0:begin() ---- -- -... -c1 = txn_proxy.new() ---- -... -c1:begin() ---- -- -... -c2 = txn_proxy.new() ---- -... -c2:begin() ---- -- -... -c3 = txn_proxy.new() ---- -... -c3:begin() ---- -- -... --- --- Prepared transactions --- --- Pause WAL writer to cause all further calls to box.commit() to move --- transactions into prepared, but not committed yet state. -errinj.set("ERRINJ_WAL_DELAY", true) ---- -- ok -... -lsn = box.info.lsn ---- -... -c0('s:replace{1, "c0"}') ---- -- - [1, 'c0'] -... -c0('s:replace{2, "c0"}') ---- -- - [2, 'c0'] -... -c0('s:replace{3, "c0"}') ---- -- - [3, 'c0'] -... -_ = fiber.create(c0.commit, c0) ---- -... -box.info.lsn == lsn ---- -- true -... -c1('s:replace{1, "c1"}') ---- -- - [1, 'c1'] -... -c1('s:replace{2, "c1"}') ---- -- - [2, 'c1'] -... -_ = fiber.create(c1.commit, c1) ---- -... -box.info.lsn == lsn ---- -- true -... -c3('s:select{1}') -- c1 is visible ---- -- - [[1, 'c1']] -... -c2('s:replace{1, "c2"}') ---- -- - [1, 'c2'] -... -c2('s:replace{3, "c2"}') ---- -- - [3, 'c2'] -... -_ = fiber.create(c2.commit, c2) ---- -... -box.info.lsn == lsn ---- -- true -... -c3('s:select{1}') -- c1 is visible, c2 is not ---- -- - [[1, 'c1']] -... -c3('s:select{2}') -- c1 is visible ---- -- - [[2, 'c1']] -... -c3('s:select{3}') -- c2 is not visible ---- -- - [[3, 'c0']] -... --- Resume WAL writer and wait until all transactions will been committed -errinj.set("ERRINJ_WAL_DELAY", false) ---- -- ok -... -REQ_COUNT = 7 ---- -... -while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end ---- -... -box.info.lsn == lsn + REQ_COUNT ---- -- true -... -c3('s:select{1}') -- c1 is visible, c2 is not ---- -- - [[1, 'c1']] -... -c3('s:select{2}') -- c1 is visible ---- -- - [[2, 'c1']] -... -c3('s:select{3}') -- c2 is not visible ---- -- - [[3, 'c0']] -... -c3:commit() ---- -- -... -s:drop() ---- -... --- --- Test mem restoration on a prepared and not commited statement --- after moving iterator into read view. --- -space = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -pk = space:create_index('pk') ---- -... -space:replace{1} ---- -- [1] -... -space:replace{2} ---- -- [2] -... -space:replace{3} ---- -- [3] -... -last_read = nil ---- -... -errinj.set("ERRINJ_WAL_DELAY", true) ---- -- ok -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... --- block until wal_delay = false --- send iterator to read view --- flush mem and update index version to trigger iterator restore -function fill_space() - box.begin() - space:replace{1} - space:replace{2} - space:replace{3} - box.commit() - space:replace{1, 1} - box.snapshot() -end; ---- -... -function iterate_in_read_view() - local i = create_iterator(space) - last_read = i.next() - fiber.sleep(100000) - last_read = i.next() -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -f1 = fiber.create(fill_space) ---- -... --- Prepared transaction is blocked due to wal_delay. --- Start iterator with vlsn = INT64_MAX -f2 = fiber.create(iterate_in_read_view) ---- -... -last_read ---- -- [1] -... --- Finish prepared transaction and send to read view the iterator. -errinj.set("ERRINJ_WAL_DELAY", false) ---- -- ok -... -while f1:status() ~= 'dead' do fiber.sleep(0.01) end ---- -... -f2:wakeup() ---- -... -while f2:status() ~= 'dead' do fiber.sleep(0.01) end ---- -... -last_read ---- -- [2] -... -space:drop() ---- -... -- -- Space drop in the middle of dump. -- @@ -976,38 +544,6 @@ test_run:cmd("cleanup server test") --- - true ... --- --- If we logged an index creation in the metadata log before WAL write, --- WAL failure would result in leaving the index record in vylog forever. --- Since we use LSN to identify indexes in vylog, retrying index creation --- would then lead to a duplicate index id in vylog and hence inability --- to make a snapshot or recover. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -errinj.set('ERRINJ_WAL_IO', true) ---- -- ok -... -_ = s:create_index('pk') ---- -- error: Failed to write to disk -... -errinj.set('ERRINJ_WAL_IO', false) ---- -- ok -... -_ = s:create_index('pk') ---- -... -box.snapshot() ---- -- ok -... -s:drop() ---- -... s = box.schema.space.create('test', {engine = 'vinyl'}) --- ... @@ -1392,81 +928,6 @@ test_run:cmd("cleanup server low_quota") - true ... -- --- Check that ALTER is abroted if a tuple inserted during space --- format change does not conform to the new format. --- -format = {} ---- -... -format[1] = {name = 'field1', type = 'unsigned'} ---- -... -format[2] = {name = 'field2', type = 'string', is_nullable = true} ---- -... -s = box.schema.space.create('test', {engine = 'vinyl', format = format}) ---- -... -_ = s:create_index('pk', {page_size = 16}) ---- -... -pad = string.rep('x', 16) ---- -... -for i = 101, 200 do s:replace{i, pad} end ---- -... -box.snapshot() ---- -- ok -... -ch = fiber.channel(1) ---- -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i, box.NULL} - end - ch:put(true) -end); ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) ---- -- ok -... -format[2].is_nullable = false ---- -... -s:format(format) -- must fail ---- -- error: 'Tuple field 2 type does not match one required by operation: expected string' -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) ---- -- ok -... -ch:get() ---- -- true -... -s:count() -- 200 ---- -- 200 -... -s:drop() ---- -... --- -- gh-3437: if compaction races with checkpointing, it may remove -- files needed for backup. -- @@ -1533,317 +994,6 @@ s:drop() --- ... -- --- gh-2449: change 'unique' index property from true to false --- is done without index rebuild. --- -s = box.schema.space.create('test', { engine = 'vinyl' }) ---- -... -_ = s:create_index('primary') ---- -... -_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}}) ---- -... -s:insert{1, 10} ---- -- [1, 10] -... -box.snapshot() ---- -- ok -... -errinj.set("ERRINJ_VY_READ_PAGE", true); ---- -- ok -... -s.index.secondary:alter{unique = false} -- ok ---- -... -s.index.secondary.unique ---- -- false -... -s.index.secondary:alter{unique = true} -- error ---- -- error: Error injection 'vinyl page read' -... -s.index.secondary.unique ---- -- false -... -errinj.set("ERRINJ_VY_READ_PAGE", false); ---- -- ok -... -s:insert{2, 10} ---- -- [2, 10] -... -s.index.secondary:select(10) ---- -- - [1, 10] - - [2, 10] -... -s:drop() ---- -... --- --- Check that ALTER is aborted if a tuple inserted during index build --- doesn't conform to the new format. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk', {page_size = 16}) ---- -... -pad = string.rep('x', 16) ---- -... -for i = 101, 200 do s:replace{i, i, pad} end ---- -... -box.snapshot() ---- -- ok -... -ch = fiber.channel(1) ---- -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i} - end - ch:put(true) -end); ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) ---- -- ok -... -s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail ---- -- error: Tuple field 2 required by space format is missing -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) ---- -- ok -... -ch:get() ---- -- true -... -s:count() -- 200 ---- -- 200 -... -s:drop() ---- -... --- --- Check that ALTER is aborted if a tuple inserted during index build --- violates unique constraint. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk', {page_size = 16}) ---- -... -pad = string.rep('x', 16) ---- -... -for i = 101, 200 do s:replace{i, i, pad} end ---- -... -box.snapshot() ---- -- ok -... -ch = fiber.channel(1) ---- -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i, i + 1} - end - ch:put(true) -end); ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) ---- -- ok -... -s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail ---- -- error: Duplicate key exists in unique index 'sk' in space 'test' -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) ---- -- ok -... -ch:get() ---- -- true -... -s:count() -- 200 ---- -- 200 -... -s:drop() ---- -... --- --- Check that modifications done to the space during the final dump --- of a newly built index are recovered properly. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -for i = 1, 5 do s:replace{i, i} end ---- -... -errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true) ---- -- ok -... -ch = fiber.channel(1) ---- -... -_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end) ---- -... -fiber.sleep(0.01) ---- -... -_ = s:delete{1} ---- -... -_ = s:replace{2, -2} ---- -... -_ = s:delete{2} ---- -... -_ = s:replace{3, -3} ---- -... -_ = s:replace{3, -2} ---- -... -_ = s:replace{3, -1} ---- -... -_ = s:delete{3} ---- -... -_ = s:upsert({3, 3}, {{'=', 2, 1}}) ---- -... -_ = s:upsert({3, 3}, {{'=', 2, 2}}) ---- -... -_ = s:delete{3} ---- -... -_ = s:replace{4, -1} ---- -... -_ = s:replace{4, -2} ---- -... -_ = s:replace{4, -4} ---- -... -_ = s:upsert({5, 1}, {{'=', 2, 1}}) ---- -... -_ = s:upsert({5, 2}, {{'=', 2, -5}}) ---- -... -_ = s:replace{6, -6} ---- -... -_ = s:upsert({7, -7}, {{'=', 2, -7}}) ---- -... -errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false) ---- -- ok -... -ch:get() ---- -- true -... -s.index.sk:select() ---- -- - [7, -7] - - [6, -6] - - [5, -5] - - [4, -4] -... -s.index.sk:stat().memory.rows ---- -- 27 -... -test_run:cmd('restart server default') -s = box.space.test ---- -... -s.index.sk:select() ---- -- - [7, -7] - - [6, -6] - - [5, -5] - - [4, -4] -... -s.index.sk:stat().memory.rows ---- -- 27 -... -box.snapshot() ---- -- ok -... -s.index.sk:select() ---- -- - [7, -7] - - [6, -6] - - [5, -5] - - [4, -4] -... -s.index.sk:stat().memory.rows ---- -- 0 -... -s:drop() ---- -... --- -- Check that tarantool doesn't hang or crash if error -- occurs while writing a deferred DELETE to WAL. -- @@ -1926,314 +1076,6 @@ s:drop() --- ... -- --- gh-3458: check that rw transactions that started before DDL are --- aborted. --- -vinyl_cache = box.cfg.vinyl_cache ---- -... -box.cfg{vinyl_cache = 0} ---- -... -s1 = box.schema.space.create('test1', {engine = 'vinyl'}) ---- -... -_ = s1:create_index('pk', {page_size = 16}) ---- -... -s2 = box.schema.space.create('test2', {engine = 'vinyl'}) ---- -... -_ = s2:create_index('pk') ---- -... -pad = string.rep('x', 16) ---- -... -for i = 101, 200 do s1:replace{i, i, pad} end ---- -... -box.snapshot() ---- -- ok -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -function async_replace(space, tuple, timeout) - local c = fiber.channel(1) - fiber.create(function() - box.begin() - space:replace(tuple) - fiber.sleep(timeout) - local status = pcall(box.commit) - c:put(status) - end) - return c -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -c1 = async_replace(s1, {1}, 0.01) ---- -... -c2 = async_replace(s2, {1}, 0.01) ---- -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) ---- -- ok -... -s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}} ---- -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) ---- -- ok -... -c1:get() -- false (transaction was aborted) ---- -- false -... -c2:get() -- true ---- -- true -... -s1:get(1) == nil ---- -- true -... -s2:get(1) ~= nil ---- -- true -... -s1:format() ---- -- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'unsigned'}] -... -s1:format{} ---- -... -c1 = async_replace(s1, {2}, 0.01) ---- -... -c2 = async_replace(s2, {2}, 0.01) ---- -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) ---- -- ok -... -_ = s1:create_index('sk', {parts = {2, 'unsigned'}}) ---- -... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) ---- -- ok -... -c1:get() -- false (transaction was aborted) ---- -- false -... -c2:get() -- true ---- -- true -... -s1:get(2) == nil ---- -- true -... -s2:get(2) ~= nil ---- -- true -... -s1.index.pk:count() == s1.index.sk:count() ---- -- true -... -s1:drop() ---- -... -s2:drop() ---- -... -box.cfg{vinyl_cache = vinyl_cache} ---- -... --- Transactions that reached WAL must not be aborted. -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -errinj.set('ERRINJ_WAL_DELAY', true) ---- -- ok -... -_ = fiber.create(function() s:replace{1} end) ---- -... -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) ---- -... -fiber.sleep(0) ---- -... -s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail ---- -- error: Tuple field 2 required by space format is missing -... -s:select() ---- -- - [1] -... -s:truncate() ---- -... -errinj.set('ERRINJ_WAL_DELAY', true) ---- -- ok -... -_ = fiber.create(function() s:replace{1} end) ---- -... -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) ---- -... -fiber.sleep(0) ---- -... -s:create_index('sk', {parts = {2, 'unsigned'}}) ---- -- error: Tuple field 2 required by space format is missing -... -s:select() ---- -- - [1] -... -s:drop() ---- -... --- --- Check disk.compact.queue stat. --- -test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: '") ---- -- true -... -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -i = s:create_index('pk', {run_count_per_level = 2}) ---- -... -function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end ---- -... -dump() ---- -... -i:stat().disk.compact.queue -- none ---- -- bytes_compressed: - pages: 0 - rows: 0 - bytes: 0 -... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue ---- -- true -... -errinj.set('ERRINJ_VY_COMPACTION_DELAY', true) ---- -- ok -... -dump() ---- -... -dump() ---- -... -i:stat().disk.compact.queue -- 30 statements ---- -- bytes_compressed: - pages: 3 - rows: 30 - bytes: 471 -... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue ---- -- true -... -dump() ---- -... -i:stat().disk.compact.queue -- 40 statements ---- -- bytes_compressed: - pages: 4 - rows: 40 - bytes: 628 -... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue ---- -- true -... -dump() ---- -... -i:stat().disk.compact.queue -- 50 statements ---- -- bytes_compressed: - pages: 5 - rows: 50 - bytes: 785 -... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue ---- -- true -... -box.stat.reset() -- doesn't affect queue size ---- -... -i:stat().disk.compact.queue -- 50 statements ---- -- bytes_compressed: - pages: 5 - rows: 50 - bytes: 785 -... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue ---- -- true -... -errinj.set('ERRINJ_VY_COMPACTION_DELAY', false) ---- -- ok -... -while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end ---- -... -i:stat().disk.compact.queue -- none ---- -- bytes_compressed: - pages: 0 - rows: 0 - bytes: 0 -... -s:drop() ---- -... -test_run:cmd("clear filter") ---- -- true -... --- -- Check that an instance doesn't crash if a run file needed for -- joining a replica is corrupted (see gh-3708). -- diff --git a/test/vinyl/errinj.test.lua b/test/vinyl/errinj.test.lua index 59e9f81c..d4590fb4 100644 --- a/test/vinyl/errinj.test.lua +++ b/test/vinyl/errinj.test.lua @@ -1,21 +1,8 @@ --- --- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE --- test_run = require('test_run').new() fio = require('fio') fiber = require('fiber') errinj = box.error.injection errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.040) -s = box.schema.space.create('test', {engine='vinyl'}) -_ = s:create_index('pk') -function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end -errinj.set("ERRINJ_WAL_WRITE", true) -f() -s:select{} -errinj.set("ERRINJ_WAL_WRITE", false) -f() -s:select{} -s:drop() -- -- Lost data in case of dump error -- @@ -165,186 +152,6 @@ s:drop() -- index is gone fiber.sleep(0.05) errinj.set("ERRINJ_VY_SQUASH_TIMEOUT", 0) ---https://github.com/tarantool/tarantool/issues/1842 ---test error injection -s = box.schema.space.create('test', {engine='vinyl'}) -_ = s:create_index('pk') -s:replace{0, 0} - -s:replace{1, 0} -s:replace{2, 0} -errinj.set("ERRINJ_WAL_WRITE", true) -s:replace{3, 0} -s:replace{4, 0} -s:replace{5, 0} -s:replace{6, 0} -errinj.set("ERRINJ_WAL_WRITE", false) -s:replace{7, 0} -s:replace{8, 0} -s:select{} - -s:drop() - -create_iterator = require('utils').create_iterator - ---iterator test -test_run:cmd("setopt delimiter ';'") - -fiber_status = 0 - -function fiber_func() - box.begin() - s:replace{5, 5} - fiber_status = 1 - local res = {pcall(box.commit) } - fiber_status = 2 - return unpack(res) -end; - -test_run:cmd("setopt delimiter ''"); - -s = box.schema.space.create('test', {engine='vinyl'}) -_ = s:create_index('pk') -fiber = require('fiber') - -_ = s:replace{0, 0} -_ = s:replace{10, 0} -_ = s:replace{20, 0} - -test_run:cmd("setopt delimiter ';'"); - -faced_trash = false -for i = 1,100 do - errinj.set("ERRINJ_WAL_WRITE", true) - local f = fiber.create(fiber_func) - local itr = create_iterator(s, {0}, {iterator='GE'}) - local first = itr.next() - local second = itr.next() - if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end - while fiber_status <= 1 do fiber.sleep(0.001) end - local _,next = pcall(itr.next) - _,next = pcall(itr.next) - _,next = pcall(itr.next) - errinj.set("ERRINJ_WAL_WRITE", false) - s:delete{5} -end; - -test_run:cmd("setopt delimiter ''"); - -faced_trash - -s:drop() - --- TX in prepared but not committed state -s = box.schema.space.create('test', {engine='vinyl'}) -_ = s:create_index('pk') -fiber = require('fiber') -txn_proxy = require('txn_proxy') - -s:replace{1, "original"} -s:replace{2, "original"} -s:replace{3, "original"} - -c0 = txn_proxy.new() -c0:begin() -c1 = txn_proxy.new() -c1:begin() -c2 = txn_proxy.new() -c2:begin() -c3 = txn_proxy.new() -c3:begin() - --- --- Prepared transactions --- - --- Pause WAL writer to cause all further calls to box.commit() to move --- transactions into prepared, but not committed yet state. -errinj.set("ERRINJ_WAL_DELAY", true) -lsn = box.info.lsn -c0('s:replace{1, "c0"}') -c0('s:replace{2, "c0"}') -c0('s:replace{3, "c0"}') -_ = fiber.create(c0.commit, c0) -box.info.lsn == lsn -c1('s:replace{1, "c1"}') -c1('s:replace{2, "c1"}') -_ = fiber.create(c1.commit, c1) -box.info.lsn == lsn -c3('s:select{1}') -- c1 is visible -c2('s:replace{1, "c2"}') -c2('s:replace{3, "c2"}') -_ = fiber.create(c2.commit, c2) -box.info.lsn == lsn -c3('s:select{1}') -- c1 is visible, c2 is not -c3('s:select{2}') -- c1 is visible -c3('s:select{3}') -- c2 is not visible - --- Resume WAL writer and wait until all transactions will been committed -errinj.set("ERRINJ_WAL_DELAY", false) -REQ_COUNT = 7 -while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end -box.info.lsn == lsn + REQ_COUNT - -c3('s:select{1}') -- c1 is visible, c2 is not -c3('s:select{2}') -- c1 is visible -c3('s:select{3}') -- c2 is not visible -c3:commit() - -s:drop() - --- --- Test mem restoration on a prepared and not commited statement --- after moving iterator into read view. --- -space = box.schema.space.create('test', {engine = 'vinyl'}) -pk = space:create_index('pk') -space:replace{1} -space:replace{2} -space:replace{3} - -last_read = nil - -errinj.set("ERRINJ_WAL_DELAY", true) - -test_run:cmd("setopt delimiter ';'") - -function fill_space() - box.begin() - space:replace{1} - space:replace{2} - space:replace{3} --- block until wal_delay = false - box.commit() --- send iterator to read view - space:replace{1, 1} --- flush mem and update index version to trigger iterator restore - box.snapshot() -end; - -function iterate_in_read_view() - local i = create_iterator(space) - last_read = i.next() - fiber.sleep(100000) - last_read = i.next() -end; - -test_run:cmd("setopt delimiter ''"); - -f1 = fiber.create(fill_space) --- Prepared transaction is blocked due to wal_delay. --- Start iterator with vlsn = INT64_MAX -f2 = fiber.create(iterate_in_read_view) -last_read --- Finish prepared transaction and send to read view the iterator. -errinj.set("ERRINJ_WAL_DELAY", false) -while f1:status() ~= 'dead' do fiber.sleep(0.01) end -f2:wakeup() -while f2:status() ~= 'dead' do fiber.sleep(0.01) end -last_read - -space:drop() - -- -- Space drop in the middle of dump. -- @@ -389,21 +196,6 @@ t2 = fiber.time() t2 - t1 < 1 test_run:cmd("cleanup server test") --- --- If we logged an index creation in the metadata log before WAL write, --- WAL failure would result in leaving the index record in vylog forever. --- Since we use LSN to identify indexes in vylog, retrying index creation --- would then lead to a duplicate index id in vylog and hence inability --- to make a snapshot or recover. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) -errinj.set('ERRINJ_WAL_IO', true) -_ = s:create_index('pk') -errinj.set('ERRINJ_WAL_IO', false) -_ = s:create_index('pk') -box.snapshot() -s:drop() - s = box.schema.space.create('test', {engine = 'vinyl'}) _ = s:create_index('i1', {parts = {1, 'unsigned'}}) @@ -541,41 +333,6 @@ test_run:cmd("stop server low_quota") test_run:cmd("cleanup server low_quota") -- --- Check that ALTER is abroted if a tuple inserted during space --- format change does not conform to the new format. --- -format = {} -format[1] = {name = 'field1', type = 'unsigned'} -format[2] = {name = 'field2', type = 'string', is_nullable = true} -s = box.schema.space.create('test', {engine = 'vinyl', format = format}) -_ = s:create_index('pk', {page_size = 16}) - -pad = string.rep('x', 16) -for i = 101, 200 do s:replace{i, pad} end -box.snapshot() - -ch = fiber.channel(1) -test_run:cmd("setopt delimiter ';'") -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i, box.NULL} - end - ch:put(true) -end); -test_run:cmd("setopt delimiter ''"); - -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) -format[2].is_nullable = false -s:format(format) -- must fail -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) - -ch:get() - -s:count() -- 200 -s:drop() - --- -- gh-3437: if compaction races with checkpointing, it may remove -- files needed for backup. -- @@ -604,140 +361,6 @@ box.backup.stop() s:drop() -- --- gh-2449: change 'unique' index property from true to false --- is done without index rebuild. --- -s = box.schema.space.create('test', { engine = 'vinyl' }) -_ = s:create_index('primary') -_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}}) -s:insert{1, 10} -box.snapshot() -errinj.set("ERRINJ_VY_READ_PAGE", true); -s.index.secondary:alter{unique = false} -- ok -s.index.secondary.unique -s.index.secondary:alter{unique = true} -- error -s.index.secondary.unique -errinj.set("ERRINJ_VY_READ_PAGE", false); -s:insert{2, 10} -s.index.secondary:select(10) -s:drop() - --- --- Check that ALTER is aborted if a tuple inserted during index build --- doesn't conform to the new format. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) -_ = s:create_index('pk', {page_size = 16}) - -pad = string.rep('x', 16) -for i = 101, 200 do s:replace{i, i, pad} end -box.snapshot() - -ch = fiber.channel(1) -test_run:cmd("setopt delimiter ';'") -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i} - end - ch:put(true) -end); -test_run:cmd("setopt delimiter ''"); - -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) -s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) - -ch:get() - -s:count() -- 200 -s:drop() - --- --- Check that ALTER is aborted if a tuple inserted during index build --- violates unique constraint. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) -_ = s:create_index('pk', {page_size = 16}) - -pad = string.rep('x', 16) -for i = 101, 200 do s:replace{i, i, pad} end -box.snapshot() - -ch = fiber.channel(1) -test_run:cmd("setopt delimiter ';'") -_ = fiber.create(function() - fiber.sleep(0.01) - for i = 1, 100 do - s:replace{i, i + 1} - end - ch:put(true) -end); -test_run:cmd("setopt delimiter ''"); - -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) -s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) - -ch:get() - -s:count() -- 200 -s:drop() - --- --- Check that modifications done to the space during the final dump --- of a newly built index are recovered properly. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) -_ = s:create_index('pk') - -for i = 1, 5 do s:replace{i, i} end - -errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true) -ch = fiber.channel(1) -_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end) - -fiber.sleep(0.01) - -_ = s:delete{1} -_ = s:replace{2, -2} -_ = s:delete{2} -_ = s:replace{3, -3} -_ = s:replace{3, -2} -_ = s:replace{3, -1} -_ = s:delete{3} -_ = s:upsert({3, 3}, {{'=', 2, 1}}) -_ = s:upsert({3, 3}, {{'=', 2, 2}}) -_ = s:delete{3} -_ = s:replace{4, -1} -_ = s:replace{4, -2} -_ = s:replace{4, -4} -_ = s:upsert({5, 1}, {{'=', 2, 1}}) -_ = s:upsert({5, 2}, {{'=', 2, -5}}) -_ = s:replace{6, -6} -_ = s:upsert({7, -7}, {{'=', 2, -7}}) - -errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false) -ch:get() - -s.index.sk:select() -s.index.sk:stat().memory.rows - -test_run:cmd('restart server default') - -s = box.space.test - -s.index.sk:select() -s.index.sk:stat().memory.rows - -box.snapshot() - -s.index.sk:select() -s.index.sk:stat().memory.rows - -s:drop() - --- -- Check that tarantool doesn't hang or crash if error -- occurs while writing a deferred DELETE to WAL. -- @@ -768,123 +391,6 @@ box.snapshot() -- ok s:drop() -- --- gh-3458: check that rw transactions that started before DDL are --- aborted. --- -vinyl_cache = box.cfg.vinyl_cache -box.cfg{vinyl_cache = 0} - -s1 = box.schema.space.create('test1', {engine = 'vinyl'}) -_ = s1:create_index('pk', {page_size = 16}) -s2 = box.schema.space.create('test2', {engine = 'vinyl'}) -_ = s2:create_index('pk') - -pad = string.rep('x', 16) -for i = 101, 200 do s1:replace{i, i, pad} end -box.snapshot() - -test_run:cmd("setopt delimiter ';'") -function async_replace(space, tuple, timeout) - local c = fiber.channel(1) - fiber.create(function() - box.begin() - space:replace(tuple) - fiber.sleep(timeout) - local status = pcall(box.commit) - c:put(status) - end) - return c -end; -test_run:cmd("setopt delimiter ''"); - -c1 = async_replace(s1, {1}, 0.01) -c2 = async_replace(s2, {1}, 0.01) - -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) -s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) - -c1:get() -- false (transaction was aborted) -c2:get() -- true - -s1:get(1) == nil -s2:get(1) ~= nil -s1:format() -s1:format{} - -c1 = async_replace(s1, {2}, 0.01) -c2 = async_replace(s2, {2}, 0.01) - -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) -_ = s1:create_index('sk', {parts = {2, 'unsigned'}}) -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) - -c1:get() -- false (transaction was aborted) -c2:get() -- true - -s1:get(2) == nil -s2:get(2) ~= nil -s1.index.pk:count() == s1.index.sk:count() - -s1:drop() -s2:drop() -box.cfg{vinyl_cache = vinyl_cache} - --- Transactions that reached WAL must not be aborted. -s = box.schema.space.create('test', {engine = 'vinyl'}) -_ = s:create_index('pk') - -errinj.set('ERRINJ_WAL_DELAY', true) -_ = fiber.create(function() s:replace{1} end) -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) - -fiber.sleep(0) -s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail -s:select() -s:truncate() - -errinj.set('ERRINJ_WAL_DELAY', true) -_ = fiber.create(function() s:replace{1} end) -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) - -fiber.sleep(0) -s:create_index('sk', {parts = {2, 'unsigned'}}) -s:select() -s:drop() - --- --- Check disk.compact.queue stat. --- -test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: '") - -s = box.schema.space.create('test', {engine = 'vinyl'}) -i = s:create_index('pk', {run_count_per_level = 2}) -function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end -dump() -i:stat().disk.compact.queue -- none -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue -errinj.set('ERRINJ_VY_COMPACTION_DELAY', true) -dump() -dump() -i:stat().disk.compact.queue -- 30 statements -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue -dump() -i:stat().disk.compact.queue -- 40 statements -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue -dump() -i:stat().disk.compact.queue -- 50 statements -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue -box.stat.reset() -- doesn't affect queue size -i:stat().disk.compact.queue -- 50 statements -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue -errinj.set('ERRINJ_VY_COMPACTION_DELAY', false) -while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end -i:stat().disk.compact.queue -- none -s:drop() - -test_run:cmd("clear filter") - --- -- Check that an instance doesn't crash if a run file needed for -- joining a replica is corrupted (see gh-3708). -- diff --git a/test/vinyl/errinj_ddl.result b/test/vinyl/errinj_ddl.result new file mode 100644 index 00000000..4f5d59cd --- /dev/null +++ b/test/vinyl/errinj_ddl.result @@ -0,0 +1,595 @@ +test_run = require('test_run').new() +--- +... +fiber = require('fiber') +--- +... +errinj = box.error.injection +--- +... +-- +-- Check that ALTER is abroted if a tuple inserted during space +-- format change does not conform to the new format. +-- +format = {} +--- +... +format[1] = {name = 'field1', type = 'unsigned'} +--- +... +format[2] = {name = 'field2', type = 'string', is_nullable = true} +--- +... +s = box.schema.space.create('test', {engine = 'vinyl', format = format}) +--- +... +_ = s:create_index('pk', {page_size = 16}) +--- +... +pad = string.rep('x', 16) +--- +... +for i = 101, 200 do s:replace{i, pad} end +--- +... +box.snapshot() +--- +- ok +... +ch = fiber.channel(1) +--- +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i, box.NULL} + end + ch:put(true) +end); +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +--- +- ok +... +format[2].is_nullable = false +--- +... +s:format(format) -- must fail +--- +- error: 'Tuple field 2 type does not match one required by operation: expected string' +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +--- +- ok +... +ch:get() +--- +- true +... +s:count() -- 200 +--- +- 200 +... +s:drop() +--- +... +-- +-- gh-2449: change 'unique' index property from true to false +-- is done without index rebuild. +-- +s = box.schema.space.create('test', { engine = 'vinyl' }) +--- +... +_ = s:create_index('primary') +--- +... +_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}}) +--- +... +s:insert{1, 10} +--- +- [1, 10] +... +box.snapshot() +--- +- ok +... +errinj.set("ERRINJ_VY_READ_PAGE", true); +--- +- ok +... +s.index.secondary:alter{unique = false} -- ok +--- +... +s.index.secondary.unique +--- +- false +... +s.index.secondary:alter{unique = true} -- error +--- +- error: Error injection 'vinyl page read' +... +s.index.secondary.unique +--- +- false +... +errinj.set("ERRINJ_VY_READ_PAGE", false); +--- +- ok +... +s:insert{2, 10} +--- +- [2, 10] +... +s.index.secondary:select(10) +--- +- - [1, 10] + - [2, 10] +... +s:drop() +--- +... +-- +-- Check that ALTER is aborted if a tuple inserted during index build +-- doesn't conform to the new format. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +_ = s:create_index('pk', {page_size = 16}) +--- +... +pad = string.rep('x', 16) +--- +... +for i = 101, 200 do s:replace{i, i, pad} end +--- +... +box.snapshot() +--- +- ok +... +ch = fiber.channel(1) +--- +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i} + end + ch:put(true) +end); +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +--- +- ok +... +s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail +--- +- error: Tuple field 2 required by space format is missing +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +--- +- ok +... +ch:get() +--- +- true +... +s:count() -- 200 +--- +- 200 +... +s:drop() +--- +... +-- +-- Check that ALTER is aborted if a tuple inserted during index build +-- violates unique constraint. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +_ = s:create_index('pk', {page_size = 16}) +--- +... +pad = string.rep('x', 16) +--- +... +for i = 101, 200 do s:replace{i, i, pad} end +--- +... +box.snapshot() +--- +- ok +... +ch = fiber.channel(1) +--- +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i, i + 1} + end + ch:put(true) +end); +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +--- +- ok +... +s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail +--- +- error: Duplicate key exists in unique index 'sk' in space 'test' +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +--- +- ok +... +ch:get() +--- +- true +... +s:count() -- 200 +--- +- 200 +... +s:drop() +--- +... +-- +-- Check that modifications done to the space during the final dump +-- of a newly built index are recovered properly. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +for i = 1, 5 do s:replace{i, i} end +--- +... +errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true) +--- +- ok +... +ch = fiber.channel(1) +--- +... +_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end) +--- +... +fiber.sleep(0.01) +--- +... +_ = s:delete{1} +--- +... +_ = s:replace{2, -2} +--- +... +_ = s:delete{2} +--- +... +_ = s:replace{3, -3} +--- +... +_ = s:replace{3, -2} +--- +... +_ = s:replace{3, -1} +--- +... +_ = s:delete{3} +--- +... +_ = s:upsert({3, 3}, {{'=', 2, 1}}) +--- +... +_ = s:upsert({3, 3}, {{'=', 2, 2}}) +--- +... +_ = s:delete{3} +--- +... +_ = s:replace{4, -1} +--- +... +_ = s:replace{4, -2} +--- +... +_ = s:replace{4, -4} +--- +... +_ = s:upsert({5, 1}, {{'=', 2, 1}}) +--- +... +_ = s:upsert({5, 2}, {{'=', 2, -5}}) +--- +... +_ = s:replace{6, -6} +--- +... +_ = s:upsert({7, -7}, {{'=', 2, -7}}) +--- +... +errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false) +--- +- ok +... +ch:get() +--- +- true +... +s.index.sk:select() +--- +- - [7, -7] + - [6, -6] + - [5, -5] + - [4, -4] +... +s.index.sk:stat().memory.rows +--- +- 27 +... +test_run:cmd('restart server default') +fiber = require('fiber') +--- +... +errinj = box.error.injection +--- +... +s = box.space.test +--- +... +s.index.sk:select() +--- +- - [7, -7] + - [6, -6] + - [5, -5] + - [4, -4] +... +s.index.sk:stat().memory.rows +--- +- 27 +... +box.snapshot() +--- +- ok +... +s.index.sk:select() +--- +- - [7, -7] + - [6, -6] + - [5, -5] + - [4, -4] +... +s.index.sk:stat().memory.rows +--- +- 0 +... +s:drop() +--- +... +-- +-- gh-3458: check that rw transactions that started before DDL are +-- aborted. +-- +vinyl_cache = box.cfg.vinyl_cache +--- +... +box.cfg{vinyl_cache = 0} +--- +... +s1 = box.schema.space.create('test1', {engine = 'vinyl'}) +--- +... +_ = s1:create_index('pk', {page_size = 16}) +--- +... +s2 = box.schema.space.create('test2', {engine = 'vinyl'}) +--- +... +_ = s2:create_index('pk') +--- +... +pad = string.rep('x', 16) +--- +... +for i = 101, 200 do s1:replace{i, i, pad} end +--- +... +box.snapshot() +--- +- ok +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +function async_replace(space, tuple, timeout) + local c = fiber.channel(1) + fiber.create(function() + box.begin() + space:replace(tuple) + fiber.sleep(timeout) + local status = pcall(box.commit) + c:put(status) + end) + return c +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +c1 = async_replace(s1, {1}, 0.01) +--- +... +c2 = async_replace(s2, {1}, 0.01) +--- +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +--- +- ok +... +s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}} +--- +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +--- +- ok +... +c1:get() -- false (transaction was aborted) +--- +- false +... +c2:get() -- true +--- +- true +... +s1:get(1) == nil +--- +- true +... +s2:get(1) ~= nil +--- +- true +... +s1:format() +--- +- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'unsigned'}] +... +s1:format{} +--- +... +c1 = async_replace(s1, {2}, 0.01) +--- +... +c2 = async_replace(s2, {2}, 0.01) +--- +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +--- +- ok +... +_ = s1:create_index('sk', {parts = {2, 'unsigned'}}) +--- +... +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +--- +- ok +... +c1:get() -- false (transaction was aborted) +--- +- false +... +c2:get() -- true +--- +- true +... +s1:get(2) == nil +--- +- true +... +s2:get(2) ~= nil +--- +- true +... +s1.index.pk:count() == s1.index.sk:count() +--- +- true +... +s1:drop() +--- +... +s2:drop() +--- +... +box.cfg{vinyl_cache = vinyl_cache} +--- +... +-- Transactions that reached WAL must not be aborted. +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +errinj.set('ERRINJ_WAL_DELAY', true) +--- +- ok +... +_ = fiber.create(function() s:replace{1} end) +--- +... +_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) +--- +... +fiber.sleep(0) +--- +... +s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail +--- +- error: Tuple field 2 required by space format is missing +... +s:select() +--- +- - [1] +... +s:truncate() +--- +... +errinj.set('ERRINJ_WAL_DELAY', true) +--- +- ok +... +_ = fiber.create(function() s:replace{1} end) +--- +... +_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) +--- +... +fiber.sleep(0) +--- +... +s:create_index('sk', {parts = {2, 'unsigned'}}) +--- +- error: Tuple field 2 required by space format is missing +... +s:select() +--- +- - [1] +... +s:drop() +--- +... diff --git a/test/vinyl/errinj_ddl.test.lua b/test/vinyl/errinj_ddl.test.lua new file mode 100644 index 00000000..0948bc3d --- /dev/null +++ b/test/vinyl/errinj_ddl.test.lua @@ -0,0 +1,260 @@ +test_run = require('test_run').new() +fiber = require('fiber') +errinj = box.error.injection + +-- +-- Check that ALTER is abroted if a tuple inserted during space +-- format change does not conform to the new format. +-- +format = {} +format[1] = {name = 'field1', type = 'unsigned'} +format[2] = {name = 'field2', type = 'string', is_nullable = true} +s = box.schema.space.create('test', {engine = 'vinyl', format = format}) +_ = s:create_index('pk', {page_size = 16}) + +pad = string.rep('x', 16) +for i = 101, 200 do s:replace{i, pad} end +box.snapshot() + +ch = fiber.channel(1) +test_run:cmd("setopt delimiter ';'") +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i, box.NULL} + end + ch:put(true) +end); +test_run:cmd("setopt delimiter ''"); + +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +format[2].is_nullable = false +s:format(format) -- must fail +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) + +ch:get() + +s:count() -- 200 +s:drop() + +-- +-- gh-2449: change 'unique' index property from true to false +-- is done without index rebuild. +-- +s = box.schema.space.create('test', { engine = 'vinyl' }) +_ = s:create_index('primary') +_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}}) +s:insert{1, 10} +box.snapshot() +errinj.set("ERRINJ_VY_READ_PAGE", true); +s.index.secondary:alter{unique = false} -- ok +s.index.secondary.unique +s.index.secondary:alter{unique = true} -- error +s.index.secondary.unique +errinj.set("ERRINJ_VY_READ_PAGE", false); +s:insert{2, 10} +s.index.secondary:select(10) +s:drop() + +-- +-- Check that ALTER is aborted if a tuple inserted during index build +-- doesn't conform to the new format. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk', {page_size = 16}) + +pad = string.rep('x', 16) +for i = 101, 200 do s:replace{i, i, pad} end +box.snapshot() + +ch = fiber.channel(1) +test_run:cmd("setopt delimiter ';'") +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i} + end + ch:put(true) +end); +test_run:cmd("setopt delimiter ''"); + +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) + +ch:get() + +s:count() -- 200 +s:drop() + +-- +-- Check that ALTER is aborted if a tuple inserted during index build +-- violates unique constraint. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk', {page_size = 16}) + +pad = string.rep('x', 16) +for i = 101, 200 do s:replace{i, i, pad} end +box.snapshot() + +ch = fiber.channel(1) +test_run:cmd("setopt delimiter ';'") +_ = fiber.create(function() + fiber.sleep(0.01) + for i = 1, 100 do + s:replace{i, i + 1} + end + ch:put(true) +end); +test_run:cmd("setopt delimiter ''"); + +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) + +ch:get() + +s:count() -- 200 +s:drop() + +-- +-- Check that modifications done to the space during the final dump +-- of a newly built index are recovered properly. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk') + +for i = 1, 5 do s:replace{i, i} end + +errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true) +ch = fiber.channel(1) +_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end) + +fiber.sleep(0.01) + +_ = s:delete{1} +_ = s:replace{2, -2} +_ = s:delete{2} +_ = s:replace{3, -3} +_ = s:replace{3, -2} +_ = s:replace{3, -1} +_ = s:delete{3} +_ = s:upsert({3, 3}, {{'=', 2, 1}}) +_ = s:upsert({3, 3}, {{'=', 2, 2}}) +_ = s:delete{3} +_ = s:replace{4, -1} +_ = s:replace{4, -2} +_ = s:replace{4, -4} +_ = s:upsert({5, 1}, {{'=', 2, 1}}) +_ = s:upsert({5, 2}, {{'=', 2, -5}}) +_ = s:replace{6, -6} +_ = s:upsert({7, -7}, {{'=', 2, -7}}) + +errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false) +ch:get() + +s.index.sk:select() +s.index.sk:stat().memory.rows + +test_run:cmd('restart server default') + +fiber = require('fiber') +errinj = box.error.injection + +s = box.space.test + +s.index.sk:select() +s.index.sk:stat().memory.rows + +box.snapshot() + +s.index.sk:select() +s.index.sk:stat().memory.rows + +s:drop() + +-- +-- gh-3458: check that rw transactions that started before DDL are +-- aborted. +-- +vinyl_cache = box.cfg.vinyl_cache +box.cfg{vinyl_cache = 0} + +s1 = box.schema.space.create('test1', {engine = 'vinyl'}) +_ = s1:create_index('pk', {page_size = 16}) +s2 = box.schema.space.create('test2', {engine = 'vinyl'}) +_ = s2:create_index('pk') + +pad = string.rep('x', 16) +for i = 101, 200 do s1:replace{i, i, pad} end +box.snapshot() + +test_run:cmd("setopt delimiter ';'") +function async_replace(space, tuple, timeout) + local c = fiber.channel(1) + fiber.create(function() + box.begin() + space:replace(tuple) + fiber.sleep(timeout) + local status = pcall(box.commit) + c:put(status) + end) + return c +end; +test_run:cmd("setopt delimiter ''"); + +c1 = async_replace(s1, {1}, 0.01) +c2 = async_replace(s2, {1}, 0.01) + +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}} +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) + +c1:get() -- false (transaction was aborted) +c2:get() -- true + +s1:get(1) == nil +s2:get(1) ~= nil +s1:format() +s1:format{} + +c1 = async_replace(s1, {2}, 0.01) +c2 = async_replace(s2, {2}, 0.01) + +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +_ = s1:create_index('sk', {parts = {2, 'unsigned'}}) +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) + +c1:get() -- false (transaction was aborted) +c2:get() -- true + +s1:get(2) == nil +s2:get(2) ~= nil +s1.index.pk:count() == s1.index.sk:count() + +s1:drop() +s2:drop() +box.cfg{vinyl_cache = vinyl_cache} + +-- Transactions that reached WAL must not be aborted. +s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk') + +errinj.set('ERRINJ_WAL_DELAY', true) +_ = fiber.create(function() s:replace{1} end) +_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) + +fiber.sleep(0) +s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail +s:select() +s:truncate() + +errinj.set('ERRINJ_WAL_DELAY', true) +_ = fiber.create(function() s:replace{1} end) +_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) + +fiber.sleep(0) +s:create_index('sk', {parts = {2, 'unsigned'}}) +s:select() +s:drop() diff --git a/test/vinyl/errinj_stat.result b/test/vinyl/errinj_stat.result new file mode 100644 index 00000000..fac56cee --- /dev/null +++ b/test/vinyl/errinj_stat.result @@ -0,0 +1,152 @@ +test_run = require('test_run').new() +--- +... +-- Since we store LSNs in data files, the data size may differ +-- from run to run. Deploy a new server to make sure it will be +-- the same so that we can check it. +test_run:cmd('create server test with script = "vinyl/stat.lua"') +--- +- true +... +test_run:cmd('start server test') +--- +- true +... +test_run:cmd('switch test') +--- +- true +... +-- Compressed data size depends on the zstd version so let's +-- filter it out. +test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: '") +--- +- true +... +fiber = require('fiber') +--- +... +errinj = box.error.injection +--- +... +-- +-- Check disk.compact.queue stat. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +i = s:create_index('pk', {run_count_per_level = 2}) +--- +... +function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end +--- +... +dump() +--- +... +i:stat().disk.compact.queue -- none +--- +- bytes_compressed: + pages: 0 + rows: 0 + bytes: 0 +... +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +--- +- true +... +errinj.set('ERRINJ_VY_COMPACTION_DELAY', true) +--- +- ok +... +dump() +--- +... +dump() +--- +... +i:stat().disk.compact.queue -- 30 statements +--- +- bytes_compressed: + pages: 3 + rows: 30 + bytes: 411 +... +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +--- +- true +... +dump() +--- +... +i:stat().disk.compact.queue -- 40 statements +--- +- bytes_compressed: + pages: 4 + rows: 40 + bytes: 548 +... +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +--- +- true +... +dump() +--- +... +i:stat().disk.compact.queue -- 50 statements +--- +- bytes_compressed: + pages: 5 + rows: 50 + bytes: 685 +... +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +--- +- true +... +box.stat.reset() -- doesn't affect queue size +--- +... +i:stat().disk.compact.queue -- 50 statements +--- +- bytes_compressed: + pages: 5 + rows: 50 + bytes: 685 +... +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +--- +- true +... +errinj.set('ERRINJ_VY_COMPACTION_DELAY', false) +--- +- ok +... +while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end +--- +... +i:stat().disk.compact.queue -- none +--- +- bytes_compressed: + pages: 0 + rows: 0 + bytes: 0 +... +s:drop() +--- +... +test_run:cmd("clear filter") +--- +- true +... +test_run:cmd('switch default') +--- +- true +... +test_run:cmd('stop server test') +--- +- true +... +test_run:cmd('cleanup server test') +--- +- true +... diff --git a/test/vinyl/errinj_stat.test.lua b/test/vinyl/errinj_stat.test.lua new file mode 100644 index 00000000..1e0e63ae --- /dev/null +++ b/test/vinyl/errinj_stat.test.lua @@ -0,0 +1,48 @@ +test_run = require('test_run').new() + +-- Since we store LSNs in data files, the data size may differ +-- from run to run. Deploy a new server to make sure it will be +-- the same so that we can check it. +test_run:cmd('create server test with script = "vinyl/stat.lua"') +test_run:cmd('start server test') +test_run:cmd('switch test') + +-- Compressed data size depends on the zstd version so let's +-- filter it out. +test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: '") + +fiber = require('fiber') +errinj = box.error.injection + +-- +-- Check disk.compact.queue stat. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +i = s:create_index('pk', {run_count_per_level = 2}) +function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end +dump() +i:stat().disk.compact.queue -- none +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +errinj.set('ERRINJ_VY_COMPACTION_DELAY', true) +dump() +dump() +i:stat().disk.compact.queue -- 30 statements +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +dump() +i:stat().disk.compact.queue -- 40 statements +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +dump() +i:stat().disk.compact.queue -- 50 statements +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +box.stat.reset() -- doesn't affect queue size +i:stat().disk.compact.queue -- 50 statements +i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +errinj.set('ERRINJ_VY_COMPACTION_DELAY', false) +while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end +i:stat().disk.compact.queue -- none +s:drop() + +test_run:cmd("clear filter") +test_run:cmd('switch default') +test_run:cmd('stop server test') +test_run:cmd('cleanup server test') diff --git a/test/vinyl/errinj_tx.result b/test/vinyl/errinj_tx.result new file mode 100644 index 00000000..a7583b2e --- /dev/null +++ b/test/vinyl/errinj_tx.result @@ -0,0 +1,435 @@ +test_run = require('test_run').new() +--- +... +fiber = require('fiber') +--- +... +txn_proxy = require('txn_proxy') +--- +... +create_iterator = require('utils').create_iterator +--- +... +errinj = box.error.injection +--- +... +-- +-- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE +-- +s = box.schema.space.create('test', {engine='vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end +--- +... +errinj.set("ERRINJ_WAL_WRITE", true) +--- +- ok +... +f() +--- +- error: Failed to write to disk +... +s:select{} +--- +- [] +... +errinj.set("ERRINJ_WAL_WRITE", false) +--- +- ok +... +f() +--- +... +s:select{} +--- +- - [1, 'hi'] + - [2, 'bye'] +... +s:drop() +--- +... +--https://github.com/tarantool/tarantool/issues/1842 +--test error injection +s = box.schema.space.create('test', {engine='vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +s:replace{0, 0} +--- +- [0, 0] +... +s:replace{1, 0} +--- +- [1, 0] +... +s:replace{2, 0} +--- +- [2, 0] +... +errinj.set("ERRINJ_WAL_WRITE", true) +--- +- ok +... +s:replace{3, 0} +--- +- error: Failed to write to disk +... +s:replace{4, 0} +--- +- error: Failed to write to disk +... +s:replace{5, 0} +--- +- error: Failed to write to disk +... +s:replace{6, 0} +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_WRITE", false) +--- +- ok +... +s:replace{7, 0} +--- +- [7, 0] +... +s:replace{8, 0} +--- +- [8, 0] +... +s:select{} +--- +- - [0, 0] + - [1, 0] + - [2, 0] + - [7, 0] + - [8, 0] +... +s:drop() +--- +... +--iterator test +test_run:cmd("setopt delimiter ';'") +--- +- true +... +fiber_status = 0 + +function fiber_func() + box.begin() + s:replace{5, 5} + fiber_status = 1 + local res = {pcall(box.commit) } + fiber_status = 2 + return unpack(res) +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +s = box.schema.space.create('test', {engine='vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +_ = s:replace{0, 0} +--- +... +_ = s:replace{10, 0} +--- +... +_ = s:replace{20, 0} +--- +... +test_run:cmd("setopt delimiter ';'"); +--- +- true +... +faced_trash = false +for i = 1,100 do + errinj.set("ERRINJ_WAL_WRITE", true) + local f = fiber.create(fiber_func) + local itr = create_iterator(s, {0}, {iterator='GE'}) + local first = itr.next() + local second = itr.next() + if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end + while fiber_status <= 1 do fiber.sleep(0.001) end + local _,next = pcall(itr.next) + _,next = pcall(itr.next) + _,next = pcall(itr.next) + errinj.set("ERRINJ_WAL_WRITE", false) + s:delete{5} +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +faced_trash +--- +- false +... +s:drop() +--- +... +-- TX in prepared but not committed state +s = box.schema.space.create('test', {engine='vinyl'}) +--- +... +_ = s:create_index('pk') +--- +... +s:replace{1, "original"} +--- +- [1, 'original'] +... +s:replace{2, "original"} +--- +- [2, 'original'] +... +s:replace{3, "original"} +--- +- [3, 'original'] +... +c0 = txn_proxy.new() +--- +... +c0:begin() +--- +- +... +c1 = txn_proxy.new() +--- +... +c1:begin() +--- +- +... +c2 = txn_proxy.new() +--- +... +c2:begin() +--- +- +... +c3 = txn_proxy.new() +--- +... +c3:begin() +--- +- +... +-- +-- Prepared transactions +-- +-- Pause WAL writer to cause all further calls to box.commit() to move +-- transactions into prepared, but not committed yet state. +errinj.set("ERRINJ_WAL_DELAY", true) +--- +- ok +... +lsn = box.info.lsn +--- +... +c0('s:replace{1, "c0"}') +--- +- - [1, 'c0'] +... +c0('s:replace{2, "c0"}') +--- +- - [2, 'c0'] +... +c0('s:replace{3, "c0"}') +--- +- - [3, 'c0'] +... +_ = fiber.create(c0.commit, c0) +--- +... +box.info.lsn == lsn +--- +- true +... +c1('s:replace{1, "c1"}') +--- +- - [1, 'c1'] +... +c1('s:replace{2, "c1"}') +--- +- - [2, 'c1'] +... +_ = fiber.create(c1.commit, c1) +--- +... +box.info.lsn == lsn +--- +- true +... +c3('s:select{1}') -- c1 is visible +--- +- - [[1, 'c1']] +... +c2('s:replace{1, "c2"}') +--- +- - [1, 'c2'] +... +c2('s:replace{3, "c2"}') +--- +- - [3, 'c2'] +... +_ = fiber.create(c2.commit, c2) +--- +... +box.info.lsn == lsn +--- +- true +... +c3('s:select{1}') -- c1 is visible, c2 is not +--- +- - [[1, 'c1']] +... +c3('s:select{2}') -- c1 is visible +--- +- - [[2, 'c1']] +... +c3('s:select{3}') -- c2 is not visible +--- +- - [[3, 'c0']] +... +-- Resume WAL writer and wait until all transactions will been committed +errinj.set("ERRINJ_WAL_DELAY", false) +--- +- ok +... +REQ_COUNT = 7 +--- +... +while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end +--- +... +box.info.lsn == lsn + REQ_COUNT +--- +- true +... +c3('s:select{1}') -- c1 is visible, c2 is not +--- +- - [[1, 'c1']] +... +c3('s:select{2}') -- c1 is visible +--- +- - [[2, 'c1']] +... +c3('s:select{3}') -- c2 is not visible +--- +- - [[3, 'c0']] +... +c3:commit() +--- +- +... +s:drop() +--- +... +-- +-- Test mem restoration on a prepared and not commited statement +-- after moving iterator into read view. +-- +space = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +pk = space:create_index('pk') +--- +... +space:replace{1} +--- +- [1] +... +space:replace{2} +--- +- [2] +... +space:replace{3} +--- +- [3] +... +last_read = nil +--- +... +errinj.set("ERRINJ_WAL_DELAY", true) +--- +- ok +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +-- block until wal_delay = false +-- send iterator to read view +-- flush mem and update index version to trigger iterator restore +function fill_space() + box.begin() + space:replace{1} + space:replace{2} + space:replace{3} + box.commit() + space:replace{1, 1} + box.snapshot() +end; +--- +... +function iterate_in_read_view() + local i = create_iterator(space) + last_read = i.next() + fiber.sleep(100000) + last_read = i.next() +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +f1 = fiber.create(fill_space) +--- +... +-- Prepared transaction is blocked due to wal_delay. +-- Start iterator with vlsn = INT64_MAX +f2 = fiber.create(iterate_in_read_view) +--- +... +last_read +--- +- [1] +... +-- Finish prepared transaction and send to read view the iterator. +errinj.set("ERRINJ_WAL_DELAY", false) +--- +- ok +... +while f1:status() ~= 'dead' do fiber.sleep(0.01) end +--- +... +f2:wakeup() +--- +... +while f2:status() ~= 'dead' do fiber.sleep(0.01) end +--- +... +last_read +--- +- [2] +... +space:drop() +--- +... diff --git a/test/vinyl/errinj_tx.test.lua b/test/vinyl/errinj_tx.test.lua new file mode 100644 index 00000000..cf83b326 --- /dev/null +++ b/test/vinyl/errinj_tx.test.lua @@ -0,0 +1,194 @@ +test_run = require('test_run').new() +fiber = require('fiber') +txn_proxy = require('txn_proxy') +create_iterator = require('utils').create_iterator +errinj = box.error.injection + +-- +-- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE +-- +s = box.schema.space.create('test', {engine='vinyl'}) +_ = s:create_index('pk') +function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end +errinj.set("ERRINJ_WAL_WRITE", true) +f() +s:select{} +errinj.set("ERRINJ_WAL_WRITE", false) +f() +s:select{} +s:drop() + +--https://github.com/tarantool/tarantool/issues/1842 +--test error injection +s = box.schema.space.create('test', {engine='vinyl'}) +_ = s:create_index('pk') +s:replace{0, 0} + +s:replace{1, 0} +s:replace{2, 0} +errinj.set("ERRINJ_WAL_WRITE", true) +s:replace{3, 0} +s:replace{4, 0} +s:replace{5, 0} +s:replace{6, 0} +errinj.set("ERRINJ_WAL_WRITE", false) +s:replace{7, 0} +s:replace{8, 0} +s:select{} + +s:drop() + +--iterator test +test_run:cmd("setopt delimiter ';'") + +fiber_status = 0 + +function fiber_func() + box.begin() + s:replace{5, 5} + fiber_status = 1 + local res = {pcall(box.commit) } + fiber_status = 2 + return unpack(res) +end; + +test_run:cmd("setopt delimiter ''"); + +s = box.schema.space.create('test', {engine='vinyl'}) +_ = s:create_index('pk') + +_ = s:replace{0, 0} +_ = s:replace{10, 0} +_ = s:replace{20, 0} + +test_run:cmd("setopt delimiter ';'"); + +faced_trash = false +for i = 1,100 do + errinj.set("ERRINJ_WAL_WRITE", true) + local f = fiber.create(fiber_func) + local itr = create_iterator(s, {0}, {iterator='GE'}) + local first = itr.next() + local second = itr.next() + if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end + while fiber_status <= 1 do fiber.sleep(0.001) end + local _,next = pcall(itr.next) + _,next = pcall(itr.next) + _,next = pcall(itr.next) + errinj.set("ERRINJ_WAL_WRITE", false) + s:delete{5} +end; + +test_run:cmd("setopt delimiter ''"); + +faced_trash + +s:drop() + +-- TX in prepared but not committed state +s = box.schema.space.create('test', {engine='vinyl'}) +_ = s:create_index('pk') + +s:replace{1, "original"} +s:replace{2, "original"} +s:replace{3, "original"} + +c0 = txn_proxy.new() +c0:begin() +c1 = txn_proxy.new() +c1:begin() +c2 = txn_proxy.new() +c2:begin() +c3 = txn_proxy.new() +c3:begin() + +-- +-- Prepared transactions +-- + +-- Pause WAL writer to cause all further calls to box.commit() to move +-- transactions into prepared, but not committed yet state. +errinj.set("ERRINJ_WAL_DELAY", true) +lsn = box.info.lsn +c0('s:replace{1, "c0"}') +c0('s:replace{2, "c0"}') +c0('s:replace{3, "c0"}') +_ = fiber.create(c0.commit, c0) +box.info.lsn == lsn +c1('s:replace{1, "c1"}') +c1('s:replace{2, "c1"}') +_ = fiber.create(c1.commit, c1) +box.info.lsn == lsn +c3('s:select{1}') -- c1 is visible +c2('s:replace{1, "c2"}') +c2('s:replace{3, "c2"}') +_ = fiber.create(c2.commit, c2) +box.info.lsn == lsn +c3('s:select{1}') -- c1 is visible, c2 is not +c3('s:select{2}') -- c1 is visible +c3('s:select{3}') -- c2 is not visible + +-- Resume WAL writer and wait until all transactions will been committed +errinj.set("ERRINJ_WAL_DELAY", false) +REQ_COUNT = 7 +while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end +box.info.lsn == lsn + REQ_COUNT + +c3('s:select{1}') -- c1 is visible, c2 is not +c3('s:select{2}') -- c1 is visible +c3('s:select{3}') -- c2 is not visible +c3:commit() + +s:drop() + +-- +-- Test mem restoration on a prepared and not commited statement +-- after moving iterator into read view. +-- +space = box.schema.space.create('test', {engine = 'vinyl'}) +pk = space:create_index('pk') +space:replace{1} +space:replace{2} +space:replace{3} + +last_read = nil + +errinj.set("ERRINJ_WAL_DELAY", true) + +test_run:cmd("setopt delimiter ';'") + +function fill_space() + box.begin() + space:replace{1} + space:replace{2} + space:replace{3} +-- block until wal_delay = false + box.commit() +-- send iterator to read view + space:replace{1, 1} +-- flush mem and update index version to trigger iterator restore + box.snapshot() +end; + +function iterate_in_read_view() + local i = create_iterator(space) + last_read = i.next() + fiber.sleep(100000) + last_read = i.next() +end; + +test_run:cmd("setopt delimiter ''"); + +f1 = fiber.create(fill_space) +-- Prepared transaction is blocked due to wal_delay. +-- Start iterator with vlsn = INT64_MAX +f2 = fiber.create(iterate_in_read_view) +last_read +-- Finish prepared transaction and send to read view the iterator. +errinj.set("ERRINJ_WAL_DELAY", false) +while f1:status() ~= 'dead' do fiber.sleep(0.01) end +f2:wakeup() +while f2:status() ~= 'dead' do fiber.sleep(0.01) end +last_read + +space:drop() diff --git a/test/vinyl/errinj_vylog.result b/test/vinyl/errinj_vylog.result index 9ced03df..0e3b79c4 100644 --- a/test/vinyl/errinj_vylog.result +++ b/test/vinyl/errinj_vylog.result @@ -5,6 +5,41 @@ fiber = require('fiber') --- ... -- +-- Check that DDL operations are logged in vylog only after successful +-- WAL write. +-- +-- If we logged an index creation in the metadata log before WAL write, +-- WAL failure would result in leaving the index record in vylog forever. +-- Since we use LSN to identify indexes in vylog, retrying index creation +-- would then lead to a duplicate index id in vylog and hence inability +-- to make a snapshot or recover. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +box.error.injection.set('ERRINJ_WAL_IO', true) +--- +- ok +... +_ = s:create_index('pk') +--- +- error: Failed to write to disk +... +box.error.injection.set('ERRINJ_WAL_IO', false) +--- +- ok +... +_ = s:create_index('pk') +--- +... +box.snapshot() +--- +- ok +... +s:drop() +--- +... +-- -- Check that an error to commit a new run to vylog does not -- break vinyl permanently. -- diff --git a/test/vinyl/errinj_vylog.test.lua b/test/vinyl/errinj_vylog.test.lua index c1fd517d..ce9e12e5 100644 --- a/test/vinyl/errinj_vylog.test.lua +++ b/test/vinyl/errinj_vylog.test.lua @@ -2,6 +2,24 @@ test_run = require('test_run').new() fiber = require('fiber') -- +-- Check that DDL operations are logged in vylog only after successful +-- WAL write. +-- +-- If we logged an index creation in the metadata log before WAL write, +-- WAL failure would result in leaving the index record in vylog forever. +-- Since we use LSN to identify indexes in vylog, retrying index creation +-- would then lead to a duplicate index id in vylog and hence inability +-- to make a snapshot or recover. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) +box.error.injection.set('ERRINJ_WAL_IO', true) +_ = s:create_index('pk') +box.error.injection.set('ERRINJ_WAL_IO', false) +_ = s:create_index('pk') +box.snapshot() +s:drop() + +-- -- Check that an error to commit a new run to vylog does not -- break vinyl permanently. -- diff --git a/test/vinyl/suite.ini b/test/vinyl/suite.ini index ace53e7f..d2a194d8 100644 --- a/test/vinyl/suite.ini +++ b/test/vinyl/suite.ini @@ -2,7 +2,7 @@ core = tarantool description = vinyl integration tests script = vinyl.lua -release_disabled = errinj.test.lua errinj_gc.test.lua errinj_vylog.test.lua partial_dump.test.lua quota_timeout.test.lua recovery_quota.test.lua replica_rejoin.test.lua +release_disabled = errinj.test.lua errinj_ddl.test.lua errinj_gc.test.lua errinj_stat.test.lua errinj_tx.test.lua errinj_vylog.test.lua partial_dump.test.lua quota_timeout.test.lua recovery_quota.test.lua replica_rejoin.test.lua config = suite.cfg lua_libs = suite.lua stress.lua large.lua txn_proxy.lua ../box/lua/utils.lua use_unix_sockets = True -- 2.11.0