Commit fa5f6068 authored by Rinat Ibragimov's avatar Rinat Ibragimov Committed by Monty

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.
parent 8f4a3bf0
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';
# 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
......@@ -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)
......
......@@ -909,7 +909,8 @@ class MDL_context
/** @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 @@ class MDL_context
*/
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment