diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result index 23ab845b3f087988bcfe8811d52e529e25580558..a16475f5eb512faa516a8eb7335a9ccf6d56e031 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,1765 +435,646 @@ 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') +-- +-- Space drop in the middle of dump. +-- +test_run:cmd("create server test with script='vinyl/low_quota.lua'") --- +- true ... -s:replace{0, 0} +test_run:cmd("start server test with args='1048576'") --- -- [0, 0] +- true ... -s:replace{1, 0} +test_run:cmd('switch test') --- -- [1, 0] +- true ... -s:replace{2, 0} +fiber = require 'fiber' --- -- [2, 0] ... -errinj.set("ERRINJ_WAL_WRITE", true) +box.cfg{vinyl_timeout = 0.001} --- -- ok ... -s:replace{3, 0} +s = box.schema.space.create('test', {engine = 'vinyl'}) --- -- error: Failed to write to disk ... -s:replace{4, 0} +_ = s:create_index('i1', {parts = {1, 'unsigned'}}) --- -- error: Failed to write to disk ... -s:replace{5, 0} +_ = s:create_index('i2', {parts = {2, 'unsigned'}}) --- -- error: Failed to write to disk ... -s:replace{6, 0} +_ = s:insert{1, 1} --- -- error: Failed to write to disk ... -errinj.set("ERRINJ_WAL_WRITE", false) +-- Delay dump so that we can manage to drop the space +-- while it is still being dumped. +box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', true) --- - ok ... -s:replace{7, 0} ---- -- [7, 0] -... -s:replace{8, 0} +-- Before failing on quota timeout, the following fiber +-- will trigger dump due to memory shortage. +_ = fiber.create(function() s:insert{2, 2, string.rep('x', box.cfg.vinyl_memory)} end) --- -- [8, 0] ... -s:select{} +-- Let the fiber run. +fiber.sleep(0) --- -- - [0, 0] - - [1, 0] - - [2, 0] - - [7, 0] - - [8, 0] ... +-- Drop the space while the dump task is still running. s:drop() --- ... -create_iterator = require('utils').create_iterator ---- -... ---iterator test -test_run:cmd("setopt delimiter ';'") +-- Wait for the dump task to complete. +box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', false) --- -- true +- ok ... -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; +box.snapshot() --- +- ok ... -test_run:cmd("setopt delimiter ''"); +-- +-- Check that all dump/compact tasks that are in progress at +-- the time when the server stops are aborted immediately. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) --- -- true ... -s = box.schema.space.create('test', {engine='vinyl'}) +_ = s:create_index('i1', {parts = {1, 'unsigned'}}) --- ... -_ = s:create_index('pk') +_ = s:create_index('i2', {parts = {2, 'unsigned'}}) --- ... -fiber = require('fiber') +box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01) --- +- ok ... -_ = s:replace{0, 0} +for i = 1, 1000 do s:replace{i, i} end --- ... -_ = s:replace{10, 0} +_ = fiber.create(function() box.snapshot() end) --- ... -_ = s:replace{20, 0} +fiber.sleep(0.01) --- ... -test_run:cmd("setopt delimiter ';'"); +test_run:cmd('switch default') --- - 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; +t1 = fiber.time() --- ... -test_run:cmd("setopt delimiter ''"); +test_run:cmd("stop server test") --- - true ... -faced_trash ---- -- false -... -s:drop() +t2 = fiber.time() --- ... --- TX in prepared but not committed state -s = box.schema.space.create('test', {engine='vinyl'}) +t2 - t1 < 1 --- +- true ... -_ = s:create_index('pk') +test_run:cmd("cleanup server test") --- +- true ... -fiber = require('fiber') +s = box.schema.space.create('test', {engine = 'vinyl'}) --- ... -txn_proxy = require('txn_proxy') +_ = s:create_index('i1', {parts = {1, 'unsigned'}}) --- ... -s:replace{1, "original"} +c = 10 --- -- [1, 'original'] ... -s:replace{2, "original"} +errinj.set("ERRINJ_WAL_WRITE_DISK", true) --- -- [2, 'original'] +- ok ... -s:replace{3, "original"} +for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end --- -- [3, 'original'] ... -c0 = txn_proxy.new() +while c ~= 0 do fiber.sleep(0.001) end --- ... -c0:begin() +s:select{} --- -- +- [] ... -c1 = txn_proxy.new() +errinj.set("ERRINJ_WAL_WRITE_DISK", false) --- +- ok ... -c1:begin() +s:drop() --- -- ... -c2 = txn_proxy.new() +s = box.schema.space.create('test', {engine = 'vinyl'}) --- ... -c2:begin() +_ = s:create_index('i1', {parts = {1, 'unsigned'}}) --- -- ... -c3 = txn_proxy.new() +for i = 0, 9 do s:replace({i, i + 1}) end --- ... -c3:begin() +box.snapshot() --- -- +- ok ... --- --- 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) +errinj.set("ERRINJ_XLOG_GARBAGE", true) --- - ok ... -lsn = box.info.lsn +s:select() --- +- error: tx checksum mismatch ... -c0('s:replace{1, "c0"}') +errinj.set("ERRINJ_XLOG_GARBAGE", false) --- -- - [1, 'c0'] +- ok ... -c0('s:replace{2, "c0"}') +errinj.set("ERRINJ_VYRUN_DATA_READ", true) --- -- - [2, 'c0'] +- ok ... -c0('s:replace{3, "c0"}') +s:select() --- -- - [3, 'c0'] +- error: failed to read from file ... -_ = fiber.create(c0.commit, c0) +errinj.set("ERRINJ_VYRUN_DATA_READ", false) --- +- ok ... -box.info.lsn == lsn +s:select() --- -- true +- - [0, 1] + - [1, 2] + - [2, 3] + - [3, 4] + - [4, 5] + - [5, 6] + - [6, 7] + - [7, 8] + - [8, 9] + - [9, 10] ... -c1('s:replace{1, "c1"}') +s:drop() --- -- - [1, 'c1'] ... -c1('s:replace{2, "c1"}') +s = box.schema.space.create('test', {engine = 'vinyl'}) --- -- - [2, 'c1'] ... -_ = fiber.create(c1.commit, c1) +_ = s:create_index('i1', {parts = {1, 'unsigned'}}) --- ... -box.info.lsn == lsn +for i = 0, 9 do s:replace({i, i + 1}) end --- -- true ... -c3('s:select{1}') -- c1 is visible +errinj.set("ERRINJ_XLOG_GARBAGE", true) --- -- - [[1, 'c1']] +- ok ... -c2('s:replace{1, "c2"}') +box.snapshot() --- -- - [1, 'c2'] +- error: tx checksum mismatch ... -c2('s:replace{3, "c2"}') +for i = 10, 19 do s:replace({i, i + 1}) end --- -- - [3, 'c2'] ... -_ = fiber.create(c2.commit, c2) +errinj.set("ERRINJ_XLOG_GARBAGE", false) --- +- ok ... -box.info.lsn == lsn +box.snapshot() --- -- true +- ok ... -c3('s:select{1}') -- c1 is visible, c2 is not +s:select() --- -- - [[1, 'c1']] +- - [0, 1] + - [1, 2] + - [2, 3] + - [3, 4] + - [4, 5] + - [5, 6] + - [6, 7] + - [7, 8] + - [8, 9] + - [9, 10] + - [10, 11] + - [11, 12] + - [12, 13] + - [13, 14] + - [14, 15] + - [15, 16] + - [16, 17] + - [17, 18] + - [18, 19] + - [19, 20] ... -c3('s:select{2}') -- c1 is visible +s:drop() --- -- - [[2, 'c1']] ... -c3('s:select{3}') -- c2 is not visible +-- Point select from secondary index during snapshot. +-- Once upon time that leaded to crash. +s = box.schema.space.create('test', {engine = 'vinyl'}) --- -- - [[3, 'c0']] ... --- Resume WAL writer and wait until all transactions will been committed -errinj.set("ERRINJ_WAL_DELAY", false) +i1 = s:create_index('pk', {parts = {1, 'uint'}, bloom_fpr = 0.5}) --- -- ok ... -REQ_COUNT = 7 +i2 = s:create_index('sk', {parts = {2, 'uint'}, bloom_fpr = 0.5}) --- ... -while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end +for i = 1,10 do s:replace{i, i, 0} end --- ... -box.info.lsn == lsn + REQ_COUNT +test_run:cmd("setopt delimiter ';'") --- - true ... -c3('s:select{1}') -- c1 is visible, c2 is not +function worker() + for i = 11,20,2 do + s:upsert({i, i}, {{'=', 3, 1}}) + errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true) + i1:select{i} + s:upsert({i + 1 ,i + 1}, {{'=', 3, 1}}) + errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true) + i2:select{i + 1} + end +end +test_run:cmd("setopt delimiter ''"); --- -- - [[1, 'c1']] ... -c3('s:select{2}') -- c1 is visible +f = fiber.create(worker) --- -- - [[2, 'c1']] ... -c3('s:select{3}') -- c2 is not visible +while f:status() ~= 'dead' do box.snapshot() fiber.sleep(0.01) end --- -- - [[3, 'c0']] ... -c3:commit() +errinj.set("ERRINJ_VY_POINT_ITER_WAIT", false) --- -- +- ok ... 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'}) +-- vinyl: vy_cache_add: Assertion `0' failed +-- https://github.com/tarantool/tarantool/issues/2685 +s = box.schema.create_space('test', {engine = 'vinyl'}) --- ... -pk = space:create_index('pk') +pk = s:create_index('pk') --- ... -space:replace{1} +s:replace{2, 0} --- -- [1] +- [2, 0] ... -space:replace{2} +box.snapshot() --- -- [2] +- ok ... -space:replace{3} +s:replace{1, 0} --- -- [3] +- [1, 0] ... -last_read = nil +box.snapshot() --- +- ok ... -errinj.set("ERRINJ_WAL_DELAY", true) +s:replace{0, 0} --- -- ok +- [0, 0] ... -test_run:cmd("setopt delimiter ';'") +s:select{0} --- -- true +- - [0, 0] ... --- 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; +errinj.set("ERRINJ_WAL_DELAY", true) --- +- ok ... -function iterate_in_read_view() - local i = create_iterator(space) - last_read = i.next() - fiber.sleep(100000) - last_read = i.next() -end; +wait_replace = true --- ... -test_run:cmd("setopt delimiter ''"); +_ = fiber.create(function() s:replace{1, 1} wait_replace = false end) --- -- true ... -f1 = fiber.create(fill_space) +gen,param,state = s:pairs({1}, {iterator = 'GE'}) --- ... --- Prepared transaction is blocked due to wal_delay. --- Start iterator with vlsn = INT64_MAX -f2 = fiber.create(iterate_in_read_view) +state, value = gen(param, state) --- ... -last_read +value --- -- [1] +- [1, 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 +while wait_replace do fiber.sleep(0.01) end --- ... -f2:wakeup() +state, value = gen(param, state) --- ... -while f2:status() ~= 'dead' do fiber.sleep(0.01) end +value --- +- [2, 0] ... -last_read ---- -- [2] -... -space:drop() ---- -... --- --- Space drop in the middle of dump. --- -test_run:cmd("create server test with script='vinyl/low_quota.lua'") ---- -- true -... -test_run:cmd("start server test with args='1048576'") ---- -- true -... -test_run:cmd('switch test') ---- -- true -... -fiber = require 'fiber' ---- -... -box.cfg{vinyl_timeout = 0.001} ---- -... -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('i1', {parts = {1, 'unsigned'}}) ---- -... -_ = s:create_index('i2', {parts = {2, 'unsigned'}}) ---- -... -_ = s:insert{1, 1} ---- -... --- Delay dump so that we can manage to drop the space --- while it is still being dumped. -box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', true) ---- -- ok -... --- Before failing on quota timeout, the following fiber --- will trigger dump due to memory shortage. -_ = fiber.create(function() s:insert{2, 2, string.rep('x', box.cfg.vinyl_memory)} end) ---- -... --- Let the fiber run. -fiber.sleep(0) ---- -... --- Drop the space while the dump task is still running. -s:drop() ---- -... --- Wait for the dump task to complete. -box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', false) ---- -- ok -... -box.snapshot() ---- -- ok -... --- --- Check that all dump/compact tasks that are in progress at --- the time when the server stops are aborted immediately. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('i1', {parts = {1, 'unsigned'}}) ---- -... -_ = s:create_index('i2', {parts = {2, 'unsigned'}}) ---- -... -box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01) ---- -- ok -... -for i = 1, 1000 do s:replace{i, i} end ---- -... -_ = fiber.create(function() box.snapshot() end) ---- -... -fiber.sleep(0.01) ---- -... -test_run:cmd('switch default') ---- -- true -... -t1 = fiber.time() ---- -... -test_run:cmd("stop server test") ---- -- true -... -t2 = fiber.time() ---- -... -t2 - t1 < 1 ---- -- true -... -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'}) ---- -... -_ = s:create_index('i1', {parts = {1, 'unsigned'}}) ---- -... -c = 10 ---- -... -errinj.set("ERRINJ_WAL_WRITE_DISK", true) ---- -- ok -... -for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end ---- -... -while c ~= 0 do fiber.sleep(0.001) end ---- -... -s:select{} ---- -- [] -... -errinj.set("ERRINJ_WAL_WRITE_DISK", false) ---- -- ok -... -s:drop() ---- -... -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('i1', {parts = {1, 'unsigned'}}) ---- -... -for i = 0, 9 do s:replace({i, i + 1}) end ---- -... -box.snapshot() ---- -- ok -... -errinj.set("ERRINJ_XLOG_GARBAGE", true) ---- -- ok -... -s:select() ---- -- error: tx checksum mismatch -... -errinj.set("ERRINJ_XLOG_GARBAGE", false) ---- -- ok -... -errinj.set("ERRINJ_VYRUN_DATA_READ", true) ---- -- ok -... -s:select() ---- -- error: failed to read from file -... -errinj.set("ERRINJ_VYRUN_DATA_READ", false) ---- -- ok -... -s:select() ---- -- - [0, 1] - - [1, 2] - - [2, 3] - - [3, 4] - - [4, 5] - - [5, 6] - - [6, 7] - - [7, 8] - - [8, 9] - - [9, 10] -... -s:drop() ---- -... -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('i1', {parts = {1, 'unsigned'}}) ---- -... -for i = 0, 9 do s:replace({i, i + 1}) end ---- -... -errinj.set("ERRINJ_XLOG_GARBAGE", true) ---- -- ok -... -box.snapshot() ---- -- error: tx checksum mismatch -... -for i = 10, 19 do s:replace({i, i + 1}) end ---- -... -errinj.set("ERRINJ_XLOG_GARBAGE", false) ---- -- ok -... -box.snapshot() ---- -- ok -... -s:select() ---- -- - [0, 1] - - [1, 2] - - [2, 3] - - [3, 4] - - [4, 5] - - [5, 6] - - [6, 7] - - [7, 8] - - [8, 9] - - [9, 10] - - [10, 11] - - [11, 12] - - [12, 13] - - [13, 14] - - [14, 15] - - [15, 16] - - [16, 17] - - [17, 18] - - [18, 19] - - [19, 20] -... -s:drop() ---- -... --- Point select from secondary index during snapshot. --- Once upon time that leaded to crash. -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -i1 = s:create_index('pk', {parts = {1, 'uint'}, bloom_fpr = 0.5}) ---- -... -i2 = s:create_index('sk', {parts = {2, 'uint'}, bloom_fpr = 0.5}) ---- -... -for i = 1,10 do s:replace{i, i, 0} end ---- -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -function worker() - for i = 11,20,2 do - s:upsert({i, i}, {{'=', 3, 1}}) - errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true) - i1:select{i} - s:upsert({i + 1 ,i + 1}, {{'=', 3, 1}}) - errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true) - i2:select{i + 1} - end -end -test_run:cmd("setopt delimiter ''"); ---- -... -f = fiber.create(worker) ---- -... -while f:status() ~= 'dead' do box.snapshot() fiber.sleep(0.01) end ---- -... -errinj.set("ERRINJ_VY_POINT_ITER_WAIT", false) ---- -- ok -... -s:drop() ---- -... --- vinyl: vy_cache_add: Assertion `0' failed --- https://github.com/tarantool/tarantool/issues/2685 -s = box.schema.create_space('test', {engine = 'vinyl'}) ---- -... -pk = s:create_index('pk') ---- -... -s:replace{2, 0} ---- -- [2, 0] -... -box.snapshot() ---- -- ok -... -s:replace{1, 0} ---- -- [1, 0] -... -box.snapshot() ---- -- ok -... -s:replace{0, 0} ---- -- [0, 0] -... -s:select{0} ---- -- - [0, 0] -... -errinj.set("ERRINJ_WAL_DELAY", true) ---- -- ok -... -wait_replace = true ---- -... -_ = fiber.create(function() s:replace{1, 1} wait_replace = false end) ---- -... -gen,param,state = s:pairs({1}, {iterator = 'GE'}) ---- -... -state, value = gen(param, state) ---- -... -value ---- -- [1, 1] -... -errinj.set("ERRINJ_WAL_DELAY", false) ---- -- ok -... -while wait_replace do fiber.sleep(0.01) end ---- -... -state, value = gen(param, state) ---- -... -value ---- -- [2, 0] -... -s:drop() ---- -... --- --- gh-2442: secondary index cursor must skip key update, made --- after the secondary index scan, but before a primary index --- lookup. It is ok, and the test checks this. --- -s = box.schema.create_space('test', {engine = 'vinyl'}) ---- -... -pk = s:create_index('pk') ---- -... -sk = s:create_index('sk', {parts = {{2, 'unsigned'}}}) ---- -... -s:replace{1, 1} ---- -- [1, 1] -... -s:replace{3, 3} ---- -- [3, 3] -... -box.snapshot() ---- -- ok -... -ret = nil ---- -... -function do_read() ret = sk:select({2}, {iterator = 'GE'}) end ---- -... -errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", true) ---- -- ok -... -f = fiber.create(do_read) ---- -... -f:status() ---- -- suspended -... -ret ---- -- null -... -s:replace{2, 2} ---- -- [2, 2] -... -errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", false) ---- -- ok -... -while ret == nil do fiber.sleep(0.01) end ---- -... -ret ---- -- - [3, 3] -... -s:drop() ---- -... --- --- gh-3412 - assertion failure at exit in case: --- * there is a fiber waiting for quota --- * there is a pending vylog write --- -test_run:cmd("create server low_quota with script='vinyl/low_quota.lua'") ---- -- true -... -test_run:cmd("start server low_quota with args='1048576'") ---- -- true -... -test_run:cmd('switch low_quota') ---- -- true -... -_ = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = box.space.test:create_index('pk') ---- -... -box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01) ---- -- ok -... -fiber = require('fiber') ---- -... -pad = string.rep('x', 100 * 1024) ---- -... -_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end) ---- -... -repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len() ---- -... -test_run:cmd("restart server low_quota with args='1048576'") -box.error.injection.set('ERRINJ_VY_LOG_FLUSH_DELAY', true) ---- -- ok -... -fiber = require('fiber') ---- -... -pad = string.rep('x', 100 * 1024) ---- -... -_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end) ---- -... -repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len() ---- -... -test_run:cmd('switch default') ---- -- true -... -test_run:cmd("stop server low_quota") ---- -- true -... -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. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk', {run_count_per_level = 1}) ---- -... --- Create a run file. -_ = s:replace{1} ---- -... -box.snapshot() ---- -- ok -... --- Create another run file. This will trigger compaction --- as run_count_per_level is set to 1. Due to the error --- injection compaction will finish before snapshot. -_ = s:replace{2} ---- -... -errinj.set('ERRINJ_SNAP_COMMIT_DELAY', true) ---- -- ok -... -c = fiber.channel(1) ---- -... -_ = fiber.create(function() box.snapshot() c:put(true) end) ---- -... -while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end ---- -... -errinj.set('ERRINJ_SNAP_COMMIT_DELAY', false) ---- -- ok -... -c:get() ---- -- true -... --- Check that all files corresponding to the last checkpoint --- are present. -files = box.backup.start() ---- -... -missing = {} ---- -... -for _, f in pairs(files) do if not fio.path.exists(f) then table.insert(missing, f) end end ---- -... -missing ---- -- [] -... -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} ---- -- [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() +s:drop() --- ... -- --- Check that tarantool doesn't hang or crash if error --- occurs while writing a deferred DELETE to WAL. +-- gh-2442: secondary index cursor must skip key update, made +-- after the secondary index scan, but before a primary index +-- lookup. It is ok, and the test checks this. -- -fiber = require('fiber') ---- -... -errinj = box.error.injection ---- -... -s = box.schema.space.create('test', {engine = 'vinyl'}) +s = box.schema.create_space('test', {engine = 'vinyl'}) --- ... -_ = s:create_index('pk', {run_count_per_level = 10}) +pk = s:create_index('pk') --- ... -_ = s:create_index('sk', {unique = false, parts = {2, 'unsigned'}}) +sk = s:create_index('sk', {parts = {{2, 'unsigned'}}}) --- ... -s:replace{1, 10} +s:replace{1, 1} --- -- [1, 10] +- [1, 1] ... -s:replace{10, 100} -- to prevent last-level compaction (gh-3657) +s:replace{3, 3} --- -- [10, 100] +- [3, 3] ... box.snapshot() --- - ok ... -s:replace{1, 20} ---- -- [1, 20] -... -box.snapshot() +ret = nil --- -- ok ... -errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.001) +function do_read() ret = sk:select({2}, {iterator = 'GE'}) end --- -- ok ... -errinj.set("ERRINJ_WAL_IO", true) +errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", true) --- - ok ... -errors = box.stat.ERROR.total +f = fiber.create(do_read) --- ... -s.index.pk:compact() +f:status() --- +- suspended ... -while box.stat.ERROR.total - errors == 0 do fiber.sleep(0.001) end +ret --- +- null ... -s.index.pk:stat().disk.compact.count -- 0 +s:replace{2, 2} --- -- 0 +- [2, 2] ... -errinj.set("ERRINJ_WAL_IO", false) +errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", false) --- - ok ... -while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end ---- -... -s.index.pk:stat().disk.compact.count -- 1 ---- -- 1 -... -errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0) +while ret == nil do fiber.sleep(0.01) end --- -- ok ... -box.snapshot() -- ok +ret --- -- ok +- - [3, 3] ... s:drop() --- ... -- --- gh-3458: check that rw transactions that started before DDL are --- aborted. +-- gh-3412 - assertion failure at exit in case: +-- * there is a fiber waiting for quota +-- * there is a pending vylog write -- -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 ';'") +test_run:cmd("create server low_quota with script='vinyl/low_quota.lua'") --- - 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 ''"); +test_run:cmd("start server low_quota with args='1048576'") --- - true ... -c1 = async_replace(s1, {1}, 0.01) ---- -... -c2 = async_replace(s2, {1}, 0.01) +test_run:cmd('switch low_quota') --- +- true ... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +_ = box.schema.space.create('test', {engine = 'vinyl'}) --- -- ok ... -s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}} +_ = box.space.test:create_index('pk') --- ... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01) --- - ok ... -c1:get() -- false (transaction was aborted) ---- -- false -... -c2:get() -- true ---- -- true -... -s1:get(1) == nil ---- -- true -... -s2:get(1) ~= nil ---- -- true -... -s1:format() +fiber = require('fiber') --- -- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'unsigned'}] ... -s1:format{} +pad = string.rep('x', 100 * 1024) --- ... -c1 = async_replace(s1, {2}, 0.01) +_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end) --- ... -c2 = async_replace(s2, {2}, 0.01) +repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len() --- ... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) +test_run:cmd("restart server low_quota with args='1048576'") +box.error.injection.set('ERRINJ_VY_LOG_FLUSH_DELAY', true) --- - ok ... -_ = s1:create_index('sk', {parts = {2, 'unsigned'}}) +fiber = require('fiber') --- ... -errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) +pad = string.rep('x', 100 * 1024) --- -- ok ... -c1:get() -- false (transaction was aborted) +_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end) --- -- false ... -c2:get() -- true +repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len() --- -- true ... -s1:get(2) == nil +test_run:cmd('switch default') --- - true ... -s2:get(2) ~= nil +test_run:cmd("stop server low_quota") --- - true ... -s1.index.pk:count() == s1.index.sk:count() +test_run:cmd("cleanup server low_quota") --- - true ... -s1:drop() ---- -... -s2:drop() ---- -... -box.cfg{vinyl_cache = vinyl_cache} +-- +-- gh-3437: if compaction races with checkpointing, it may remove +-- files needed for backup. +-- +s = box.schema.space.create('test', {engine = 'vinyl'}) --- ... --- Transactions that reached WAL must not be aborted. -s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk', {run_count_per_level = 1}) --- ... -_ = s:create_index('pk') +-- Create a run file. +_ = s:replace{1} --- ... -errinj.set('ERRINJ_WAL_DELAY', true) +box.snapshot() --- - ok ... -_ = fiber.create(function() s:replace{1} end) +-- Create another run file. This will trigger compaction +-- as run_count_per_level is set to 1. Due to the error +-- injection compaction will finish before snapshot. +_ = s:replace{2} --- ... -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) +errinj.set('ERRINJ_SNAP_COMMIT_DELAY', true) --- +- ok ... -fiber.sleep(0) +c = fiber.channel(1) --- ... -s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail +_ = fiber.create(function() box.snapshot() c:put(true) end) --- -- error: Tuple field 2 required by space format is missing ... -s:select() +while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end --- -- - [1] ... -s:truncate() +errinj.set('ERRINJ_SNAP_COMMIT_DELAY', false) --- +- ok ... -errinj.set('ERRINJ_WAL_DELAY', true) +c:get() --- -- ok +- true ... -_ = fiber.create(function() s:replace{1} end) +-- Check that all files corresponding to the last checkpoint +-- are present. +files = box.backup.start() --- ... -_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end) +missing = {} --- ... -fiber.sleep(0) +for _, f in pairs(files) do if not fio.path.exists(f) then table.insert(missing, f) end end --- ... -s:create_index('sk', {parts = {2, 'unsigned'}}) +missing --- -- error: Tuple field 2 required by space format is missing +- [] ... -s:select() +box.backup.stop() --- -- - [1] ... s:drop() --- ... -- --- Check disk.compact.queue stat. +-- Check that tarantool doesn't hang or crash if error +-- occurs while writing a deferred DELETE to WAL. -- -test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'") +fiber = require('fiber') --- -- true ... -s = box.schema.space.create('test', {engine = 'vinyl'}) +errinj = box.error.injection --- ... -i = s:create_index('pk', {run_count_per_level = 2}) +s = box.schema.space.create('test', {engine = 'vinyl'}) --- ... -function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end +_ = s:create_index('pk', {run_count_per_level = 10}) --- ... -dump() +_ = s:create_index('sk', {unique = false, parts = {2, 'unsigned'}}) --- ... -i:stat().disk.compact.queue -- none +s:replace{1, 10} --- -- bytes_compressed: <bytes_compressed> - pages: 0 - rows: 0 - bytes: 0 +- [1, 10] ... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +s:replace{10, 100} -- to prevent last-level compaction (gh-3657) --- -- true +- [10, 100] ... -errinj.set('ERRINJ_VY_COMPACTION_DELAY', true) +box.snapshot() --- - ok ... -dump() ---- -... -dump() ---- -... -i:stat().disk.compact.queue -- 30 statements +s:replace{1, 20} --- -- bytes_compressed: <bytes_compressed> - pages: 3 - rows: 30 - bytes: 471 +- [1, 20] ... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +box.snapshot() --- -- true +- ok ... -dump() +errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.001) --- +- ok ... -i:stat().disk.compact.queue -- 40 statements +errinj.set("ERRINJ_WAL_IO", true) --- -- bytes_compressed: <bytes_compressed> - pages: 4 - rows: 40 - bytes: 628 +- ok ... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +errors = box.stat.ERROR.total --- -- true ... -dump() +s.index.pk:compact() --- ... -i:stat().disk.compact.queue -- 50 statements +while box.stat.ERROR.total - errors == 0 do fiber.sleep(0.001) end --- -- bytes_compressed: <bytes_compressed> - pages: 5 - rows: 50 - bytes: 785 ... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +s.index.pk:stat().disk.compact.count -- 0 --- -- true +- 0 ... -box.stat.reset() -- doesn't affect queue size +errinj.set("ERRINJ_WAL_IO", false) --- +- ok ... -i:stat().disk.compact.queue -- 50 statements +while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end --- -- bytes_compressed: <bytes_compressed> - pages: 5 - rows: 50 - bytes: 785 ... -i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue +s.index.pk:stat().disk.compact.count -- 1 --- -- true +- 1 ... -errinj.set('ERRINJ_VY_COMPACTION_DELAY', false) +errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0) --- - ok ... -while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end ---- -... -i:stat().disk.compact.queue -- none +box.snapshot() -- ok --- -- bytes_compressed: <bytes_compressed> - pages: 0 - rows: 0 - bytes: 0 +- ok ... 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 59e9f81c6d9327d99b302c59d414003be2db073b..d4590fb420211ceb9c7b82c34171e270c74393c9 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'}}) @@ -540,41 +332,6 @@ test_run:cmd('switch default') 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. @@ -603,140 +360,6 @@ missing 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. @@ -767,123 +390,6 @@ errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0) 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: <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 0000000000000000000000000000000000000000..4f5d59cdc8f4d10982c4b919a372690058262735 --- /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 0000000000000000000000000000000000000000..0948bc3d34ca929110d00c5cb41f45eb5b982ff2 --- /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 0000000000000000000000000000000000000000..fac56cee25fed3d9f7529b7d6dd786ae57aa191e --- /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: <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: <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: <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: <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: <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: <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: <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 0000000000000000000000000000000000000000..1e0e63aef9dcdec6d96209745072290ebe1c8018 --- /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: <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 0000000000000000000000000000000000000000..a7583b2e22ac3beda60ae0b8f152873fbd52d21e --- /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 0000000000000000000000000000000000000000..cf83b32625d14feab529a6186cc6cf624c7dd20d --- /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 9ced03df131daaf8c041f6de9ddbe64dc3eb708e..0e3b79c4a00643c848656f4e70e2ccbadf7b3dac 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 c1fd517d3307a38ecc0775747a056e2fb398cab9..ce9e12e5d9a169b98286f0d424fe0f662cea11fd 100644 --- a/test/vinyl/errinj_vylog.test.lua +++ b/test/vinyl/errinj_vylog.test.lua @@ -1,6 +1,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 ace53e7f1f5fc99a3136117bc41820c71ec2115e..d2a194d85997c7a071e5d4f050be1cdc8504b4ec 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