diff --git a/data_substrate b/data_substrate index 137a3e1d9d7..3be56b82581 160000 --- a/data_substrate +++ b/data_substrate @@ -1 +1 @@ -Subproject commit 137a3e1d9d7a4aa5ae51c088adc59cfe8680d001 +Subproject commit 3be56b825812b9c4183614eff8b01acf87e0a26e diff --git a/storage/eloq/mysql-test/mono_basic/r/range_read_block_on_write_lock.result b/storage/eloq/mysql-test/mono_basic/r/range_read_block_on_write_lock.result new file mode 100644 index 00000000000..c5404e1c5c3 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/r/range_read_block_on_write_lock.result @@ -0,0 +1,59 @@ +set @@default_storage_engine= eloq; +DROP TABLE IF EXISTS t1; +# Test: Range reads SHOULD block when range split holds write lock +# When range split holds write lock (Phase 2-3), reads should block +# because write locks are exclusive and incompatible with read locks. +create table t1 (c0 int auto_increment, c1 char(250), c2 char(250), c3 char(250), c4 int, primary key(c0)); +insert into t1 values (0,'initial', 'data', 'row', 1); +SET SESSION debug_dbug="+d,eloq;term_SplitFlushOp_CommitAcquireAllWriteOp_Continue;node_id=-1"; +SET SESSION debug_dbug="+d,eloq;at_once;node_id=-1;action=NOTIFY_CHECKPOINTER"; +connect conn_read1,localhost,root,,test,$MONO1_PORT; +connect conn_read2,localhost,root,,test,$MONO1_PORT; +connect conn_read3,localhost,root,,test,$MONO1_PORT; +# Test Case 1: Single read should BLOCK (not complete immediately) +connection conn_read1; +begin; +select count(*) from t1;; +connection default; +# Test Case 2: Multiple concurrent reads should all BLOCK +connection conn_read2; +begin; +select count(*) from t1 where c4 = 1;; +connection conn_read3; +begin; +select c0, c1, c2 from t1 order by c0;; +connection default; +# Printing processlist before verification: +# Test Case 3: Complete the range split (releases write lock) +SET SESSION debug_dbug="-d,eloq;term_SplitFlushOp_CommitAcquireAllWriteOp_Continue;node_id=-1"; +# Test Case 4: Verify blocked reads complete after write lock is released +connection conn_read1; +count(*) +2048 +select count(*) from t1; +count(*) +2048 +commit; +connection conn_read2; +count(*) +2048 +select count(*) from t1 where c4 = 1; +count(*) +2048 +commit; +connection conn_read3; +select c0, c1, c2 from t1; +commit; +# Test Case 5: Verify no queries are still blocked +connection default; +# Printing processlist before verification: +# Test Case 6: Verify reads completed successfully and range split completed +connection conn_read1; +select count(*) from t1; +count(*) +2048 +disconnect conn_read1; +disconnect conn_read2; +disconnect conn_read3; +connection default; +drop table t1; diff --git a/storage/eloq/mysql-test/mono_basic/r/range_split_deadlock_abort.result b/storage/eloq/mysql-test/mono_basic/r/range_split_deadlock_abort.result new file mode 100644 index 00000000000..3663f0f3091 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/r/range_split_deadlock_abort.result @@ -0,0 +1,26 @@ +set @@default_storage_engine= eloq; +DROP TABLE IF EXISTS t1; +# Test: Range split SHOULD downgrade lock and retry at phase 8 when deadlock detected +# When range split is at phase 8 and deadlock is detected, it should +# downgrade write intent lock and retry phase 8 instead of fully aborting. +# +# This test uses the split_flush_commit_acquire_all_deadlock fault injector +# to simulate a deadlock at phase 8, which triggers the downgrade and retry logic. +create table t1 (c0 int auto_increment, c1 char(250), c2 char(250), c3 char(250), c4 int, primary key(c0)); +insert into t1 values (0,'initial', 'data', 'row', 1); +SET SESSION debug_dbug="+d,eloq;split_flush_commit_acquire_all_deadlock;node_id=-1"; +# Step 2: Trigger range split with checkpoint +# Range split will progress through phases 1-7, and at phase 8: +# - The fault injector will inject DEAD_LOCK_ABORT error +# - IsDeadlock() will return true +# - Range split will downgrade write intent lock via PostWriteType::DowngradeLock +# - Range split will retry phase 8 after downgrade +SET SESSION debug_dbug="+d,eloq;at_once;node_id=-1;action=NOTIFY_CHECKPOINTER"; +# Step 3: Verify range split completed successfully +connection default; +select count(*) from t1; +count(*) +2048 +# Step 4: Verify no queries are still blocked +connection default; +drop table t1; diff --git a/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.opt b/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.opt new file mode 100644 index 00000000000..a62d1fea474 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.opt @@ -0,0 +1 @@ +--checkpointer_interval=86400 diff --git a/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.test b/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.test new file mode 100644 index 00000000000..bd1139cf391 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/t/range_read_block_on_write_lock.test @@ -0,0 +1,149 @@ +--source include/have_eloq.inc +set @@default_storage_engine= eloq; + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # Test: Range reads SHOULD block when range split holds write lock +--echo # When range split holds write lock (Phase 2-3), reads should block +--echo # because write locks are exclusive and incompatible with read locks. + +create table t1 (c0 int auto_increment, c1 char(250), c2 char(250), c3 char(250), c4 int, primary key(c0)); + +# Insert initial data +insert into t1 values (0,'initial', 'data', 'row', 1); + +# Set debug point to pause range split at prepare phase (Phase 2-3) +# This must be set BEFORE building up data, so when the split is triggered, +# it will pause at the prepare phase where write lock is held +SET SESSION debug_dbug="+d,eloq;term_SplitFlushOp_CommitAcquireAllWriteOp_Continue;node_id=-1"; + +# Build up enough data to trigger range split +# We need to exceed range_max_size (1MB for testing builds, 256MB otherwise) +# Using the same pattern as range_split_write_forward.test: double the data each time +# 11 iterations: 1 -> 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 -> 256 -> 512 -> 1024 -> 2048 rows +# With char(250) columns, this should exceed the threshold +let $counter= 11; +--disable_result_log +--disable_query_log +while($counter) +{ + insert into t1 (c1, c2, c3, c4) select c1, c2, c3, c4 from t1; + dec $counter; +} +--enable_query_log +--enable_result_log + +# Trigger checkpoint to calculate range size and trigger range split if threshold exceeded +# The range split will pause at prepare phase (write lock held) due to the debug point set above +SET SESSION debug_dbug="+d,eloq;at_once;node_id=-1;action=NOTIFY_CHECKPOINTER"; +# Wait longer for checkpoint to process and range split to start and pause +--sleep 5 + +# Create connections: multiple for concurrent reads +--connect (conn_read1,localhost,root,,test,$MONO1_PORT) +--connect (conn_read2,localhost,root,,test,$MONO1_PORT) +--connect (conn_read3,localhost,root,,test,$MONO1_PORT) + +# Wait a bit more to ensure range split has started and paused at prepare phase +--sleep 2 + +--echo # Test Case 1: Single read should BLOCK (not complete immediately) +--connection conn_read1 +begin; +--send select count(*) from t1; + +# Wait a bit to ensure the read is blocked +--sleep 2 + +# Verify the read is blocked (check processlist) +# Note: Blocked queries are identified by state and info columns +# We check for connections that have: +# 1. Lock-related state (state like "%lock%") +# 2. Matching query info (info like "select count(*) from t1%") +# 3. NULL info with time >= 1 (waiting/blocked) +--connection default +let $wait_condition= + select count(*) >= 1 from information_schema.processlist + where (state like "%lock%" or info like "select count(*) from t1%") + and id != CONNECTION_ID(); +--source include/wait_condition.inc + +--echo # Test Case 2: Multiple concurrent reads should all BLOCK +--connection conn_read2 +begin; +--send select count(*) from t1 where c4 = 1; + +--connection conn_read3 +begin; +--send select c0, c1, c2 from t1 order by c0; + +# Wait a bit to ensure reads are blocked +--sleep 10 + +# Verify all reads are blocked +# Note: Blocked queries are identified by state and info columns +# We check for connections that have: +# 1. Lock-related state (state like "%lock%") +# 2. Matching query info (info like "select count(*) from t1%" or "select c0, c1, c2 from t1%") +# 3. NULL info with time >= 1 (waiting/blocked) +--connection default +--echo # Printing processlist before verification: +let $wait_condition= + select count(*) >= 3 from information_schema.processlist + where (state like "%lock%" or + info like "select count(*) from t1%" or + info like "select c0, c1, c2 from t1%") + and id != CONNECTION_ID(); +--source include/wait_condition.inc + +--echo # Test Case 3: Complete the range split (releases write lock) +# Complete the range split by removing the debug point and triggering checkpoint +# This will allow the split to proceed through all phases and release the write lock +SET SESSION debug_dbug="-d,eloq;term_SplitFlushOp_CommitAcquireAllWriteOp_Continue;node_id=-1"; +# SET SESSION debug_dbug="+d,eloq;at_once;node_id=-1;action=NOTIFY_CHECKPOINTER"; +--sleep 5 + +--echo # Test Case 4: Verify blocked reads complete after write lock is released +# All reads should now complete successfully after the range split releases the write lock +--connection conn_read1 +--reap +select count(*) from t1; +commit; + +--connection conn_read2 +--reap +select count(*) from t1 where c4 = 1; +commit; + +--connection conn_read3 +--disable_result_log +--reap +select c0, c1, c2 from t1; +--enable_result_log +commit; + +--echo # Test Case 5: Verify no queries are still blocked +--connection default +# After reaping, queries should be completed +# We verify by checking that there are no blocked queries (identified by state and info columns) +--echo # Printing processlist before verification: +let $wait_condition= + select count(*) = 0 from information_schema.processlist + where info like "select%from t1%" + and id != CONNECTION_ID(); +--source include/wait_condition.inc + +--echo # Test Case 6: Verify reads completed successfully and range split completed +--connection conn_read1 +select count(*) from t1; + +# Cleanup +--disconnect conn_read1 +--disconnect conn_read2 +--disconnect conn_read3 + +--connection default +drop table t1; + diff --git a/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.opt b/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.opt new file mode 100644 index 00000000000..a62d1fea474 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.opt @@ -0,0 +1 @@ +--checkpointer_interval=86400 diff --git a/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.test b/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.test new file mode 100644 index 00000000000..8820d334c07 --- /dev/null +++ b/storage/eloq/mysql-test/mono_basic/t/range_split_deadlock_abort.test @@ -0,0 +1,72 @@ +--source include/have_eloq.inc +set @@default_storage_engine= eloq; + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # Test: Range split SHOULD downgrade lock and retry at phase 8 when deadlock detected +--echo # When range split is at phase 8 and deadlock is detected, it should +--echo # downgrade write intent lock and retry phase 8 instead of fully aborting. +--echo # +--echo # This test uses the split_flush_commit_acquire_all_deadlock fault injector +--echo # to simulate a deadlock at phase 8, which triggers the downgrade and retry logic. + +create table t1 (c0 int auto_increment, c1 char(250), c2 char(250), c3 char(250), c4 int, primary key(c0)); + +# Insert initial data +insert into t1 values (0,'initial', 'data', 'row', 1); + +# Step 1: Enable fault injector to simulate deadlock at phase 8 +# This fault injector will inject a DEAD_LOCK_ABORT error when range split +# reaches phase 8 (Commit Acquire All Write Lock), which will trigger the +# downgrade and retry logic +SET SESSION debug_dbug="+d,eloq;split_flush_commit_acquire_all_deadlock;node_id=-1"; + +# Build up enough data in t1 to trigger range split +# We need to exceed range_max_size (1MB for testing builds, 256MB otherwise) +# Using the same pattern as range_read_block_on_write_lock.test: double the data each time +# 11 iterations: 1 -> 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 -> 256 -> 512 -> 1024 -> 2048 rows +# With char(250) columns, this should exceed the threshold +let $counter= 11; +--disable_result_log +--disable_query_log +while($counter) +{ + insert into t1 select * from t1; + dec $counter; +} +--enable_query_log +--enable_result_log + +--echo # Step 2: Trigger range split with checkpoint +--echo # Range split will progress through phases 1-7, and at phase 8: +--echo # - The fault injector will inject DEAD_LOCK_ABORT error +--echo # - IsDeadlock() will return true +--echo # - Range split will downgrade write intent lock via PostWriteType::DowngradeLock +--echo # - Range split will retry phase 8 after downgrade +SET SESSION debug_dbug="+d,eloq;at_once;node_id=-1;action=NOTIFY_CHECKPOINTER"; + +# Wait for checkpoint to process and range split to start +--sleep 5 + +# Wait for range split to reach phase 8, trigger fault injector, downgrade, and retry +--sleep 10 + +--echo # Step 3: Verify range split completed successfully +--connection default +# Verify range split completed by checking that data is accessible +# If range split failed, we would see errors or the split wouldn't complete +select count(*) from t1; + +--echo # Step 4: Verify no queries are still blocked +--connection default +# After range split completes, there should be no blocked queries +let $wait_condition= + select count(*) = 0 from information_schema.processlist + where info like "select%from t1%" + and id != CONNECTION_ID(); +--source include/wait_condition.inc + +# Cleanup +drop table t1;