diff --git a/mysql-test/suite/innodb/r/bug_release_locks.result b/mysql-test/suite/innodb/r/bug_release_locks.result new file mode 100644 index 00000000000..c97c92ac2db --- /dev/null +++ b/mysql-test/suite/innodb/r/bug_release_locks.result @@ -0,0 +1,66 @@ +CREATE TABLE t0 (id INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t1 (id INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t2 (id INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (id INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t4 (id INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t5 (id INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t0 (id) VALUES (1); +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +XA START 'x'; +INSERT INTO t1 (id) VALUES (1);; +INSERT INTO t2 (id) VALUES (1);; +INSERT INTO t3 (id) VALUES (1);; +INSERT INTO t4 (id) VALUES (1);; +INSERT INTO t5 (id) VALUES (1);; +SELECT * FROM t0 WHERE id=1 FOR UPDATE; +id +1 +XA END 'x'; +SET DEBUG='+d,simulate_release_many_locks'; +SET DEBUG_SYNC='try_relatch_trx_and_shard_and_do_noted_expected_version + SIGNAL c0_noted_expected_version + WAIT_FOR c0_can_go + EXECUTE 5'; +XA PREPARE 'x'; +BEGIN; +SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; +SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go'; +SELECT * FROM t1 FOR SHARE; +BEGIN; +SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; +SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go'; +SELECT * FROM t2 FOR SHARE; +BEGIN; +SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; +SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go'; +SELECT * FROM t3 FOR SHARE; +BEGIN; +SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; +SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go'; +SELECT * FROM t4 FOR SHARE; +BEGIN; +SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; +SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go'; +SELECT * FROM t5 FOR SHARE; +XA COMMIT 'x'; +id +1 +COMMIT; +id +1 +COMMIT; +id +1 +COMMIT; +id +1 +COMMIT; +id +1 +COMMIT; +DROP TABLE t0; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +DROP TABLE t4; +DROP TABLE t5; diff --git a/mysql-test/suite/innodb/t/bug_release_locks.test b/mysql-test/suite/innodb/t/bug_release_locks.test new file mode 100644 index 00000000000..960e507dfa1 --- /dev/null +++ b/mysql-test/suite/innodb/t/bug_release_locks.test @@ -0,0 +1,69 @@ +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +# keep in sync with MAX_FAILURES defined in lock_trx_release_read_locks() +--let MAX_FAILURES=5 +--let i=0 +while($i<=$MAX_FAILURES) +{ + --eval CREATE TABLE t$i (id INT PRIMARY KEY) ENGINE=InnoDB + --inc $i +} +INSERT INTO t0 (id) VALUES (1); + +--connect (c0, localhost, root,,) + SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + XA START 'x'; + # create at least MAX_FAILURES implicit locks + --let i=1 + while($i<=$MAX_FAILURES) + { + --eval INSERT INTO t$i (id) VALUES (1); + --inc $i + } + # create at least 1 explicit lock + SELECT * FROM t0 WHERE id=1 FOR UPDATE; + XA END 'x'; + SET DEBUG='+d,simulate_release_many_locks'; + SET DEBUG_SYNC='try_relatch_trx_and_shard_and_do_noted_expected_version + SIGNAL c0_noted_expected_version + WAIT_FOR c0_can_go + EXECUTE 5'; + --send XA PREPARE 'x' + +--let i=1 +while($i<=$MAX_FAILURES) +{ + --connect (c$i, localhost, root,,) + BEGIN; + SET DEBUG_SYNC = 'now WAIT_FOR c0_noted_expected_version'; + --eval SET DEBUG_SYNC='lock_wait_will_wait SIGNAL c0_can_go' + --send_eval SELECT * FROM t$i FOR SHARE + + --inc $i +} + +--connection c0 + --reap + XA COMMIT 'x'; + +--disconnect c0 +--let i=1 +while($i<=$MAX_FAILURES) +{ + --connection c$i + --reap + COMMIT; + --connection default + --disconnect c$i + --inc $i +} + +--let i=0 +while($i<=$MAX_FAILURES) +{ + --eval DROP TABLE t$i + --inc $i +} + +--source include/wait_until_count_sessions.inc diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 0efa1f4e139..aab1f753ee8 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4081,9 +4081,10 @@ exclusive mode, which is a bit too expensive to do by default. It is called during XA prepare to release locks early. @param[in,out] trx transaction @param[in] only_gap release only GAP locks +@param[in] ignore_timeout ignore timeout @return true if and only if it succeeded to do the job*/ -[[nodiscard]] static bool try_release_read_locks_in_x_mode(trx_t *trx, - bool only_gap) { +[[nodiscard]] static bool try_release_read_locks_in_x_mode( + trx_t *trx, bool only_gap, bool ignore_timeout) { ut_ad(!trx_mutex_own(trx)); /* We will iterate over locks from various shards. */ Global_exclusive_latch_guard guard{UT_LOCATION_HERE}; @@ -4091,13 +4092,16 @@ It is called during XA prepare to release locks early. trx_mutex_enter_first_of_two(trx); for (auto lock : trx->lock.trx_locks.removable()) { - if (MAX_CS_DURATION < std::chrono::steady_clock::now() - started_at) { + if (!ignore_timeout && + MAX_CS_DURATION < std::chrono::steady_clock::now() - started_at) { trx_mutex_exit(trx); return false; } DEBUG_SYNC_C("lock_trx_release_read_locks_in_x_mode_will_release"); lock_release_read_lock(lock, only_gap); + + DBUG_EXECUTE_IF("simulate_release_many_locks", sleep(1);); } trx_mutex_exit(trx); @@ -4117,7 +4121,11 @@ void lock_trx_release_read_locks(trx_t *trx, bool only_gap) { std::this_thread::yield(); } - while (!locksys::try_release_read_locks_in_x_mode(trx, only_gap)) { + for (size_t failures = 0;; ++failures) { + if (locksys::try_release_read_locks_in_x_mode(trx, only_gap, + failures >= MAX_FAILURES)) { + return; + } std::this_thread::yield(); } }