polardbxengine/mysql-test/suite/innodb/t/undetected_deadlock.test

197 lines
6.5 KiB
Plaintext

--source include/have_debug_sync.inc
--echo #
--echo # Bug #29882690 UNDETECTED DEADLOCK WITH GAP AND INSERT INTENTION LOCKS
--echo # IS POSSIBLE
--echo #
# Save the original settings, to be restored at the end of test
SET @innodb_lock_wait_timeout_saved = @@global.innodb_lock_wait_timeout;
# Make sure that transactions will not finish prematurely
SET @@global.innodb_lock_wait_timeout = 20;
# This is just to identify transactions easily as con1,con2,con3 in show_locks()
CREATE TABLE t0 (id INT NOT NULL PRIMARY KEY);
INSERT INTO t0 VALUES (1), (2), (3);
DELIMITER //;
CREATE PROCEDURE show_locks ()
BEGIN
SELECT engine_transaction_id INTO @con1
FROM performance_schema.data_locks
WHERE object_name = 't0' AND lock_data = '1';
SELECT engine_transaction_id INTO @con2
FROM performance_schema.data_locks
WHERE object_name = 't0' AND lock_data = '2';
SELECT engine_transaction_id INTO @con3
FROM performance_schema.data_locks
WHERE object_name = 't0' AND lock_data = '3';
SELECT
CASE engine_transaction_id
WHEN @con1 THEN "con1"
WHEN @con2 THEN "con2"
WHEN @con3 THEN "con3"
ELSE "unknown"
END connection,index_name,lock_type,lock_mode,lock_status,lock_data
FROM performance_schema.data_locks
WHERE object_name = 't1'
ORDER BY 1,2,3,4,5,6;
END //
DELIMITER ;//
# Scenario for a deadlock which can be undetected if we move granted gap lock
# in front of waiting insert intention lock.
# Initially we have (1,"a"),(3,"c"),(4,"d")
# 1. con1 obtains a GRANTED LOCK_GAP on gap before row id=3
# 2. con2 obtains a GRANTED LOCK_X on row id=4
# (exact type, and row is not very important)
# 3. con2 enqueues a waiting LOCK_INSERT_INTENTION into the gap before id=3
# (and is blocked by con1)
# 4. con3 obtains a GRANTED LOCK_GAP on gap before row id=3
# (gap locks don't wait for II)
# 5. con3 enqueues a waiting lock on same row as in step 2.
# (we have: con3 `waits-for` con2 `waits-for` con1 - no deadlock so far)
# 6. con1 resizes the row with id=3 ("c"->"cccccccccccccccc") causing locks
# to be moved from one `heap_no` to another
# 7. it depends on implementation of move opeartion, if con3's LOCK_GAP lands in
# front of con2's waiting II or behind
# 8. con1 commits
# 9. if con3's LOCK_GAP is behind con2's II, then
# con2's II becomes granted.
# con2 re-attempts to insert.
# con2 notices the con3's gap lock.
# con2 decides to wait.
# This creates a deadlock cycle, con2 has to wait-for con3, but con3 is
# already waiting for con2.
# if we perform deadlock detection from requesting thread then
# con2 notices the deadlock immediately,
# otherwise, if we perform deadlock detection from dedicated thread then
# we notice the deadlock after a while.
# In either case, deadlock is resolved.
# else:
# We have a deadlock cycle, as con2's II lock is behind con3's GAP lock,
# so con2 waits-for con3, and con3 waits for con2 already, but this cycle
# can go undetected unless we have a dedicated deadlock detector thread.
# When con1 lock is released, con2 sees a con3's GAP lock in front of it,
# thus con2's II is not granted, so it continues to be waiting.
# Both con2 and con3 wait (and finally someone timesout).
--let force_CATS=0
while($force_CATS <= 1)
{
CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY, val VARCHAR(200));
INSERT INTO t1 (id,val) VALUES (1, "a"), (3, "c"), (4, "d");
# These SELECTS are just to identify transactions easily as con1,con2,con3 in
# show_locks() by checking which row is locked by which.
--connect (con1, localhost, root,,)
BEGIN;
SELECT * FROM t0 WHERE id=1 FOR UPDATE;
--connect (con2, localhost, root,,)
BEGIN;
SELECT * FROM t0 WHERE id=2 FOR UPDATE;
--connect (con3, localhost, root,,)
BEGIN;
SELECT * FROM t0 WHERE id=3 FOR UPDATE;
--echo # 1. con1 obtains a GRANTED LOCK_GAP on gap before row id=3
--connection con1
SELECT * FROM t1 WHERE id=2 FOR UPDATE;
--connection default
CALL show_locks();
--echo # 2. con2 obtains a GRANTED LOCK_X on row id=4
--connection con2
SELECT * FROM t1 WHERE id=4 FOR UPDATE;
--connection default
CALL show_locks();
--echo # 3. con2 enqueues a waiting LOCK_INSERT_INTENTION into the gap
--echo # before id=3
--connection con2
SET DEBUG_SYNC = 'lock_wait_will_wait SIGNAL con2_will_wait';
--send INSERT INTO t1 (id, val) VALUES (2, "b")
--connection default
SET DEBUG_SYNC = 'now WAIT_FOR con2_will_wait';
CALL show_locks();
--echo # 4. con3 obtains a GRANTED LOCK_GAP on gap before row id=3
--connection con3
SELECT * FROM t1 WHERE id=2 FOR UPDATE;
--connection default
CALL show_locks();
--echo # 5. con3 enqueues a waiting lock on same row as in step 2.
--connection con3
SET DEBUG_SYNC = 'lock_wait_will_wait SIGNAL con3_will_wait';
--send SELECT * FROM t1 WHERE id=4 FOR UPDATE
--connection default
SET DEBUG_SYNC = 'now WAIT_FOR con3_will_wait';
CALL show_locks();
if($force_CATS == 1)
{
--echo # We enable CATS algorithm, to force placing GRANTED locks at the
--echo # front of queue
--source suite/innodb/include/force_cats.inc
}
--echo # 6. con1 resizes the row with id=3 ("c"->"cccccccccccccccc") causing
--echo # locks to be moved
--connection con1
UPDATE t1 SET val="cccccccccccccccc" WHERE id=3;
--echo # 7. it depends on implementation of con3's LOCK_GAP lands is in front
--echo # of con2's waiting II or not
--echo # 8. con1 commits
COMMIT;
# At this point there should be no waiters at all, as the deadlock should be
# noticed and resolved - either when re-acquiring II lock, on in dedicated
# deadlock detector thread.
# Yet on a faulty implementation, we will see an unresolved deadlock:
# two threads, each with a lock WAITING for another transaction.
# Eventually transactions will timeout, and we will see ER_LOCK_WAIT_TIMEOUT
# as opposed to expected: success, or ER_LOCK_DEADLOCK.
--connection con2
--reap
ROLLBACK;
--connection con3
--error ER_LOCK_DEADLOCK
--reap
ROLLBACK;
if($force_CATS == 1)
{
--echo # Turn of CATS
--source suite/innodb/include/discourage_cats.inc
}
--connection default
--disconnect con1
--disconnect con2
--disconnect con3
DROP TABLE t1;
--inc $force_CATS
}
DROP PROCEDURE show_locks;
DROP TABLE t0;
# Restore saved settings
SET @@global.innodb_lock_wait_timeout = @innodb_lock_wait_timeout_saved ;