summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRinat Ibragimov <ri@tempesta-tech.com>2020-06-18 01:11:39 +0300
committerMonty <monty@mariadb.org>2021-03-10 18:08:29 +0200
commitfa5f60681fe4ed9d5bb160e551381cb7b1be5850 (patch)
treec4f2991acc59aaf8024aca68a0924f4f2719eedc
parent8f4a3bf07cc9d3f42899314411da344dabd66c5e (diff)
downloadmariadb-git-fa5f60681fe4ed9d5bb160e551381cb7b1be5850.tar.gz
MDEV-20946: Hard FTWRL deadlock under user level locks
It was possibile for a user to create an interlocked state which may go on for a significant period of time. There is a tight loop in the FTWRL code path that tries to repeatedly acquire a read lock. As the weight of FTWRL lock is the smallest among others, it's always selected by the deadlock detector, but can never be killed. Imaging the following sequence: connection_0 connection_1 GET_LOCK("l1", 0); LOCK TABLES t WRITE; FLUSH TABLES WITH READ LOCK; GET_LOCK("l1", 1000); The GET_LOCK statement in connection_1 triggers the deadlock detector, which tries to select the lock in FTWRL, since its weight is 0. However, since a loop in Global_read_lock::lock_global_read_lock() tries to always win, it tries to acquire lock again. Which invokes the deadlock detector, and that cycle continues until GET_LOCK in connection_1 times out. This patch resolves the live-locking by introducing a dynamic bonus to the deadlock weight associated with every lock. Each lock gets a bonus weight each time it's selected by the deadlock detector. In case of a live-lock situation, those locks that cannot be killed, get additional weight each iteration. Eventually their weight becomes so high that the deadlock detector shifts its attention to other lock, until it find the one that can be killed.
-rw-r--r--mysql-test/main/deadlock_ftwrl.result21
-rw-r--r--mysql-test/main/deadlock_ftwrl.test36
-rw-r--r--sql/mdl.cc1
-rw-r--r--sql/mdl.h4
4 files changed, 61 insertions, 1 deletions
diff --git a/mysql-test/main/deadlock_ftwrl.result b/mysql-test/main/deadlock_ftwrl.result
new file mode 100644
index 00000000000..95eed70f664
--- /dev/null
+++ b/mysql-test/main/deadlock_ftwrl.result
@@ -0,0 +1,21 @@
+CREATE TABLE t1(a INT);
+SELECT GET_LOCK("l1", 0);
+GET_LOCK("l1", 0)
+1
+connect con1,localhost,root,,;
+LOCK TABLES t1 WRITE;
+connection default;
+set debug_sync='mdl_acquire_lock_wait SIGNAL ftwrl';
+FLUSH TABLES WITH READ LOCK;
+connection con1;
+set debug_sync='now WAIT_FOR ftwrl';
+SELECT GET_LOCK("l1", 1000);
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+disconnect con1;
+connection default;
+SELECT RELEASE_LOCK("l1");
+RELEASE_LOCK("l1")
+1
+UNLOCK TABLES;
+DROP TABLE t1;
+set debug_sync='reset';
diff --git a/mysql-test/main/deadlock_ftwrl.test b/mysql-test/main/deadlock_ftwrl.test
new file mode 100644
index 00000000000..fc943bcf953
--- /dev/null
+++ b/mysql-test/main/deadlock_ftwrl.test
@@ -0,0 +1,36 @@
+# MDEV-20946 Hard FTWRL deadlock under user level locks
+#
+# Deadlock detector should resolve conflicts between FTWRL and user locks.
+
+--source include/have_debug_sync.inc
+--source include/count_sessions.inc
+
+CREATE TABLE t1(a INT);
+SELECT GET_LOCK("l1", 0);
+
+connect(con1,localhost,root,,);
+LOCK TABLES t1 WRITE;
+
+connection default;
+set debug_sync='mdl_acquire_lock_wait SIGNAL ftwrl';
+send FLUSH TABLES WITH READ LOCK;
+# At this point "default" is waiting for tables to be unlocked from
+# LOCK TABLES WRITE issued by "con1".
+
+connection con1;
+set debug_sync='now WAIT_FOR ftwrl';
+# The lock in the following GET_LOCK cannot be acquired since "default" holds
+# a lock on "l1" and is waiting in FLUSH TABLES for con1.
+--error ER_LOCK_DEADLOCK
+SELECT GET_LOCK("l1", 1000);
+disconnect con1; # Performs an implicit UNLOCK TABLES.
+
+connection default;
+reap;
+SELECT RELEASE_LOCK("l1");
+UNLOCK TABLES;
+DROP TABLE t1;
+
+set debug_sync='reset';
+
+--source include/wait_until_count_sessions.inc
diff --git a/sql/mdl.cc b/sql/mdl.cc
index 1798d3039fa..5e54178db70 100644
--- a/sql/mdl.cc
+++ b/sql/mdl.cc
@@ -2782,6 +2782,7 @@ void MDL_context::find_deadlock()
context was waiting is concurrently satisfied.
*/
(void) victim->m_wait.set_status(MDL_wait::VICTIM);
+ victim->inc_deadlock_overweight();
victim->unlock_deadlock_victim();
if (victim == this)
diff --git a/sql/mdl.h b/sql/mdl.h
index 9a788b0ea31..a2cb7c2aa85 100644
--- a/sql/mdl.h
+++ b/sql/mdl.h
@@ -909,7 +909,8 @@ public:
/** @pre Only valid if we started waiting for lock. */
inline uint get_deadlock_weight() const
- { return m_waiting_for->get_deadlock_weight(); }
+ { return m_waiting_for->get_deadlock_weight() + m_deadlock_overweight; }
+ void inc_deadlock_overweight() { m_deadlock_overweight++; }
/**
Post signal to the context (and wake it up if necessary).
@@ -1027,6 +1028,7 @@ private:
*/
MDL_wait_for_subgraph *m_waiting_for;
LF_PINS *m_pins;
+ uint m_deadlock_overweight= 0;
private:
MDL_ticket *find_ticket(MDL_request *mdl_req,
enum_mdl_duration *duration);