summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/suite/galera/r/galera_FK_duplicate_client_insert.result40
-rw-r--r--mysql-test/suite/galera/t/galera_FK_duplicate_client_insert.test4
-rw-r--r--mysql-test/suite/innodb/r/cursor-restore-locking.result4
-rw-r--r--mysql-test/suite/innodb/r/deadlock_victim_race.result42
-rw-r--r--mysql-test/suite/innodb/r/update-cascade.result8
-rw-r--r--mysql-test/suite/innodb/t/cursor-restore-locking.test6
-rw-r--r--mysql-test/suite/innodb/t/deadlock_victim_race.test102
-rw-r--r--mysql-test/suite/innodb/t/update-cascade.test8
-rw-r--r--storage/innobase/dict/drop.cc3
-rw-r--r--storage/innobase/include/trx0trx.h30
-rw-r--r--storage/innobase/lock/lock0lock.cc107
-rw-r--r--storage/innobase/trx/trx0roll.cc10
-rw-r--r--storage/innobase/trx/trx0trx.cc3
13 files changed, 271 insertions, 96 deletions
diff --git a/mysql-test/suite/galera/r/galera_FK_duplicate_client_insert.result b/mysql-test/suite/galera/r/galera_FK_duplicate_client_insert.result
index 5ae577a6323..b40c57c5d90 100644
--- a/mysql-test/suite/galera/r/galera_FK_duplicate_client_insert.result
+++ b/mysql-test/suite/galera/r/galera_FK_duplicate_client_insert.result
@@ -14,7 +14,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -32,7 +32,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -50,7 +50,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -68,7 +68,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -86,7 +86,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -104,7 +104,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -122,7 +122,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -140,7 +140,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -158,7 +158,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -176,7 +176,7 @@ connection node_1_u;
begin;
update user set j = j + 1 WHERE id > 0;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
insert into user_session(id,fk1,fk2) values (2, 2, 2);
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -202,7 +202,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -220,7 +220,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -238,7 +238,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -256,7 +256,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -274,7 +274,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -292,7 +292,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -310,7 +310,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -328,7 +328,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -346,7 +346,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
@@ -364,7 +364,7 @@ connection node_1_u;
begin;
execute upd;
connection node_1_i;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
execute ins1;
connection node_1;
set debug_sync='now WAIT_FOR ins_waiting';
diff --git a/mysql-test/suite/galera/t/galera_FK_duplicate_client_insert.test b/mysql-test/suite/galera/t/galera_FK_duplicate_client_insert.test
index 02322fc02ec..6dd1fee1d4e 100644
--- a/mysql-test/suite/galera/t/galera_FK_duplicate_client_insert.test
+++ b/mysql-test/suite/galera/t/galera_FK_duplicate_client_insert.test
@@ -72,7 +72,7 @@ while($counter > 0)
update user set j = j + 1 WHERE id > 0;
--connection node_1_i
- set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+ set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
send insert into user_session(id,fk1,fk2) values (2, 2, 2);
--connection node_1
@@ -126,7 +126,7 @@ while($counter > 0)
#update user set j = j + 1 WHERE id > 0;
--connection node_1_i
- set debug_sync='lock_wait_suspend_thread_enter SIGNAL ins_waiting WAIT_FOR cont_ins';
+ set debug_sync='lock_wait_start SIGNAL ins_waiting WAIT_FOR cont_ins';
send execute ins1;
--connection node_1
diff --git a/mysql-test/suite/innodb/r/cursor-restore-locking.result b/mysql-test/suite/innodb/r/cursor-restore-locking.result
index bc1127f57b3..beeb5a87844 100644
--- a/mysql-test/suite/innodb/r/cursor-restore-locking.result
+++ b/mysql-test/suite/innodb/r/cursor-restore-locking.result
@@ -7,12 +7,12 @@ SET DEBUG_SYNC = 'innodb_row_search_for_mysql_exit SIGNAL first_del_row_search_m
DELETE FROM t WHERE b = 20;
connect con_ins_1,localhost,root,,;
SET DEBUG_SYNC = 'now WAIT_FOR first_del_row_search_mvcc_finished';
-SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL first_ins_locked';
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL first_ins_locked';
SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL first_ins_row_inserted WAIT_FOR first_ins_cont';
INSERT INTO t VALUES(10, 20);
connect con_del_2,localhost,root,,;
SET DEBUG_SYNC = 'now WAIT_FOR first_ins_locked';
-SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL second_del_locked';
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL second_del_locked';
DELETE FROM t WHERE b = 20;
connection default;
SET DEBUG_SYNC = 'now WAIT_FOR second_del_locked';
diff --git a/mysql-test/suite/innodb/r/deadlock_victim_race.result b/mysql-test/suite/innodb/r/deadlock_victim_race.result
new file mode 100644
index 00000000000..9a7ef51ef24
--- /dev/null
+++ b/mysql-test/suite/innodb/r/deadlock_victim_race.result
@@ -0,0 +1,42 @@
+CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
+CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
+INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
+INSERT INTO t2 VALUES (10), (20), (30);
+BEGIN;
+SELECT * FROM t WHERE a = 20 FOR UPDATE;
+a b
+20 20
+connect con_2,localhost,root,,;
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN;
+SET DEBUG_SYNC = 'lock_trx_handle_wait_enter SIGNAL upd_locked WAIT_FOR upd_cont EXECUTE 2';
+UPDATE t SET b = 100;
+connect con_3,localhost,root,,;
+BEGIN;
+UPDATE t2 SET a = a + 100;
+SELECT * FROM t WHERE a = 30 FOR UPDATE;
+a b
+30 30
+SET DEBUG_SYNC='now WAIT_FOR upd_locked';
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL sel_locked';
+SELECT * FROM t WHERE a = 20 FOR UPDATE;
+connection default;
+SET DEBUG_SYNC='now WAIT_FOR sel_locked';
+ROLLBACK;
+SET DEBUG_SYNC='now SIGNAL upd_cont';
+SET innodb_lock_wait_timeout=1;
+SET DEBUG_SYNC="now WAIT_FOR upd_locked";
+SET DEBUG_SYNC="lock_wait_end SIGNAL upd_cont";
+SELECT * FROM t WHERE a = 10 FOR UPDATE;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con_3;
+a b
+20 20
+connection con_2;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+disconnect con_3;
+disconnect con_2;
+connection default;
+SET DEBUG_SYNC = 'RESET';
+DROP TABLE t;
+DROP TABLE t2;
diff --git a/mysql-test/suite/innodb/r/update-cascade.result b/mysql-test/suite/innodb/r/update-cascade.result
index a3c8fed931e..21d8f11e1cb 100644
--- a/mysql-test/suite/innodb/r/update-cascade.result
+++ b/mysql-test/suite/innodb/r/update-cascade.result
@@ -38,7 +38,7 @@ select f1, f2 from t2 for update;
f1 f2
1 2
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
update t1 set f1 = 10 where f1 = 2;
connection con1;
set debug_sync='now WAIT_FOR upd_waiting';
@@ -97,7 +97,7 @@ select f1, f2 from t2 for update;
f1 f2
1 91
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
update t1 set f2 = 28 where f2 = 91;
connection con1;
set debug_sync='now WAIT_FOR upd_waiting';
@@ -164,7 +164,7 @@ select f1 from t3 for update;
f1
2
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
update t1 set f1 = 10 where f1 = 2;
connection con1;
set debug_sync='now WAIT_FOR upd_waiting';
@@ -253,7 +253,7 @@ select f1 from t3 for update;
f1
2
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
update t1 set f2 = 28 where f2 = 91;
connection con1;
set debug_sync='now WAIT_FOR upd_waiting';
diff --git a/mysql-test/suite/innodb/t/cursor-restore-locking.test b/mysql-test/suite/innodb/t/cursor-restore-locking.test
index d032d8a8def..a398768fc66 100644
--- a/mysql-test/suite/innodb/t/cursor-restore-locking.test
+++ b/mysql-test/suite/innodb/t/cursor-restore-locking.test
@@ -16,19 +16,19 @@ SET DEBUG_SYNC = 'innodb_row_search_for_mysql_exit SIGNAL first_del_row_search_m
--connect(con_ins_1,localhost,root,,)
SET DEBUG_SYNC = 'now WAIT_FOR first_del_row_search_mvcc_finished';
# It's supposed the following INSERT will be suspended just after
-# lock_wait_suspend_thread_enter syncpoint, and will be awaken
+# lock_wait_start syncpoint, and will be awaken
# after the previous DELETE commits. ib_after_row_insert will be executed
# after the INSERT is woken up. The previous DELETE will wait for
# first_del_cont signal before commit, and this signal will be sent later.
# So it's safe to use two signals in a row here, it's guaranted the first
# signal will be received before the second signal is sent.
-SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL first_ins_locked';
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL first_ins_locked';
SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL first_ins_row_inserted WAIT_FOR first_ins_cont';
--send INSERT INTO t VALUES(10, 20)
--connect(con_del_2,localhost,root,,)
SET DEBUG_SYNC = 'now WAIT_FOR first_ins_locked';
-SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL second_del_locked';
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL second_del_locked';
###############################################################################
# This DELETE is locked by the previous DELETE, after that DELETE is
# committed, it will still be locked by the next INSERT on delete-marked
diff --git a/mysql-test/suite/innodb/t/deadlock_victim_race.test b/mysql-test/suite/innodb/t/deadlock_victim_race.test
new file mode 100644
index 00000000000..3c9dd15fb4d
--- /dev/null
+++ b/mysql-test/suite/innodb/t/deadlock_victim_race.test
@@ -0,0 +1,102 @@
+--source include/have_innodb.inc
+--source include/have_debug_sync.inc
+--source include/count_sessions.inc
+
+CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
+CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
+
+INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
+INSERT INTO t2 VALUES (10), (20), (30);
+
+BEGIN; # trx 1
+SELECT * FROM t WHERE a = 20 FOR UPDATE;
+# Locking order:
+# (10,10) (20,20) (30,30)
+# ^
+# trx 1
+
+--connect(con_2,localhost,root,,)
+# RC is neccessary to do semi-consistent read
+SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
+BEGIN; # trx 2
+# The first time it will be hit on trying to lock (20,20), the second hit
+# will be on (30,30).
+SET DEBUG_SYNC = 'lock_trx_handle_wait_enter SIGNAL upd_locked WAIT_FOR upd_cont EXECUTE 2';
+# We must not modify primary key fields to cause rr_sequential() read record
+# function choosing in mysql_update(), i.e. both query_plan.using_filesort and
+# query_plan.using_io_buffer must be false during init_read_record() call.
+--send UPDATE t SET b = 100
+
+--connect(con_3,localhost,root,,)
+BEGIN; # trx 3
+# The following update is necessary to increase the transaction weight, which is
+# calculated as the number of locks + the number of undo records during deadlock
+# report. Victim's transaction should have minimum weight. We need trx 2 to be
+# choosen as victim, that's why we need to increase the current transaction
+# weight.
+UPDATE t2 SET a = a + 100;
+SELECT * FROM t WHERE a = 30 FOR UPDATE;
+SET DEBUG_SYNC='now WAIT_FOR upd_locked';
+# Locking queue:
+# (10,10) (20,20) (30,30)
+# ^ ^ ^
+# trx 2 trx 1 trx 3
+# trx 2 (waiting for 1)
+
+SET DEBUG_SYNC = 'lock_wait_start SIGNAL sel_locked';
+--send SELECT * FROM t WHERE a = 20 FOR UPDATE
+--connection default
+SET DEBUG_SYNC='now WAIT_FOR sel_locked';
+# Locking queue:
+# (10,10) (20,20) (30,30)
+# ^ ^ ^
+# trx 2 trx 1 trx 3
+# trx 2 (waiting for 1)
+# trx 3 (waiting for 1)
+#
+# Note trx 1 must grant lock to trx2 before trx 2 checks the lock state in
+# lock_trx_handle_wait(), i.e. the function must return DB_SUCCESS, that's why
+# the following ROLLBACK must be executed before sending upd_cont signal.
+ROLLBACK;
+SET DEBUG_SYNC='now SIGNAL upd_cont';
+
+SET innodb_lock_wait_timeout=1;
+SET DEBUG_SYNC="now WAIT_FOR upd_locked";
+# Locking queue:
+# (10,10) (20,20) (30,30)
+# ^ ^ ^
+# trx 2 trx 2 trx 3
+# trx 3 (waiting for 2) trx 2 (waiting for 3)
+#
+# Deadlock happened after trx 1 granted lock to trx 2, and trx2 continued
+# sequential read (with rr_sequential() read record function), and requested
+# lock on (30,30). But the deadlock has not been determined yet.
+
+SET DEBUG_SYNC="lock_wait_end SIGNAL upd_cont";
+--error ER_LOCK_WAIT_TIMEOUT
+# The deadlock will be determined in lock_wait() after lock wait timeout
+# expired.
+SELECT * FROM t WHERE a = 10 FOR UPDATE;
+
+--connection con_3
+--reap
+
+--connection con_2
+# As lock_trx_handle_wait() wrongly returned DB_SUCCESS instead of
+# DB_DEADLOCK, row_search_mvcc() of trx 2 behaves so as if (30,30) was locked.
+# But the waiting(for trx 3) lock was cancelled by deadlock checker after
+# trx 2 was choosen as a victim (see lock_cancel_waiting_and_release() call
+# from Deadlock::report() for details). The try to update non-locked record
+# will cause assertion if the bug is not fixed.
+--error ER_LOCK_DEADLOCK
+--reap
+
+--disconnect con_3
+--disconnect con_2
+
+--connection default
+SET DEBUG_SYNC = 'RESET';
+DROP TABLE t;
+DROP TABLE t2;
+
+--source include/wait_until_count_sessions.inc
diff --git a/mysql-test/suite/innodb/t/update-cascade.test b/mysql-test/suite/innodb/t/update-cascade.test
index de8294703b4..69e81ac4a2f 100644
--- a/mysql-test/suite/innodb/t/update-cascade.test
+++ b/mysql-test/suite/innodb/t/update-cascade.test
@@ -28,7 +28,7 @@ start transaction;
select f1, f2 from t2 for update;
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
send update t1 set f1 = 10 where f1 = 2;
connection con1;
@@ -72,7 +72,7 @@ start transaction;
select f1, f2 from t2 for update;
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
send update t1 set f2 = 28 where f2 = 91;
connection con1;
@@ -120,7 +120,7 @@ start transaction;
select f1 from t3 for update;
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
send update t1 set f1 = 10 where f1 = 2;
connection con1;
@@ -183,7 +183,7 @@ start transaction;
select f1 from t3 for update;
connection default;
-set debug_sync='lock_wait_suspend_thread_enter SIGNAL upd_waiting WAIT_FOR go_upd';
+set debug_sync='lock_wait_start SIGNAL upd_waiting WAIT_FOR go_upd';
send update t1 set f2 = 28 where f2 = 91;
connection con1;
diff --git a/storage/innobase/dict/drop.cc b/storage/innobase/dict/drop.cc
index 4a4e10b45a8..edb6add0787 100644
--- a/storage/innobase/dict/drop.cc
+++ b/storage/innobase/dict/drop.cc
@@ -245,7 +245,8 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
ut_ad(ib_vector_is_empty(autoinc_locks));
mem_heap_empty(lock.lock_heap);
lock.table_locks.clear();
- lock.was_chosen_as_deadlock_victim= false;
+ /* commit_persist() already reset this. */
+ ut_ad(!lock.was_chosen_as_deadlock_victim);
lock.n_rec_locks= 0;
while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
{
diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h
index a45bbac0334..91d1ec94e1b 100644
--- a/storage/innobase/include/trx0trx.h
+++ b/storage/innobase/include/trx0trx.h
@@ -338,27 +338,11 @@ struct trx_lock_t
/** lock wait start time */
Atomic_relaxed<my_hrtime_t> suspend_time;
+#if defined(UNIV_DEBUG) || !defined(DBUG_OFF)
/** 2=high priority WSREP thread has marked this trx to abort;
1=another transaction chose this as a victim in deadlock resolution. */
Atomic_relaxed<byte> was_chosen_as_deadlock_victim;
- /** Clear the deadlock victim status. */
- void clear_deadlock_victim()
- {
-#ifndef WITH_WSREP
- was_chosen_as_deadlock_victim= false;
-#elif defined __GNUC__ && (defined __i386__ || defined __x86_64__)
- /* There is no 8-bit version of the 80386 BTR instruction.
- Technically, this is the wrong addressing mode (16-bit), but
- there are other data members stored after the byte. */
- __asm__ __volatile__("lock btrw $0, %0"
- : "+m" (was_chosen_as_deadlock_victim));
-#else
- was_chosen_as_deadlock_victim.fetch_and(byte(~1));
-#endif
- }
-
-#ifdef WITH_WSREP
/** Flag the lock owner as a victim in Galera conflict resolution. */
void set_wsrep_victim()
{
@@ -372,7 +356,17 @@ struct trx_lock_t
was_chosen_as_deadlock_victim.fetch_or(2);
# endif
}
-#endif
+#else /* defined(UNIV_DEBUG) || !defined(DBUG_OFF) */
+
+ /** High priority WSREP thread has marked this trx to abort or
+ another transaction chose this as a victim in deadlock resolution. */
+ Atomic_relaxed<bool> was_chosen_as_deadlock_victim;
+
+ /** Flag the lock owner as a victim in Galera conflict resolution. */
+ void set_wsrep_victim() {
+ was_chosen_as_deadlock_victim= true;
+ }
+#endif /* defined(UNIV_DEBUG) || !defined(DBUG_OFF) */
/** Next available rec_pool[] entry */
byte rec_cached;
diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc
index 76ad538ea0e..1c11efafc7a 100644
--- a/storage/innobase/lock/lock0lock.cc
+++ b/storage/innobase/lock/lock0lock.cc
@@ -44,6 +44,7 @@ Created 5/7/1996 Heikki Tuuri
#include "row0vers.h"
#include "pars0pars.h"
#include "srv0mon.h"
+#include "scope.h"
#include <set>
@@ -1275,6 +1276,14 @@ lock_rec_enqueue_waiting(
trx_t* trx = thr_get_trx(thr);
ut_ad(xtest() || trx->mutex_is_owner());
ut_ad(!trx->dict_operation_lock_mode);
+ /* Apart from Galera, only transactions that have waiting lock can be
+ chosen as deadlock victim. Only one lock can be waited for at a time,
+ and a transaction is associated with a single thread. That is why there
+ must not be waiting lock requests if the transaction is deadlock victim
+ and it is not WSREP. Galera transaction abort can be invoked from MDL
+ acquisition code when the transaction does not have waiting record
+ lock, that's why we check only deadlock victim bit here. */
+ ut_ad(!(trx->lock.was_chosen_as_deadlock_victim & 1));
if (trx->mysql_thd && thd_lock_wait_timeout(trx->mysql_thd) == 0) {
trx->error_state = DB_LOCK_WAIT_TIMEOUT;
@@ -1292,7 +1301,6 @@ lock_rec_enqueue_waiting(
}
trx->lock.wait_thr = thr;
- trx->lock.clear_deadlock_victim();
DBUG_LOG("ib_lock", "trx " << ib::hex(trx->id)
<< " waits for lock in index " << index->name
@@ -1475,7 +1483,14 @@ lock_rec_lock(
que_thr_t* thr) /*!< in: query thread */
{
trx_t *trx= thr_get_trx(thr);
-
+ /* There must not be lock requests for reads or updates if transaction was
+ chosen as deadlock victim. Apart from Galera, only transactions that have
+ waiting lock may be chosen as deadlock victims. Only one lock can be waited
+ for at a time, and a transaction is associated with a single thread. Galera
+ transaction abort can be invoked from MDL acquisition code when the
+ transaction does not have waiting lock, that's why we check only deadlock
+ victim bit here. */
+ ut_ad(!(trx->lock.was_chosen_as_deadlock_victim & 1));
ut_ad(!srv_read_only_mode);
ut_ad(((LOCK_MODE_MASK | LOCK_TABLE) & mode) == LOCK_S ||
((LOCK_MODE_MASK | LOCK_TABLE) & mode) == LOCK_X);
@@ -1627,7 +1642,9 @@ void lock_sys_t::wait_resume(THD *thd, my_hrtime_t start, my_hrtime_t now)
#ifdef HAVE_REPLICATION
ATTRIBUTE_NOINLINE MY_ATTRIBUTE((nonnull))
-/** Report lock waits to parallel replication.
+/** Report lock waits to parallel replication. Sets
+trx->error_state= DB_DEADLOCK if trx->lock.was_chosen_as_deadlock_victim was
+set when lock_sys.wait_mutex was unlocked.
@param trx transaction that may be waiting for a lock
@param wait_lock lock that is being waited for */
static void lock_wait_rpl_report(trx_t *trx)
@@ -1642,7 +1659,8 @@ static void lock_wait_rpl_report(trx_t *trx)
ut_ad(!(wait_lock->type_mode & LOCK_AUTO_INC));
/* This would likely be too large to attempt to use a memory transaction,
even for wait_lock->is_table(). */
- if (!lock_sys.wr_lock_try())
+ const bool nowait= lock_sys.wr_lock_try();
+ if (!nowait)
{
mysql_mutex_unlock(&lock_sys.wait_mutex);
lock_sys.wr_lock(SRW_LOCK_CALL);
@@ -1652,6 +1670,10 @@ static void lock_wait_rpl_report(trx_t *trx)
{
func_exit:
lock_sys.wr_unlock();
+ /* trx->lock.was_chosen_as_deadlock_victim can be set when
+ lock_sys.wait_mutex was unlocked, let's check it. */
+ if (!nowait && trx->lock.was_chosen_as_deadlock_victim)
+ trx->error_state= DB_DEADLOCK;
return;
}
ut_ad(wait_lock->is_waiting());
@@ -1700,7 +1722,13 @@ dberr_t lock_wait(que_thr_t *thr)
trx_t *trx= thr_get_trx(thr);
if (trx->mysql_thd)
- DEBUG_SYNC_C("lock_wait_suspend_thread_enter");
+ DEBUG_SYNC_C("lock_wait_start");
+
+ /* Create the sync point for any quit from the function. */
+ ut_d(SCOPE_EXIT([trx]() {
+ if (trx->mysql_thd)
+ DEBUG_SYNC_C("lock_wait_end");
+ }));
/* InnoDB system transactions may use the global value of
innodb_lock_wait_timeout, because trx->mysql_thd == NULL. */
@@ -1731,11 +1759,8 @@ dberr_t lock_wait(que_thr_t *thr)
{
/* The lock has already been released or this transaction
was chosen as a deadlock victim: no need to wait */
- if (trx->lock.was_chosen_as_deadlock_victim.fetch_and(byte(~1)))
- trx->error_state= DB_DEADLOCK;
- else
- trx->error_state= DB_SUCCESS;
-
+ trx->error_state=
+ trx->lock.was_chosen_as_deadlock_victim ? DB_DEADLOCK : DB_SUCCESS;
return trx->error_state;
}
@@ -1770,7 +1795,7 @@ dberr_t lock_wait(que_thr_t *thr)
wait_lock->un_member.tab_lock.table->id <= DICT_FIELDS_ID);
thd_wait_begin(trx->mysql_thd, (type_mode & LOCK_TABLE)
? THD_WAIT_TABLE_LOCK : THD_WAIT_ROW_LOCK);
- dberr_t error_state= DB_SUCCESS;
+ trx->error_state= DB_SUCCESS;
mysql_mutex_lock(&lock_sys.wait_mutex);
if (trx->lock.wait_lock)
@@ -1778,23 +1803,28 @@ dberr_t lock_wait(que_thr_t *thr)
if (Deadlock::check_and_resolve(trx))
{
ut_ad(!trx->lock.wait_lock);
- error_state= DB_DEADLOCK;
+ trx->error_state= DB_DEADLOCK;
goto end_wait;
}
}
else
+ {
+ /* trx->lock.was_chosen_as_deadlock_victim can be changed before
+ lock_sys.wait_mutex is acquired, so let's check it once more. */
+ trx->error_state=
+ trx->lock.was_chosen_as_deadlock_victim ? DB_DEADLOCK : DB_SUCCESS;
goto end_wait;
-
+ }
if (row_lock_wait)
lock_sys.wait_start();
+ trx->error_state= DB_SUCCESS;
+
#ifdef HAVE_REPLICATION
if (rpl)
lock_wait_rpl_report(trx);
#endif
- trx->error_state= DB_SUCCESS;
-
while (trx->lock.wait_lock)
{
int err;
@@ -1807,20 +1837,19 @@ dberr_t lock_wait(que_thr_t *thr)
else
err= my_cond_timedwait(&trx->lock.cond, &lock_sys.wait_mutex.m_mutex,
&abstime);
- error_state= trx->error_state;
- switch (error_state) {
+ switch (trx->error_state) {
case DB_DEADLOCK:
case DB_INTERRUPTED:
break;
default:
- ut_ad(error_state != DB_LOCK_WAIT_TIMEOUT);
+ ut_ad(trx->error_state != DB_LOCK_WAIT_TIMEOUT);
/* Dictionary transactions must ignore KILL, because they could
be executed as part of a multi-transaction DDL operation,
such as rollback_inplace_alter_table() or ha_innobase::delete_table(). */
if (!trx->dict_operation && trx_is_interrupted(trx))
/* innobase_kill_query() can only set trx->error_state=DB_INTERRUPTED
for any transaction that is attached to a connection. */
- error_state= DB_INTERRUPTED;
+ trx->error_state= DB_INTERRUPTED;
else if (!err)
continue;
#ifdef WITH_WSREP
@@ -1828,7 +1857,7 @@ dberr_t lock_wait(que_thr_t *thr)
#endif
else
{
- error_state= DB_LOCK_WAIT_TIMEOUT;
+ trx->error_state= DB_LOCK_WAIT_TIMEOUT;
lock_sys.timeouts++;
}
}
@@ -1848,8 +1877,7 @@ end_wait:
mysql_mutex_unlock(&lock_sys.wait_mutex);
thd_wait_end(trx->mysql_thd);
- trx->error_state= error_state;
- return error_state;
+ return trx->error_state;
}
@@ -1862,7 +1890,7 @@ static void lock_wait_end(trx_t *trx)
ut_ad(state == TRX_STATE_ACTIVE || state == TRX_STATE_PREPARED);
ut_ad(trx->lock.wait_thr);
- if (trx->lock.was_chosen_as_deadlock_victim.fetch_and(byte(~1)))
+ if (trx->lock.was_chosen_as_deadlock_victim)
{
ut_ad(state == TRX_STATE_ACTIVE);
trx->error_state= DB_DEADLOCK;
@@ -3401,17 +3429,18 @@ lock_table_enqueue_waiting(
ut_ad(trx->mutex_is_owner());
ut_ad(!trx->dict_operation_lock_mode);
-#ifdef WITH_WSREP
- if (trx->is_wsrep() && trx->lock.was_chosen_as_deadlock_victim) {
- return(DB_DEADLOCK);
- }
-#endif /* WITH_WSREP */
-
/* Enqueue the lock request that will wait to be granted */
lock_table_create(table, mode | LOCK_WAIT, trx, c_lock);
trx->lock.wait_thr = thr;
- trx->lock.clear_deadlock_victim();
+ /* Apart from Galera, only transactions that have waiting lock
+ may be chosen as deadlock victims. Only one lock can be waited for at a
+ time, and a transaction is associated with a single thread. That is why
+ there must not be waiting lock requests if the transaction is deadlock
+ victim and it is not WSREP. Galera transaction abort can be invoked
+ from MDL acquisition code when the transaction does not have waiting
+ lock, that's why we check only deadlock victim bit here. */
+ ut_ad(!(trx->lock.was_chosen_as_deadlock_victim & 1));
MONITOR_INC(MONITOR_TABLELOCK_WAIT);
return(DB_LOCK_WAIT);
@@ -3949,7 +3978,6 @@ released:
mysql_mutex_unlock(&lock_sys.wait_mutex);
}
- trx->lock.was_chosen_as_deadlock_victim= false;
trx->lock.n_rec_locks= 0;
#ifdef UNIV_DEBUG
@@ -5718,10 +5746,12 @@ dberr_t lock_sys_t::cancel(trx_t *trx, lock_t *lock)
lock_sys.rd_lock(SRW_LOCK_CALL);
mysql_mutex_lock(&lock_sys.wait_mutex);
lock= trx->lock.wait_lock;
- if (!lock);
- else if (check_victim && trx->lock.was_chosen_as_deadlock_victim)
+ /* Even if waiting lock was cancelled while lock_sys.wait_mutex was
+ unlocked, we need to return deadlock error if transaction was chosen
+ as deadlock victim to rollback it */
+ if (check_victim && trx->lock.was_chosen_as_deadlock_victim)
err= DB_DEADLOCK;
- else
+ else if (lock)
goto resolve_table_lock;
}
else
@@ -5769,10 +5799,12 @@ retreat:
lock_sys.wr_lock(SRW_LOCK_CALL);
mysql_mutex_lock(&lock_sys.wait_mutex);
lock= trx->lock.wait_lock;
- if (!lock);
- else if (check_victim && trx->lock.was_chosen_as_deadlock_victim)
+ /* Even if waiting lock was cancelled while lock_sys.wait_mutex was
+ unlocked, we need to return deadlock error if transaction was chosen
+ as deadlock victim to rollback it */
+ if (check_victim && trx->lock.was_chosen_as_deadlock_victim)
err= DB_DEADLOCK;
- else
+ else if (lock)
goto resolve_record_lock;
}
else
@@ -5850,6 +5882,7 @@ while holding a clustered index leaf page latch.
lock request was released */
dberr_t lock_trx_handle_wait(trx_t *trx)
{
+ DEBUG_SYNC_C("lock_trx_handle_wait_enter");
if (trx->lock.was_chosen_as_deadlock_victim)
return DB_DEADLOCK;
if (!trx->lock.wait_lock)
diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc
index 59ea0bdcd8f..59c9a319330 100644
--- a/storage/innobase/trx/trx0roll.cc
+++ b/storage/innobase/trx/trx0roll.cc
@@ -135,6 +135,9 @@ inline void trx_t::rollback_low(trx_savept_t *savept)
}
else
{
+ /* There must not be partial rollback if transaction was chosen as deadlock
+ victim. Galera transaction abort can be invoked during partial rollback. */
+ ut_ad(!(lock.was_chosen_as_deadlock_victim & 1));
ut_a(error_state == DB_SUCCESS);
const undo_no_t limit= savept->least_undo_no;
apply_online_log= false;
@@ -211,6 +214,10 @@ dberr_t trx_rollback_for_mysql(trx_t* trx)
case TRX_STATE_NOT_STARTED:
trx->will_lock = false;
ut_ad(trx->mysql_thd);
+ /* Galera transaction abort can be invoked from MDL acquision
+ code, so trx->lock.was_chosen_as_deadlock_victim can be set
+ even if trx->state is TRX_STATE_NOT_STARTED. */
+ ut_ad(!(trx->lock.was_chosen_as_deadlock_victim & 1));
#ifdef WITH_WSREP
trx->wsrep= false;
trx->lock.was_chosen_as_deadlock_victim= false;
@@ -418,9 +425,6 @@ trx_rollback_to_savepoint_for_mysql_low(
trx_mark_sql_stat_end(trx);
trx->op_info = "";
-#ifdef WITH_WSREP
- trx->lock.was_chosen_as_deadlock_victim = false;
-#endif
return(err);
}
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index 95bc6273e48..a8f8d7dcc79 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -1379,9 +1379,8 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
wsrep= false;
wsrep_commit_ordered(mysql_thd);
}
- ut_ad(!(lock.was_chosen_as_deadlock_victim & byte(~2U)));
- lock.was_chosen_as_deadlock_victim= false;
#endif /* WITH_WSREP */
+ lock.was_chosen_as_deadlock_victim= false;
}
void trx_t::commit_cleanup()