Skip to content

Commit 22bc48b

Browse files
author
Dmitry Lenev
committed
Fix for bug #51134 "Crash in MDL_lock::destroy on a concurrent
DDL workload". When a RENAME TABLE or LOCK TABLE ... WRITE statement which mentioned the same table several times were aborted during the process of acquring metadata locks (due to deadlock which was discovered or because of KILL statement) server might have crashed. When attempt to acquire all locks requested had failed we went through the list of requests and released locks which we have managed to acquire by that moment one by one. Since in the scenario described above list of requests contained duplicates this led to releasing the same ticket twice and a crash as result. This patch solves the problem by employing different approach to releasing locks in case of failure to acquire all locks requested. Now we take a MDL savepoint before starting acquiring locks and simply rollback to it if things go bad.
1 parent 0ec868c commit 22bc48b

File tree

3 files changed

+88
-3
lines changed

3 files changed

+88
-3
lines changed

mysql-test/r/lock_multi.result

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,29 @@ UNLOCK TABLES;
436436
# Reaping: DROP TABLE t1, t2
437437
# Connection default
438438
# Cleanup
439+
#
440+
# Test for bug #51134 "Crash in MDL_lock::destroy on a concurrent
441+
# DDL workload".
442+
#
443+
drop tables if exists t1, t2, t3;
444+
create table t3 (i int);
445+
# Switching to connection 'con1'
446+
# Lock 't3' so upcoming RENAME is blocked.
447+
lock table t3 read;
448+
# Switching to connection 'con2'
449+
# Remember ID for this connection.
450+
# Start statement which will try to acquire two instances
451+
# of X metadata lock on the same object.
452+
# Sending:
453+
rename tables t1 to t2, t2 to t3;;
454+
# Switching to connection 'default'
455+
# Wait until RENAME TABLE is blocked on table 't3'.
456+
# Kill RENAME TABLE.
457+
kill query ID;
458+
# Switching to connection 'con2'
459+
# RENAME TABLE should be aborted but should not crash.
460+
ERROR 70100: Query execution was interrupted
461+
# Switching to connection 'con1'
462+
unlock tables;
463+
# Switching to connection 'default'
464+
drop table t3;

mysql-test/t/lock_multi.test

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,5 +1038,59 @@ disconnect con2;
10381038
disconnect con3;
10391039

10401040

1041+
--echo #
1042+
--echo # Test for bug #51134 "Crash in MDL_lock::destroy on a concurrent
1043+
--echo # DDL workload".
1044+
--echo #
1045+
--disable_warnings
1046+
drop tables if exists t1, t2, t3;
1047+
--enable_warnings
1048+
connect (con1, localhost, root, , );
1049+
connect (con2, localhost, root, , );
1050+
connection default;
1051+
create table t3 (i int);
1052+
1053+
--echo # Switching to connection 'con1'
1054+
connection con1;
1055+
--echo # Lock 't3' so upcoming RENAME is blocked.
1056+
lock table t3 read;
1057+
1058+
--echo # Switching to connection 'con2'
1059+
connection con2;
1060+
--echo # Remember ID for this connection.
1061+
let $ID= `select connection_id()`;
1062+
--echo # Start statement which will try to acquire two instances
1063+
--echo # of X metadata lock on the same object.
1064+
--echo # Sending:
1065+
--send rename tables t1 to t2, t2 to t3;
1066+
1067+
--echo # Switching to connection 'default'
1068+
connection default;
1069+
--echo # Wait until RENAME TABLE is blocked on table 't3'.
1070+
let $wait_condition=
1071+
select count(*) = 1 from information_schema.processlist
1072+
where state = "Waiting for table" and info = "rename tables t1 to t2, t2 to t3";
1073+
--source include/wait_condition.inc
1074+
--echo # Kill RENAME TABLE.
1075+
--replace_result $ID ID
1076+
eval kill query $ID;
1077+
1078+
--echo # Switching to connection 'con2'
1079+
connection con2;
1080+
--echo # RENAME TABLE should be aborted but should not crash.
1081+
--error ER_QUERY_INTERRUPTED
1082+
--reap
1083+
1084+
--echo # Switching to connection 'con1'
1085+
connection con1;
1086+
unlock tables;
1087+
1088+
--echo # Switching to connection 'default'
1089+
connection default;
1090+
disconnect con1;
1091+
disconnect con2;
1092+
drop table t3;
1093+
1094+
10411095
# Wait till all disconnects are completed
10421096
--source include/wait_until_count_sessions.inc

sql/mdl.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests,
15321532
{
15331533
MDL_request_list::Iterator it(*mdl_requests);
15341534
MDL_request **sort_buf, **p_req;
1535+
MDL_ticket *mdl_svp= mdl_savepoint();
15351536
ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements());
15361537

15371538
if (req_count == 0)
@@ -1565,12 +1566,16 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests,
15651566
return FALSE;
15661567

15671568
err:
1568-
/* Release locks we have managed to acquire so far. */
1569+
/*
1570+
Release locks we have managed to acquire so far.
1571+
Use rollback_to_savepoint() since there may be duplicate
1572+
requests that got assigned the same ticket.
1573+
*/
1574+
rollback_to_savepoint(mdl_svp);
1575+
/* Reset lock requests back to its initial state. */
15691576
for (req_count= p_req - sort_buf, p_req= sort_buf;
15701577
p_req < sort_buf + req_count; p_req++)
15711578
{
1572-
release_lock((*p_req)->ticket);
1573-
/* Reset lock request back to its initial state. */
15741579
(*p_req)->ticket= NULL;
15751580
}
15761581
my_free(sort_buf, MYF(0));

0 commit comments

Comments
 (0)