197 lines
6.5 KiB
Plaintext
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 ; |