summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Lenev <dlenev@mysql.com>2010-02-01 14:43:06 +0300
committerDmitry Lenev <dlenev@mysql.com>2010-02-01 14:43:06 +0300
commiteba5d30e67aedf4a8d55380ec933306cce7b7563 (patch)
tree818a7077a43f09db8708035a4f1d22d369fdd4da
parent6ddd01c27ab55242f8643e7efdd5f7bc9230a908 (diff)
downloadmariadb-git-eba5d30e67aedf4a8d55380ec933306cce7b7563.tar.gz
Implement new type-of-operation-aware metadata locks.
Add a wait-for graph based deadlock detector to the MDL subsystem. Fixes bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock" and bug #37346 "innodb does not detect deadlock between update and alter table". The first bug manifested itself as an unwarranted abort of a transaction with ER_LOCK_DEADLOCK error by a concurrent ALTER statement, when this transaction tried to repeat use of a table, which it has already used in a similar fashion before ALTER started. The second bug showed up as a deadlock between table-level locks and InnoDB row locks, which was "detected" only after innodb_lock_wait_timeout timeout. A transaction would start using the table and modify a few rows. Then ALTER TABLE would come in, and start copying rows into a temporary table. Eventually it would stumble on the modified records and get blocked on a row lock. The first transaction would try to do more updates, and get blocked on thr_lock.c lock. This situation of circular wait would only get resolved by a timeout. Both these bugs stemmed from inadequate solutions to the problem of deadlocks occurring between different locking subsystems. In the first case we tried to avoid deadlocks between metadata locking and table-level locking subsystems, when upgrading shared metadata lock to exclusive one. Transactions holding the shared lock on the table and waiting for some table-level lock used to be aborted too aggressively. We also allowed ALTER TABLE to start in presence of transactions that modify the subject table. ALTER TABLE acquires TL_WRITE_ALLOW_READ lock at start, and that block all writes against the table (naturally, we don't want any writes to be lost when switching the old and the new table). TL_WRITE_ALLOW_READ lock, in turn, would block the started transaction on thr_lock.c lock, should they do more updates. This, again, lead to the need to abort such transactions. The second bug occurred simply because we didn't have any mechanism to detect deadlocks between the table-level locks in thr_lock.c and row-level locks in InnoDB, other than innodb_lock_wait_timeout. This patch solves both these problems by moving lock conflicts which are causing these deadlocks into the metadata locking subsystem, thus making it possible to avoid or detect such deadlocks inside MDL. To do this we introduce new type-of-operation-aware metadata locks, which allow MDL subsystem to know not only the fact that transaction has used or is going to use some object but also what kind of operation it has carried out or going to carry out on the object. This, along with the addition of a special kind of upgradable metadata lock, allows ALTER TABLE to wait until all transactions which has updated the table to go away. This solves the second issue. Another special type of upgradable metadata lock is acquired by LOCK TABLE WRITE. This second lock type allows to solve the first issue, since abortion of table-level locks in event of DDL under LOCK TABLES becomes also unnecessary. Below follows the list of incompatible changes introduced by this patch: - From now on, ALTER TABLE and CREATE/DROP TRIGGER SQL (i.e. those statements that acquire TL_WRITE_ALLOW_READ lock) wait for all transactions which has *updated* the table to complete. - From now on, LOCK TABLES ... WRITE, REPAIR/OPTIMIZE TABLE (i.e. all statements which acquire TL_WRITE table-level lock) wait for all transaction which *updated or read* from the table to complete. As a consequence, innodb_table_locks=0 option no longer applies to LOCK TABLES ... WRITE. - DROP DATABASE, DROP TABLE, RENAME TABLE no longer abort statements or transactions which use tables being dropped or renamed, and instead wait for these transactions to complete. - Since LOCK TABLES WRITE now takes a special metadata lock, not compatible with with reads or writes against the subject table and transaction-wide, thr_lock.c deadlock avoidance algorithm that used to ensure absence of deadlocks between LOCK TABLES WRITE and other statements is no longer sufficient, even for MyISAM. The wait-for graph based deadlock detector of MDL subsystem may sometimes be necessary and is involved. This may lead to ER_LOCK_DEADLOCK error produced for multi-statement transactions even if these only use MyISAM: session 1: session 2: begin; update t1 ... lock table t2 write, t1 write; -- gets a lock on t2, blocks on t1 update t2 ... (ER_LOCK_DEADLOCK) - Finally, support of LOW_PRIORITY option for LOCK TABLES ... WRITE was abandoned. LOCK TABLE ... LOW_PRIORITY WRITE from now on has the same priority as the usual LOCK TABLE ... WRITE. SELECT HIGH PRIORITY no longer trumps LOCK TABLE ... WRITE in the wait queue. - We do not take upgradable metadata locks on implicitly locked tables. So if one has, say, a view v1 that uses table t1, and issues: LOCK TABLE v1 WRITE; FLUSH TABLE t1; -- (or just 'FLUSH TABLES'), an error is produced. In order to be able to perform DDL on a table under LOCK TABLES, the table must be locked explicitly in the LOCK TABLES list. mysql-test/include/handler.inc: Adjusted test case to trigger an execution path on which bug 41110 "crash with handler command when used concurrently with alter table" and bug 41112 "crash in mysql_ha_close_table/get_lock_data with alter table" were originally discovered. Left old test case which no longer triggers this execution path for the sake of coverage. Added test coverage for HANDLER SQL statements and type-aware metadata locks. Added a test for the global shared lock and HANDLER SQL. Updated tests to take into account that the old simple deadlock detection heuristics was replaced with a graph-based deadlock detector. mysql-test/r/debug_sync.result: Updated results (see debug_sync.test). mysql-test/r/handler_innodb.result: Updated results (see handler.inc test). mysql-test/r/handler_myisam.result: Updated results (see handler.inc test). mysql-test/r/innodb-lock.result: Updated results (see innodb-lock.test). mysql-test/r/innodb_mysql_lock.result: Updated results (see innodb_mysql_lock.test). mysql-test/r/lock.result: Updated results (see lock.test). mysql-test/r/lock_multi.result: Updated results (see lock_multi.test). mysql-test/r/lock_sync.result: Updated results (see lock_sync.test). mysql-test/r/mdl_sync.result: Updated results (see mdl_sync.test). mysql-test/r/sp-threads.result: SHOW PROCESSLIST output has changed due to the fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/r/truncate_coverage.result: Updated results (see truncate_coverage.test). mysql-test/suite/funcs_1/datadict/processlist_val.inc: SELECT FROM I_S.PROCESSLIST output has changed due to fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/suite/funcs_1/r/processlist_val_no_prot.result: SELECT FROM I_S.PROCESSLIST output has changed due to fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/suite/rpl/t/rpl_sp.test: Updated to a new SHOW PROCESSLIST state name. mysql-test/t/debug_sync.test: Use LOCK TABLES READ instead of LOCK TABLES WRITE as the latter no longer allows to trigger execution path involving waiting on thr_lock.c lock and therefore reaching debug sync-point covered by this test. mysql-test/t/innodb-lock.test: Adjusted test case to the fact that innodb_table_locks=0 option is no longer supported, since LOCK TABLES WRITE handles all its conflicts within MDL subsystem. mysql-test/t/innodb_mysql_lock.test: Added test for bug #37346 "innodb does not detect deadlock between update and alter table". mysql-test/t/lock.test: Added test coverage which checks the fact that we no longer support DDL under LOCK TABLES on tables which were locked implicitly. Adjusted existing test cases accordingly. mysql-test/t/lock_multi.test: Added test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock". Adjusted other test cases to take into account the fact that waiting for LOCK TABLES ... WRITE now happens within MDL subsystem. mysql-test/t/lock_sync.test: Since LOCK TABLES ... WRITE now takes SNRW metadata lock for tables locked explicitly we have to implicitly lock InnoDB tables (through view) to trigger the table-level lock conflict between TL_WRITE and TL_WRITE_ALLOW_WRITE. mysql-test/t/mdl_sync.test: Added basic test coverage for type-of-operation-aware metadata locks. Also covered with tests some use cases involving HANDLER statements in which a deadlock could arise. Adjusted existing tests to take type-of-operation-aware MDL into account. mysql-test/t/multi_update.test: Update to a new SHOW PROCESSLIST state name. mysql-test/t/truncate_coverage.test: Adjusted test case after making LOCK TABLES WRITE to wait until transactions that use the table to be locked are completed. Updated to the changed name of DEBUG_SYNC point. sql/handler.cc: Global read lock functionality has been moved into a class. sql/lock.cc: Global read lock functionality has been moved into a class. Updated code to use the new MDL API. sql/mdl.cc: Introduced new type-of-operation aware metadata locks. To do this: - Changed MDL_lock to use one list for waiting requests and one list for granted requests. For each list, added a bitmap that holds information what lock types a list contains. Added a helper class MDL_lock::List to manipulate with granted and waited lists while keeping the bitmaps in sync with list contents. - Changed lock-compatibility functions to use bitmaps that define compatibility. - Introduced a graph based deadlock detector inspired by waiting_threads.c from Maria implementation. - Now that we have a deadlock detector, and no longer have a global lock to protect individual lock objects, but rather use an rw lock per object, removed redundant code for upgrade, and the global read lock. Changed the MDL API to no longer require the caller to acquire the global intention exclusive lock by means of a separate method. Removed a few more methods that became redundant. - Removed deadlock detection heuristic, it has been made obsolete by the deadlock detector. - With operation-type-aware metadata locks, MDL subsystem has become aware of potential conflicts between DDL and open transactions. This made it possible to remove calls to mysql_abort_transactions_with_shared_lock() from acquisition paths for exclusive lock and lock upgrade. Now we can simply wait for these transactions to complete without fear of deadlock. Function mysql_lock_abort() has also become unnecessary for all conflicting cases except when a DDL conflicts with a connection that has an open HANDLER. sql/mdl.h: Introduced new type-of-operation aware metadata locks. Introduced a graph based deadlock detector and supporting methods. Added comments. God rid of redundant API calls. Renamed m_lt_or_ha_sentinel to m_trans_sentinel, since now it guards the global read lock as well as LOCK TABLES and HANDLER locks. sql/mysql_priv.h: Moved the global read lock functionality into a class. Added MYSQL_OPEN_FORCE_SHARED_MDL flag which forces open_tables() to take MDL_SHARED on tables instead of metadata locks specified in the parser. We use this to allow PREPARE run concurrently in presence of LOCK TABLES ... WRITE. Added signature for find_table_for_mdl_ugprade(). sql/set_var.cc: Global read lock functionality has been moved into a class. sql/sp_head.cc: When creating TABLE_LIST elements for prelocking or system tables set the type of request for metadata lock according to the operation that will be performed on the table. sql/sql_base.cc: - Updated code to use the new MDL API. - In order to avoid locks starvation we take upgradable locks all at once. As result implicitly locked tables no longer get an upgradable lock. Consequently DDL and FLUSH TABLES for such tables is prohibited. find_write_locked_table() was replaced by find_table_for_mdl_upgrade() function. open_table() was adjusted to return TABLE instance with upgradable ticket when necessary. - We no longer wait for all locks on OT_WAIT back off action -- only on the lock that caused the wait conflict. Moreover, now we distinguish cases when we have to wait due to conflict in MDL and old version of table in TDC. - Upate mysql_notify_threads_having_share_locks() to only abort thr_lock.c waits of threads that have open HANDLERs, since lock conflicts with only these threads now can lead to deadlocks not detectable by the MDL deadlock detector. - Remove mysql_abort_transactions_with_shared_locks() which is no longer needed. sql/sql_class.cc: Global read lock functionality has been moved into a class. Re-arranged code in THD::cleanup() to simplify assert. sql/sql_class.h: Introduced class to incapsulate global read lock functionality. Now sentinel in MDL subsystem guards the global read lock as well as LOCK TABLES and HANDLER locks. Adjusted code accordingly. sql/sql_db.cc: Global read lock functionality has been moved into a class. sql/sql_delete.cc: We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result TRUNCATE TABLE is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_handler.cc: Inform MDL_context about presence of open HANDLERs. Since HANLDERs break MDL protocol by acquiring table-level lock while holding only S metadata lock on a table MDL subsystem should take special care about such contexts (Now this is the only case when mysql_lock_abort() is used). sql/sql_parse.cc: Global read lock functionality has been moved into a class. Do not take upgradable metadata locks when opening tables for CREATE TABLE SELECT as it is not necessary and limits concurrency. When initializing TABLE_LIST objects before adding them to the table list set the type of request for metadata lock according to the operation that will be performed on the table. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result FLUSH TABLES is no longer allowed for such tables. sql/sql_prepare.cc: Use MYSQL_OPEN_FORCE_SHARED_MDL flag when opening tables during PREPARE. This allows PREPARE to run concurrently in presence of LOCK TABLES ... WRITE. sql/sql_rename.cc: Global read lock functionality has been moved into a class. sql/sql_show.cc: Updated code to use the new MDL API. sql/sql_table.cc: Global read lock functionality has been moved into a class. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result DROP TABLE is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_trigger.cc: Global read lock functionality has been moved into a class. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result CREATE/DROP TRIGGER is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_view.cc: Global read lock functionality has been moved into a class. Fixed results of wrong merge that led to misuse of GLR API. CREATE VIEW statement is not a commit statement. sql/table.cc: When resetting TABLE_LIST objects for PS or SP re-execution set the type of request for metadata lock according to the operation that will be performed on the table. Do the same in auxiliary function initializing metadata lock requests in a table list. sql/table.h: When initializing TABLE_LIST objects set the type of request for metadata lock according to the operation that will be performed on the table. sql/transaction.cc: Global read lock functionality has been moved into a class.
-rw-r--r--mysql-test/include/handler.inc178
-rw-r--r--mysql-test/r/debug_sync.result2
-rw-r--r--mysql-test/r/handler_innodb.result154
-rw-r--r--mysql-test/r/handler_myisam.result154
-rw-r--r--mysql-test/r/innodb-lock.result39
-rw-r--r--mysql-test/r/innodb_mysql_lock.result32
-rw-r--r--mysql-test/r/lock.result79
-rw-r--r--mysql-test/r/lock_multi.result67
-rw-r--r--mysql-test/r/lock_sync.result7
-rw-r--r--mysql-test/r/mdl_sync.result1854
-rw-r--r--mysql-test/r/sp-threads.result2
-rw-r--r--mysql-test/r/truncate_coverage.result21
-rw-r--r--mysql-test/suite/funcs_1/datadict/processlist_val.inc2
-rw-r--r--mysql-test/suite/funcs_1/r/processlist_val_no_prot.result4
-rw-r--r--mysql-test/suite/rpl/t/rpl_sp.test2
-rw-r--r--mysql-test/t/debug_sync.test2
-rw-r--r--mysql-test/t/innodb-lock.test49
-rw-r--r--mysql-test/t/innodb_mysql_lock.test54
-rw-r--r--mysql-test/t/lock.test76
-rw-r--r--mysql-test/t/lock_multi.test113
-rw-r--r--mysql-test/t/lock_sync.test9
-rw-r--r--mysql-test/t/mdl_sync.test2472
-rw-r--r--mysql-test/t/multi_update.test11
-rw-r--r--mysql-test/t/truncate_coverage.test39
-rw-r--r--sql/handler.cc4
-rw-r--r--sql/lock.cc86
-rw-r--r--sql/mdl.cc1890
-rw-r--r--sql/mdl.h384
-rw-r--r--sql/mysql_priv.h15
-rw-r--r--sql/set_var.cc10
-rw-r--r--sql/sp_head.cc6
-rw-r--r--sql/sql_base.cc385
-rw-r--r--sql/sql_class.cc20
-rw-r--r--sql/sql_class.h55
-rw-r--r--sql/sql_db.cc12
-rw-r--r--sql/sql_delete.cc24
-rw-r--r--sql/sql_handler.cc19
-rw-r--r--sql/sql_parse.cc41
-rw-r--r--sql/sql_prepare.cc27
-rw-r--r--sql/sql_rename.cc4
-rw-r--r--sql/sql_show.cc6
-rw-r--r--sql/sql_table.cc53
-rw-r--r--sql/sql_trigger.cc18
-rw-r--r--sql/sql_view.cc4
-rw-r--r--sql/table.cc12
-rw-r--r--sql/table.h4
-rw-r--r--sql/transaction.cc4
47 files changed, 6701 insertions, 1804 deletions
diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc
index 8342a072ef9..e16c53bc1ee 100644
--- a/mysql-test/include/handler.inc
+++ b/mysql-test/include/handler.inc
@@ -732,10 +732,13 @@ connection default;
--disable_warnings
drop table if exists t1;
--enable_warnings
-create table t1 (a int, key a (a));
+--echo # First test case which is supposed trigger the execution
+--echo # path on which problem was discovered.
+create table t1 (a int);
insert into t1 values (1);
handler t1 open;
connection con1;
+lock table t1 write;
send alter table t1 engine=memory;
connection con2;
let $wait_condition=
@@ -743,10 +746,34 @@ let $wait_condition=
where state = "Waiting for table" and info = "alter table t1 engine=memory";
--source include/wait_condition.inc
connection default;
+--error ER_ILLEGAL_HA
handler t1 read a next;
handler t1 close;
connection con1;
--reap
+unlock tables;
+drop table t1;
+--echo # Now test case which was reported originally but which no longer
+--echo # triggers execution path which has caused the problem.
+connection default;
+create table t1 (a int, key(a));
+insert into t1 values (1);
+handler t1 open;
+connection con1;
+send alter table t1 engine=memory;
+connection con2;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 engine=memory";
+--source include/wait_condition.inc
+connection default;
+--echo # Since S metadata lock was already acquired at HANDLER OPEN time
+--echo # and TL_READ lock requested by HANDLER READ is compatible with
+--echo # ALTER's TL_WRITE_ALLOW_READ the below statement should succeed
+--echo # without waiting. The old version of table should be used in it.
+handler t1 read a next;
+handler t1 close;
+connection con1;
drop table t1;
disconnect con1;
--source include/wait_until_disconnected.inc
@@ -1228,15 +1255,27 @@ create table t2 like t1;
handler t1 open;
--echo # --> connection con1
connection con1;
-lock table t2 read;
+lock table t1 write, t2 write;
--echo # --> connection default
connection default;
+send drop table t2;
+--echo # --> connection con2
+connection con2;
+--echo # Waiting for 'drop table t2' to get blocked...
+let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
+--source include/wait_condition.inc
+--echo # --> connection con1
+connection con1;
--error ER_LOCK_DEADLOCK
-drop table t2;
---error ER_LOCK_DEADLOCK
-rename table t2 to t3;
+drop table t1;
+unlock tables;
+--echo # --> connection default
+connection default;
+reap;
+
--echo # Demonstrate that there is no deadlock with FLUSH TABLE,
--echo # even though it is waiting for the other table to go away
+create table t2 like t1;
--echo # Sending:
--send flush table t2
--echo # --> connection con2
@@ -1256,6 +1295,7 @@ drop table t2;
--echo # lead to deadlocks
--echo #
create table t1 (a int, key a(a));
+insert into t1 values (1), (2);
--echo # --> connection default
connection default;
@@ -1265,7 +1305,31 @@ handler t1 open;
--echo # --> connection con1
connection con1;
-lock tables t1 write;
+--echo # Sending:
+--send lock tables t1 write
+
+--echo # --> connection con2
+connection con2;
+--echo # Check that 'lock tables t1 write' waits until transaction which
+--echo # has read from the table commits.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock tables t1 write";
+--source include/wait_condition.inc
+
+--echo # --> connection default
+connection default;
+--echo # The below 'handler t1 read ...' should not be blocked as
+--echo # 'lock tables t1 write' has not succeeded yet.
+handler t1 read a next;
+
+--echo # Unblock 'lock tables t1 write'.
+commit;
+
+--echo # --> connection con1
+connection con1;
+--echo # Reap 'lock tables t1 write'.
+--reap
--echo # --> connection default
connection default;
@@ -1279,29 +1343,18 @@ let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "handler t1 read a next";
--source include/wait_condition.inc
---echo # Sending:
---send drop table t1
---echo # --> connection con2
-connection con2;
---echo # Waiting for 'drop table t1' to get blocked...
-let $wait_condition=
- select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info = "drop table t1";
---source include/wait_condition.inc
+--echo # The below 'drop table t1' should be able to proceed without
+--echo # waiting as it will force HANDLER to be closed.
+drop table t1;
+unlock tables;
--echo # --> connection default
connection default;
--echo # Reaping 'handler t1 read a next'...
---error ER_LOCK_DEADLOCK
+--error ER_NO_SUCH_TABLE
--reap
handler t1 close;
-commit;
-
---echo # --> connection con1
-connection con1;
---echo # Reaping 'drop table t1'...
---reap
--echo # --> connection con1
connection con1;
@@ -1357,3 +1410,84 @@ rename table t4 to t5, t3 to t4, t5 to t3;
handler t1 read first;
handler t2 read first;
drop table t1, t2, t3, t4;
+
+--echo #
+--echo # A test for FLUSH TABLES WITH READ LOCK and HANDLER statements.
+--echo #
+set autocommit=0;
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+create table t2 like t1;
+insert into t2 (a, b) select a, b from t1;
+create table t3 like t1;
+insert into t3 (a, b) select a, b from t1;
+commit;
+flush tables with read lock;
+handler t1 open;
+lock table t1 read;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+handler t1 read next;
+--echo # This implicitly leaves LOCK TABLES but doesn't drop the GLR
+--error ER_NO_SUCH_TABLE
+lock table not_exists_write read;
+--echo # We still have the read lock.
+--error ER_CANT_UPDATE_WITH_READLOCK
+drop table t1;
+handler t1 open;
+select a from t2;
+handler t1 read next;
+flush tables with read lock;
+handler t2 open;
+flush tables with read lock;
+handler t1 read next;
+select a from t3;
+handler t2 read next;
+handler t1 close;
+rollback;
+handler t2 close;
+--error ER_CANT_UPDATE_WITH_READLOCK
+drop table t1;
+commit;
+flush tables;
+--error ER_CANT_UPDATE_WITH_READLOCK
+drop table t1;
+unlock tables;
+drop table t1;
+set autocommit=default;
+drop table t2, t3;
+
+--echo #
+--echo # HANDLER statement and operation-type aware metadata locks.
+--echo # Check that when we clone a ticket for HANDLER we downrade
+--echo # the lock.
+--echo #
+--echo # Establish an auxiliary connection con1.
+connect (con1,localhost,root,,);
+--echo # -> connection default
+connection default;
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+begin;
+insert into t1 (a, b) values (6, 6);
+handler t1 open;
+handler t1 read a last;
+insert into t1 (a, b) values (7, 7);
+handler t1 read a last;
+commit;
+--echo # -> connection con1
+connection con1;
+--echo # Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE.
+lock table t1 write;
+unlock tables;
+--echo # -> connection default
+connection default;
+handler t1 read a prev;
+handler t1 close;
+--echo # Cleanup.
+drop table t1;
+--echo # -> connection con1
+connection con1;
+disconnect con1;
+--source include/wait_until_disconnected.inc
+--echo # -> connection default
+connection default;
diff --git a/mysql-test/r/debug_sync.result b/mysql-test/r/debug_sync.result
index 8b46334204c..25fdf523200 100644
--- a/mysql-test/r/debug_sync.result
+++ b/mysql-test/r/debug_sync.result
@@ -263,7 +263,7 @@ DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (c1 INT);
-LOCK TABLE t1 WRITE;
+LOCK TABLE t1 READ;
connection con1
SET DEBUG_SYNC= 'wait_for_lock SIGNAL locked EXECUTE 2';
INSERT INTO t1 VALUES (1);
diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result
index a3e3e325e7d..6ed7b572403 100644
--- a/mysql-test/r/handler_innodb.result
+++ b/mysql-test/r/handler_innodb.result
@@ -745,11 +745,29 @@ drop table t1;
handler t1 read a next;
ERROR 42S02: Unknown table 't1' in HANDLER
drop table if exists t1;
-create table t1 (a int, key a (a));
+# First test case which is supposed trigger the execution
+# path on which problem was discovered.
+create table t1 (a int);
insert into t1 values (1);
handler t1 open;
+lock table t1 write;
alter table t1 engine=memory;
handler t1 read a next;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+handler t1 close;
+unlock tables;
+drop table t1;
+# Now test case which was reported originally but which no longer
+# triggers execution path which has caused the problem.
+create table t1 (a int, key(a));
+insert into t1 values (1);
+handler t1 open;
+alter table t1 engine=memory;
+# Since S metadata lock was already acquired at HANDLER OPEN time
+# and TL_READ lock requested by HANDLER READ is compatible with
+# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed
+# without waiting. The old version of table should be used in it.
+handler t1 read a next;
a
1
handler t1 close;
@@ -1217,14 +1235,19 @@ create table t1 (a int, key a(a));
create table t2 like t1;
handler t1 open;
# --> connection con1
-lock table t2 read;
+lock table t1 write, t2 write;
# --> connection default
drop table t2;
+# --> connection con2
+# Waiting for 'drop table t2' to get blocked...
+# --> connection con1
+drop table t1;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
-rename table t2 to t3;
-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+unlock tables;
+# --> connection default
# Demonstrate that there is no deadlock with FLUSH TABLE,
# even though it is waiting for the other table to go away
+create table t2 like t1;
# Sending:
flush table t2;
# --> connection con2
@@ -1239,29 +1262,43 @@ drop table t2;
# lead to deadlocks
#
create table t1 (a int, key a(a));
+insert into t1 values (1), (2);
# --> connection default
begin;
select * from t1;
a
+1
+2
handler t1 open;
# --> connection con1
+# Sending:
lock tables t1 write;
+# --> connection con2
+# Check that 'lock tables t1 write' waits until transaction which
+# has read from the table commits.
+# --> connection default
+# The below 'handler t1 read ...' should not be blocked as
+# 'lock tables t1 write' has not succeeded yet.
+handler t1 read a next;
+a
+1
+# Unblock 'lock tables t1 write'.
+commit;
+# --> connection con1
+# Reap 'lock tables t1 write'.
# --> connection default
# Sending:
handler t1 read a next;
# --> connection con1
# Waiting for 'handler t1 read a next' to get blocked...
-# Sending:
+# The below 'drop table t1' should be able to proceed without
+# waiting as it will force HANDLER to be closed.
drop table t1;
-# --> connection con2
-# Waiting for 'drop table t1' to get blocked...
+unlock tables;
# --> connection default
# Reaping 'handler t1 read a next'...
-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+ERROR 42S02: Table 'test.t1' doesn't exist
handler t1 close;
-commit;
-# --> connection con1
-# Reaping 'drop table t1'...
# --> connection con1
# --> connection con2
# --> connection con3
@@ -1324,3 +1361,98 @@ a b
handler t2 read first;
a b
drop table t1, t2, t3, t4;
+#
+# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements.
+#
+set autocommit=0;
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+create table t2 like t1;
+insert into t2 (a, b) select a, b from t1;
+create table t3 like t1;
+insert into t3 (a, b) select a, b from t1;
+commit;
+flush tables with read lock;
+handler t1 open;
+lock table t1 read;
+handler t1 read next;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# This implicitly leaves LOCK TABLES but doesn't drop the GLR
+lock table not_exists_write read;
+ERROR 42S02: Table 'test.not_exists_write' doesn't exist
+# We still have the read lock.
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+handler t1 open;
+select a from t2;
+a
+1
+2
+3
+4
+5
+handler t1 read next;
+a b
+1 1
+flush tables with read lock;
+handler t2 open;
+flush tables with read lock;
+handler t1 read next;
+a b
+1 1
+select a from t3;
+a
+1
+2
+3
+4
+5
+handler t2 read next;
+a b
+1 1
+handler t1 close;
+rollback;
+handler t2 close;
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+commit;
+flush tables;
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+unlock tables;
+drop table t1;
+set autocommit=default;
+drop table t2, t3;
+#
+# HANDLER statement and operation-type aware metadata locks.
+# Check that when we clone a ticket for HANDLER we downrade
+# the lock.
+#
+# Establish an auxiliary connection con1.
+# -> connection default
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+begin;
+insert into t1 (a, b) values (6, 6);
+handler t1 open;
+handler t1 read a last;
+a b
+6 6
+insert into t1 (a, b) values (7, 7);
+handler t1 read a last;
+a b
+7 7
+commit;
+# -> connection con1
+# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE.
+lock table t1 write;
+unlock tables;
+# -> connection default
+handler t1 read a prev;
+a b
+6 6
+handler t1 close;
+# Cleanup.
+drop table t1;
+# -> connection con1
+# -> connection default
diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result
index f5c5bfebd15..0dd039eeb23 100644
--- a/mysql-test/r/handler_myisam.result
+++ b/mysql-test/r/handler_myisam.result
@@ -743,11 +743,29 @@ drop table t1;
handler t1 read a next;
ERROR 42S02: Unknown table 't1' in HANDLER
drop table if exists t1;
-create table t1 (a int, key a (a));
+# First test case which is supposed trigger the execution
+# path on which problem was discovered.
+create table t1 (a int);
insert into t1 values (1);
handler t1 open;
+lock table t1 write;
alter table t1 engine=memory;
handler t1 read a next;
+ERROR HY000: Table storage engine for 't1' doesn't have this option
+handler t1 close;
+unlock tables;
+drop table t1;
+# Now test case which was reported originally but which no longer
+# triggers execution path which has caused the problem.
+create table t1 (a int, key(a));
+insert into t1 values (1);
+handler t1 open;
+alter table t1 engine=memory;
+# Since S metadata lock was already acquired at HANDLER OPEN time
+# and TL_READ lock requested by HANDLER READ is compatible with
+# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed
+# without waiting. The old version of table should be used in it.
+handler t1 read a next;
a
1
handler t1 close;
@@ -1214,14 +1232,19 @@ create table t1 (a int, key a(a));
create table t2 like t1;
handler t1 open;
# --> connection con1
-lock table t2 read;
+lock table t1 write, t2 write;
# --> connection default
drop table t2;
+# --> connection con2
+# Waiting for 'drop table t2' to get blocked...
+# --> connection con1
+drop table t1;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
-rename table t2 to t3;
-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+unlock tables;
+# --> connection default
# Demonstrate that there is no deadlock with FLUSH TABLE,
# even though it is waiting for the other table to go away
+create table t2 like t1;
# Sending:
flush table t2;
# --> connection con2
@@ -1236,29 +1259,43 @@ drop table t2;
# lead to deadlocks
#
create table t1 (a int, key a(a));
+insert into t1 values (1), (2);
# --> connection default
begin;
select * from t1;
a
+1
+2
handler t1 open;
# --> connection con1
+# Sending:
lock tables t1 write;
+# --> connection con2
+# Check that 'lock tables t1 write' waits until transaction which
+# has read from the table commits.
+# --> connection default
+# The below 'handler t1 read ...' should not be blocked as
+# 'lock tables t1 write' has not succeeded yet.
+handler t1 read a next;
+a
+1
+# Unblock 'lock tables t1 write'.
+commit;
+# --> connection con1
+# Reap 'lock tables t1 write'.
# --> connection default
# Sending:
handler t1 read a next;
# --> connection con1
# Waiting for 'handler t1 read a next' to get blocked...
-# Sending:
+# The below 'drop table t1' should be able to proceed without
+# waiting as it will force HANDLER to be closed.
drop table t1;
-# --> connection con2
-# Waiting for 'drop table t1' to get blocked...
+unlock tables;
# --> connection default
# Reaping 'handler t1 read a next'...
-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+ERROR 42S02: Table 'test.t1' doesn't exist
handler t1 close;
-commit;
-# --> connection con1
-# Reaping 'drop table t1'...
# --> connection con1
# --> connection con2
# --> connection con3
@@ -1322,6 +1359,101 @@ handler t2 read first;
a b
drop table t1, t2, t3, t4;
#
+# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements.
+#
+set autocommit=0;
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+create table t2 like t1;
+insert into t2 (a, b) select a, b from t1;
+create table t3 like t1;
+insert into t3 (a, b) select a, b from t1;
+commit;
+flush tables with read lock;
+handler t1 open;
+lock table t1 read;
+handler t1 read next;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# This implicitly leaves LOCK TABLES but doesn't drop the GLR
+lock table not_exists_write read;
+ERROR 42S02: Table 'test.not_exists_write' doesn't exist
+# We still have the read lock.
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+handler t1 open;
+select a from t2;
+a
+1
+2
+3
+4
+5
+handler t1 read next;
+a b
+1 1
+flush tables with read lock;
+handler t2 open;
+flush tables with read lock;
+handler t1 read next;
+a b
+1 1
+select a from t3;
+a
+1
+2
+3
+4
+5
+handler t2 read next;
+a b
+1 1
+handler t1 close;
+rollback;
+handler t2 close;
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+commit;
+flush tables;
+drop table t1;
+ERROR HY000: Can't execute the query because you have a conflicting read lock
+unlock tables;
+drop table t1;
+set autocommit=default;
+drop table t2, t3;
+#
+# HANDLER statement and operation-type aware metadata locks.
+# Check that when we clone a ticket for HANDLER we downrade
+# the lock.
+#
+# Establish an auxiliary connection con1.
+# -> connection default
+create table t1 (a int, b int, key a (a));
+insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
+begin;
+insert into t1 (a, b) values (6, 6);
+handler t1 open;
+handler t1 read a last;
+a b
+6 6
+insert into t1 (a, b) values (7, 7);
+handler t1 read a last;
+a b
+7 7
+commit;
+# -> connection con1
+# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE.
+lock table t1 write;
+unlock tables;
+# -> connection default
+handler t1 read a prev;
+a b
+6 6
+handler t1 close;
+# Cleanup.
+drop table t1;
+# -> connection con1
+# -> connection default
+#
# BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash
#
CREATE TABLE t1 AS SELECT 1 AS f1;
diff --git a/mysql-test/r/innodb-lock.result b/mysql-test/r/innodb-lock.result
index 4ace4065c34..ab7e9aa7b25 100644
--- a/mysql-test/r/innodb-lock.result
+++ b/mysql-test/r/innodb-lock.result
@@ -25,6 +25,12 @@ id x
0 2
commit;
drop table t1;
+#
+# Old lock method (where LOCK TABLE was ignored by InnoDB) no longer
+# works due to fix for bugs #46272 "MySQL 5.4.4, new MDL: unnecessary
+# deadlock" and bug #37346 "innodb does not detect deadlock between
+# update and alter table".
+#
set @@innodb_table_locks=0;
create table t1 (id integer primary key, x integer) engine=INNODB;
insert into t1 values(0, 0),(1,1),(2,2);
@@ -32,26 +38,27 @@ commit;
SELECT * from t1 where id = 0 FOR UPDATE;
id x
0 0
+# Connection 'con2'.
set autocommit=0;
set @@innodb_table_locks=0;
-lock table t1 write;
-update t1 set x=10 where id = 2;
-SELECT * from t1 where id = 2;
-id x
-2 2
-UPDATE t1 set x=3 where id = 2;
-commit;
-SELECT * from t1;
+# The following statement should block because SQL-level lock
+# is taken on t1 which will wait until concurrent transaction
+# is commited.
+# Sending:
+lock table t1 write;;
+# Connection 'con1'.
+# Wait until LOCK TABLE is blocked on SQL-level lock.
+# We should be able to do UPDATEs and SELECTs within transaction.
+update t1 set x=1 where id = 0;
+select * from t1;
id x
-0 0
+0 1
1 1
-2 3
+2 2
+# Unblock LOCK TABLE.
commit;
+# Connection 'con2'.
+# Reap LOCK TABLE.
unlock tables;
-commit;
-select * from t1;
-id x
-0 0
-1 1
-2 10
+# Connection 'con1'.
drop table t1;
diff --git a/mysql-test/r/innodb_mysql_lock.result b/mysql-test/r/innodb_mysql_lock.result
index 374f67358eb..375ae8aeb12 100644
--- a/mysql-test/r/innodb_mysql_lock.result
+++ b/mysql-test/r/innodb_mysql_lock.result
@@ -26,6 +26,38 @@ commit;
set @@autocommit=1;
set @@autocommit=1;
#
+# Test for bug #37346 "innodb does not detect deadlock between update
+# and alter table".
+#
+drop table if exists t1;
+create table t1 (c1 int primary key, c2 int, c3 int) engine=InnoDB;
+insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
+begin;
+# Run statement which acquires X-lock on one of table's rows.
+update t1 set c3=c3+1 where c2=3;
+#
+# Switching to connection 'con37346'.
+# The below ALTER TABLE statement should wait till transaction
+# in connection 'default' is complete and then succeed.
+# It should not deadlock or fail with ER_LOCK_DEADLOCK error.
+# Sending:
+alter table t1 add column c4 int;;
+#
+# Switching to connection 'default'.
+# Wait until the above ALTER TABLE gets blocked because this
+# connection holds SW metadata lock on table to be altered.
+# The below statement should succeed. It should not
+# deadlock or end with ER_LOCK_DEADLOCK error.
+update t1 set c3=c3+1 where c2=4;
+# Unblock ALTER TABLE by committing transaction.
+commit;
+#
+# Switching to connection 'con37346'.
+# Reaping ALTER TABLE.
+#
+# Switching to connection 'default'.
+drop table t1;
+#
# Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB
# table cause warnings in errlog
#
diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result
index 5dbfa66b43e..c1e1ccb5bce 100644
--- a/mysql-test/r/lock.result
+++ b/mysql-test/r/lock.result
@@ -151,6 +151,12 @@ select * from t2;
a
select * from t3;
ERROR HY000: Table 't3' was not locked with LOCK TABLES
+Dropping of implicitly locked table is disallowed.
+drop table t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+Now let us also lock table explicitly and drop it.
+lock tables t1 write, v_bug5719 write;
drop table t1;
sic: left LOCK TABLES mode
@@ -282,6 +288,79 @@ insert into t1 values (1);
# Ensure that metadata locks held by the transaction are released.
drop table t1;
#
+# Coverage for situations when we try to execute DDL on tables
+# which are locked by LOCK TABLES only implicitly.
+#
+drop tables if exists t1, t2;
+drop view if exists v1;
+drop function if exists f1;
+create table t1 (i int);
+create table t2 (j int);
+#
+# Try to perform DDL on table which is locked through view.
+create view v1 as select * from t2;
+lock tables t1 write, v1 write;
+flush table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+drop table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+alter table t2 add column k int;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+# Repair produces error as part of its result set.
+repair table t2;
+Table Op Msg_type Msg_text
+test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated
+test.t2 repair status Operation failed
+unlock tables;
+drop view v1;
+#
+# Now, try DDL on table which is locked through routine.
+create function f1 () returns int
+begin
+insert into t2 values (1);
+return 0;
+end|
+create view v1 as select f1() from t1;
+lock tables v1 read;
+flush table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+drop table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+alter table t2 add column k int;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+# Repair produces error as part of its result set.
+repair table t2;
+Table Op Msg_type Msg_text
+test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated
+test.t2 repair status Operation failed
+unlock tables;
+drop view v1;
+drop function f1;
+#
+# Finally, try DDL on table which is locked thanks to trigger.
+create trigger t1_ai after insert on t1 for each row insert into t2 values (1);
+lock tables t1 write;
+flush table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+drop table t2;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+alter table t2 add column k int;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+# Repair produces error as part of its result set.
+repair table t2;
+Table Op Msg_type Msg_text
+test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated
+test.t2 repair status Operation failed
+unlock tables;
+drop trigger t1_ai;
+drop tables t1, t2;
+#
# Bug#45035 " Altering table under LOCK TABLES results in
# "Error 1213 Deadlock found..."
#
diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result
index 5d12e0efd64..4b08c175ee2 100644
--- a/mysql-test/r/lock_multi.result
+++ b/mysql-test/r/lock_multi.result
@@ -1,21 +1,39 @@
drop table if exists t1,t2;
create table t1(n int);
insert into t1 values (1);
-lock tables t1 write;
+select get_lock("mysqltest_lock", 100);
+get_lock("mysqltest_lock", 100)
+1
+update t1 set n = 2 and get_lock('mysqltest_lock', 100);
update low_priority t1 set n = 4;
select n from t1;
-unlock tables;
+select release_lock("mysqltest_lock");
+release_lock("mysqltest_lock")
+1
+select release_lock("mysqltest_lock");
+release_lock("mysqltest_lock")
+1
n
4
drop table t1;
create table t1(n int);
insert into t1 values (1);
-lock tables t1 read;
+select get_lock("mysqltest_lock", 100);
+get_lock("mysqltest_lock", 100)
+1
+select n from t1 where get_lock('mysqltest_lock', 100);
update low_priority t1 set n = 4;
select n from t1;
n
1
-unlock tables;
+select release_lock("mysqltest_lock");
+release_lock("mysqltest_lock")
+1
+n
+1
+select release_lock("mysqltest_lock");
+release_lock("mysqltest_lock")
+1
drop table t1;
create table t1 (a int, b int);
create table t2 (c int, d int);
@@ -35,6 +53,7 @@ create table t2 (a int);
lock table t1 write, t2 write;
insert t1 select * from t2;
drop table t2;
+unlock tables;
ERROR 42S02: Table 'test.t2' doesn't exist
drop table t1;
create table t1 (a int);
@@ -42,6 +61,7 @@ create table t2 (a int);
lock table t1 write, t2 write, t1 as t1_2 write, t2 as t2_2 write;
insert t1 select * from t2;
drop table t2;
+unlock tables;
ERROR 42S02: Table 'test.t2' doesn't exist
drop table t1;
End of 4.1 tests
@@ -221,6 +241,36 @@ connection: default
flush tables;
drop table t1;
#
+# Test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock".
+#
+drop table if exists t1;
+create table t1 (c1 int primary key, c2 int, c3 int);
+insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
+begin;
+update t1 set c3=c3+1 where c2=3;
+#
+# Switching to connection 'con46272'.
+# The below ALTER TABLE statement should wait till transaction
+# in connection 'default' is complete and then succeed.
+# It should not deadlock or fail with ER_LOCK_DEADLOCK error.
+# Sending:
+alter table t1 add column c4 int;;
+#
+# Switching to connection 'default'.
+# Wait until the above ALTER TABLE gets blocked because this
+# connection holds SW metadata lock on table to be altered.
+# The below statement should succeed. It should not
+# deadlock or end with ER_LOCK_DEADLOCK error.
+update t1 set c3=c3+1 where c2=4;
+# Unblock ALTER TABLE by committing transaction.
+commit;
+#
+# Switching to connection 'con46272'.
+# Reaping ALTER TABLE.
+#
+# Switching to connection 'default'.
+drop table t1;
+#
# Bug#47249 assert in MDL_global_lock::is_lock_type_compatible
#
DROP TABLE IF EXISTS t1;
@@ -228,14 +278,15 @@ DROP VIEW IF EXISTS v1;
#
# Test 1: LOCK TABLES v1 WRITE, t1 READ;
#
+# Thanks to the fact that we no longer allow DDL on tables
+# which are locked for write implicitly, the exact scenario
+# in which assert was failing is no longer repeatable.
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
-# Connection 2
LOCK TABLES v1 WRITE, t1 READ;
FLUSH TABLE t1;
-# Connection 1
-LOCK TABLES t1 WRITE;
-FLUSH TABLE t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+UNLOCK TABLES;
DROP TABLE t1;
DROP VIEW v1;
#
diff --git a/mysql-test/r/lock_sync.result b/mysql-test/r/lock_sync.result
index fc4e8c850f6..0b57b38f5ec 100644
--- a/mysql-test/r/lock_sync.result
+++ b/mysql-test/r/lock_sync.result
@@ -6,6 +6,7 @@
# statements which tried to acquire stronger write lock (TL_WRITE,
# TL_WRITE_ALLOW_READ) on this table might have led to deadlock.
drop table if exists t1;
+drop view if exists v1;
# Create auxiliary connections used through the test.
# Reset DEBUG_SYNC facility before using it.
set debug_sync= 'RESET';
@@ -14,6 +15,9 @@ set debug_sync= 'RESET';
set @old_general_log = @@global.general_log;
set @@global.general_log= OFF;
create table t1 (i int) engine=InnoDB;
+# We have to use view in order to make LOCK TABLES avoid
+# acquiring SNRW metadata lock on table.
+create view v1 as select * from t1;
insert into t1 values (1);
# Prepare user lock which will be used for resuming execution of
# the first statement after it acquires TL_WRITE_ALLOW_WRITE lock.
@@ -36,7 +40,7 @@ select count(*) > 0 from t1 as a, t1 as b for update;;
# acquiring lock for the the first instance of 't1'.
set debug_sync= 'now WAIT_FOR parked';
# Send LOCK TABLE statement which will try to get TL_WRITE lock on 't1':
-lock table t1 write;;
+lock table v1 write;;
# Switch to connection 'default'.
# Wait until this LOCK TABLES statement starts waiting for table lock.
# Allow SELECT ... FOR UPDATE to resume.
@@ -63,4 +67,5 @@ unlock tables;
# Do clean-up.
set debug_sync= 'RESET';
set @@global.general_log= @old_general_log;
+drop view v1;
drop table t1;
diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result
index 8c4d7272e29..8d8672377f0 100644
--- a/mysql-test/r/mdl_sync.result
+++ b/mysql-test/r/mdl_sync.result
@@ -10,7 +10,7 @@ alter table t1 rename t3;
connection: default
set debug_sync= 'now WAIT_FOR parked';
connection: con2
-set debug_sync='mdl_acquire_exclusive_locks_wait SIGNAL go';
+set debug_sync='mdl_acquire_lock_wait SIGNAL go';
drop table t1,t2;
connection: con1
connection: default
@@ -20,6 +20,1740 @@ ERROR 42S02: Unknown table 't1'
drop table t3;
SET DEBUG_SYNC= 'RESET';
#
+# Basic test coverage for type-of-operation aware metadata locks.
+#
+drop table if exists t1, t2, t3;
+set debug_sync= 'RESET';
+create table t1 (c1 int);
+#
+# A) First let us check compatibility rules between differend kinds of
+# type-of-operation aware metadata locks.
+# Of course, these rules are already covered by the tests scattered
+# across the test suite. But it still makes sense to have one place
+# which covers all of them.
+#
+# 1) Acquire S (simple shared) lock on the table (by using HANDLER):
+#
+handler t1 open;
+#
+# Switching to connection 'mdl_con1'.
+# Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+0
+insert into t1 values (1), (1);
+# Check that SNW lock is compatible with it. To do this use ALTER TABLE
+# which will fail after opening the table and thus obtaining SNW metadata
+# lock.
+alter table t1 add primary key (c1);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+# Check that SNRW lock is compatible with S lock.
+lock table t1 write;
+insert into t1 values (1);
+unlock tables;
+# Check that X lock is incompatible with S lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME is blocked because of S lock.
+#
+# Switching to connection 'default'.
+# Unblock RENAME TABLE.
+handler t1 close;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE.
+# Restore the original state of the things.
+rename table t2 to t1;
+#
+# Switching to connection 'default'.
+handler t1 open;
+#
+# Switching to connection 'mdl_con1'.
+# Check that upgrade from SNW to X is blocked by presence of S lock.
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER TABLE is blocked because of S lock.
+#
+# Switching to connection 'default'.
+# Unblock ALTER TABLE.
+handler t1 close;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+# Restore the original state of the things.
+alter table t1 drop column c2;
+#
+# Switching to connection 'default'.
+handler t1 open;
+#
+# Switching to connection 'mdl_con1'.
+# Check that upgrade from SNRW to X is blocked by presence of S lock.
+lock table t1 write;
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked
+# because of S lock.
+#
+# Switching to connection 'default'.
+# Unblock ALTER TABLE.
+handler t1 close;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+# Restore the original state of the things.
+alter table t1 drop column c2;
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+# 2) Acquire SH (shared high-priority) lock on the table.
+# We have to involve DEBUG_SYNC facility for this as usually
+# such kind of locks are short-lived.
+#
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+3
+insert into t1 values (1);
+# Check that SNW lock is compatible with it. To do this use ALTER TABLE
+# which will fail after opening the table and thus obtaining SNW metadata
+# lock.
+alter table t1 add primary key (c1);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+# Check that SNRW lock is compatible with SH lock.
+lock table t1 write;
+delete from t1 limit 1;
+unlock tables;
+# Check that X lock is incompatible with SH lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME is blocked because of SH lock.
+# Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping SELECT ... FROM I_S.
+table_name table_type auto_increment table_comment
+t1 BASE TABLE NULL
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE.
+# Restore the original state of the things.
+rename table t2 to t1;
+#
+# Switching to connection 'default'.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that upgrade from SNW to X is blocked by presence of SH lock.
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER TABLE is blocked because of SH lock.
+# Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping SELECT ... FROM I_S.
+table_name table_type auto_increment table_comment
+t1 BASE TABLE NULL
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+# Restore the original state of the things.
+alter table t1 drop column c2;
+#
+# Switching to connection 'default'.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that upgrade from SNRW to X is blocked by presence of S lock.
+lock table t1 write;
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked
+# because of S lock.
+# Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping SELECT ... FROM I_S.
+table_name table_type auto_increment table_comment
+t1 BASE TABLE NULL
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+# Restore the original state of the things.
+alter table t1 drop column c2;
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+#
+# 3) Acquire SR lock on the table.
+#
+#
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+3
+insert into t1 values (1);
+# Check that SNW lock is compatible with it. To do this use ALTER TABLE
+# which will fail after opening the table and thus obtaining SNW metadata
+# lock.
+alter table t1 add primary key (c1);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+# Check that SNRW lock is not compatible with SR lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'default'.
+# Check that the above LOCK TABLES is blocked because of SR lock.
+# Unblock LOCK TABLES.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLES.
+delete from t1 limit 1;
+unlock tables;
+#
+# Switching to connection 'default'.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Check that X lock is incompatible with SR lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME is blocked because of SR lock.
+#
+# Switching to connection 'default'.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE.
+# Restore the original state of the things.
+rename table t2 to t1;
+#
+# Switching to connection 'default'.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Check that upgrade from SNW to X is blocked by presence of SR lock.
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER TABLE is blocked because of SR lock.
+#
+# Switching to connection 'default'.
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+# Restore the original state of the things.
+alter table t1 drop column c2;
+#
+# There is no need to check that upgrade from SNRW to X is blocked
+# by presence of SR lock because SNRW is incompatible with SR anyway.
+#
+#
+# Switching to connection 'default'.
+#
+#
+# 4) Acquire SW lock on the table.
+#
+#
+begin;
+insert into t1 values (1);
+#
+# Switching to connection 'mdl_con1'.
+# Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+4
+insert into t1 values (1);
+# Check that SNW lock is not compatible with SW lock.
+# Again we use ALTER TABLE which fails after opening
+# the table to avoid upgrade of SNW -> X.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'default'.
+# Check that the above ALTER TABLE is blocked because of SW lock.
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+begin;
+insert into t1 values (1);
+#
+# Switching to connection 'mdl_con1'.
+# Check that SNRW lock is not compatible with SW lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'default'.
+# Check that the above LOCK TABLES is blocked because of SW lock.
+# Unblock LOCK TABLES.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLES.
+delete from t1 limit 2;
+unlock tables;
+#
+# Switching to connection 'default'.
+begin;
+insert into t1 values (1);
+#
+# Switching to connection 'mdl_con1'.
+# Check that X lock is incompatible with SW lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME is blocked because of SW lock.
+#
+# Switching to connection 'default'.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE.
+# Restore the original state of the things.
+rename table t2 to t1;
+#
+# There is no need to check that upgrade from SNW/SNRW to X is
+# blocked by presence of SW lock because SNW/SNRW is incompatible
+# with SW anyway.
+#
+#
+# Switching to connection 'default'.
+#
+#
+# 5) Acquire SNW lock on the table. We have to use DEBUG_SYNC for
+# this, to prevent SNW from being immediately upgraded to X.
+#
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that S, SH and SR locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+5
+# Check that SW lock is incompatible with SNW lock.
+# Sending:
+delete from t1 limit 2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above DELETE is blocked because of SNW lock.
+# Unblock ALTER and thus DELETE.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con1'.
+# Reaping DELETE.
+#
+# Switching to connection 'default'.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that SNW lock is incompatible with SNW lock.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER is blocked because of SNW lock.
+# Unblock ALTERs.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping first ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con1'.
+# Reaping another ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that SNRW lock is incompatible with SNW lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above LOCK TABLES is blocked because of SNW lock.
+# Unblock ALTER and thus LOCK TABLES.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLES
+insert into t1 values (1);
+unlock tables;
+#
+# Switching to connection 'default'.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con1'.
+set debug_sync= 'now WAIT_FOR locked';
+# Check that X lock is incompatible with SNW lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME is blocked because of SNW lock.
+# Unblock ALTER and thus RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'default'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE
+# Revert back to original state of things.
+rename table t2 to t1;
+#
+# There is no need to check that upgrade from SNW/SNRW to X is
+# blocked by presence of another SNW lock because SNW/SNRW is
+# incompatible with SNW anyway.
+#
+# Switching to connection 'default'.
+#
+#
+# 6) Acquire SNRW lock on the table.
+#
+#
+lock table t1 write;
+#
+# Switching to connection 'mdl_con1'.
+# Check that S and SH locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+# Check that SR lock is incompatible with SNRW lock.
+# Sending:
+select count(*) from t1;;
+#
+# Switching to connection 'default'.
+# Check that the above SELECT is blocked because of SNRW lock.
+# Unblock SELECT.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping SELECT.
+count(*)
+4
+#
+# Switching to connection 'default'.
+lock table t1 write;
+#
+# Switching to connection 'mdl_con1'.
+# Check that SW lock is incompatible with SNRW lock.
+# Sending:
+delete from t1 limit 1;;
+#
+# Switching to connection 'default'.
+# Check that the above DELETE is blocked because of SNRW lock.
+# Unblock DELETE.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping DELETE.
+#
+# Switching to connection 'default'.
+lock table t1 write;
+#
+# Switching to connection 'mdl_con1'.
+# Check that SNW lock is incompatible with SNRW lock.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'default'.
+# Check that the above ALTER is blocked because of UNWR lock.
+# Unblock ALTER.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+lock table t1 write;
+#
+# Switching to connection 'mdl_con1'.
+# Check that SNRW lock is incompatible with SNRW lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'default'.
+# Check that the above LOCK TABLES is blocked because of SNRW lock.
+# Unblock waiting LOCK TABLES.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLES
+insert into t1 values (1);
+unlock tables;
+#
+# Switching to connection 'default'.
+lock table t1 write;
+#
+# Switching to connection 'mdl_con1'.
+# Check that X lock is incompatible with SNRW lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'default'.
+# Check that the above RENAME is blocked because of SNRW lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME TABLE
+# Revert back to original state of things.
+rename table t2 to t1;
+#
+# There is no need to check that upgrade from SNW/SNRW to X is
+# blocked by presence of another SNRW lock because SNW/SNRW is
+# incompatible with SNRW anyway.
+#
+# Switching to connection 'default'.
+#
+#
+# 7) Now do the same round of tests for X lock. We use additional
+# table to get long-lived lock of this type.
+#
+create table t2 (c1 int);
+#
+# Switching to connection 'mdl_con2'.
+# Take a lock on t2, so RENAME TABLE t1 TO t2 will get blocked
+# after acquiring X lock on t1.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that S lock in incompatible with X lock.
+# Sending:
+handler t1 open;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above HANDLER statement is blocked because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping HANDLER.
+handler t1 close;
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that SH lock in incompatible with X lock.
+# Sending:
+select column_name from information_schema.columns where table_schema='test' and table_name='t1';;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT ... FROM I_S ... statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping SELECT ... FROM I_S.
+column_name
+c1
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that SR lock in incompatible with X lock.
+# Sending:
+select count(*) from t1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping SELECT.
+count(*)
+4
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that SW lock in incompatible with X lock.
+# Sending:
+delete from t1 limit 1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above DELETE statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping DELETE.
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that SNW lock is incompatible with X lock.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that SNRW lock is incompatible with X lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above LOCK TABLE statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLE.
+unlock tables;
+#
+# Switching to connection 'mdl_con2'.
+# Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+#
+# Switching to connection 'default'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME has acquired X lock on t1 and is waiting for t2.
+# Check that X lock is incompatible with X lock.
+# Sending:
+rename table t1 to t3;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above RENAME statement is blocked
+# because of X lock.
+# Unblock RENAME TABLE
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping RENAME.
+rename table t3 to t1;
+#
+# B) Now let us test compatibility in cases when both locks
+# are pending. I.e. let us test rules for priorities between
+# different types of metadata locks.
+#
+#
+# Switching to connection 'mdl_con2'.
+#
+# 1) Check compatibility for pending SNW lock.
+#
+# Acquire SW lock in order to create pending SNW lock later.
+begin;
+insert into t1 values (1);
+#
+# Switching to connection 'default'.
+# Add pending SNW lock.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con1'.
+# Check that ALTER TABLE is waiting with pending SNW lock.
+# Check that S, SH and SR locks are compatible with pending SNW
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+select count(*) from t1;
+count(*)
+4
+# Check that SW is incompatible with pending SNW
+# Sending:
+delete from t1 limit 1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above DELETE is blocked because of pending SNW lock.
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping ALTER.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con1'.
+# Reaping DELETE.
+#
+# We can't do similar check for SNW, SNRW and X locks because
+# they will also be blocked by active SW lock.
+#
+#
+# Switching to connection 'mdl_con2'.
+#
+# 2) Check compatibility for pending SNRW lock.
+#
+# Acquire SR lock in order to create pending SNRW lock.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'default'.
+# Add pending SNRW lock.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that LOCK TABLE is waiting with pending SNRW lock.
+# Check that S and SH locks are compatible with pending SNRW
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+# Check that SR is incompatible with pending SNRW
+# Sending:
+select count(*) from t1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT is blocked because of pending SNRW lock.
+# Unblock LOCK TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping LOCK TABLE.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping SELECT.
+count(*)
+3
+# Restore pending SNRW lock.
+#
+# Switching to connection 'mdl_con2'.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'default'.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that LOCK TABLE is waiting with pending SNRW lock.
+# Check that SW is incompatible with pending SNRW
+# Sending:
+insert into t1 values (1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above INSERT is blocked because of pending SNRW lock.
+# Unblock LOCK TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping LOCK TABLE.
+unlock tables;
+#
+# Switching to connection 'mdl_con1'.
+# Reaping INSERT.
+# Restore pending SNRW lock.
+#
+# Switching to connection 'mdl_con2'.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'default'.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that LOCK TABLE is waiting with pending SNRW lock.
+# Check that SNW is compatible with pending SNRW
+# So ALTER TABLE statements are not starved by LOCK TABLEs.
+alter table t1 add primary key (c1);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'mdl_con2'.
+# Unblock LOCK TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping LOCK TABLE.
+unlock tables;
+#
+# We can't do similar check for SNRW and X locks because
+# they will also be blocked by active SR lock.
+#
+#
+# Switching to connection 'mdl_con2'.
+#
+# 3) Check compatibility for pending X lock.
+#
+# Acquire SR lock in order to create pending X lock.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'default'.
+# Add pending X lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME TABLE is waiting with pending X lock.
+# Check that SH locks are compatible with pending X
+select column_name from information_schema.columns where
+table_schema='test' and table_name='t1';
+column_name
+c1
+# Check that S is incompatible with pending X
+# Sending:
+handler t1 open;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above HANDLER OPEN is blocked because of pending X lock.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping HANDLER t1 OPEN.
+handler t1 close;
+# Restore pending X lock.
+#
+# Switching to connection 'mdl_con2'.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'default'.
+# Add pending X lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME TABLE is waiting with pending X lock.
+# Check that SR is incompatible with pending X
+# Sending:
+select count(*) from t1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT is blocked because of pending X lock.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping SELECT.
+count(*)
+4
+# Restore pending X lock.
+#
+# Switching to connection 'mdl_con2'.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'default'.
+# Add pending X lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME TABLE is waiting with pending X lock.
+# Check that SW is incompatible with pending X
+# Sending:
+delete from t1 limit 1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above DELETE is blocked because of pending X lock.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping DELETE.
+# Restore pending X lock.
+#
+# Switching to connection 'mdl_con2'.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'default'.
+# Add pending X lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME TABLE is waiting with pending X lock.
+# Check that SNW is incompatible with pending X
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above ALTER TABLE is blocked because of pending X lock.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+# Restore pending X lock.
+#
+# Switching to connection 'mdl_con2'.
+handler t1 open;
+#
+# Switching to connection 'default'.
+# Add pending X lock.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that RENAME TABLE is waiting with pending X lock.
+# Check that SNRW is incompatible with pending X
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'mdl_con3'.
+# Check that the above LOCK TABLES is blocked because of pending X lock.
+#
+# Switching to connection 'mdl_con2'.
+# Unblock RENAME TABLE.
+handler t1 close;
+#
+# Switching to connection 'default'.
+# Reaping RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'mdl_con1'.
+# Reaping LOCK TABLES.
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+#
+# C) Now let us test how type-of-operation locks are handled in
+# transactional context. Obviously we are mostly interested
+# in conflicting types of locks.
+#
+#
+# 1) Let us check how various locks used within transactional
+# context interact with active/pending SNW lock.
+#
+# We start with case when we are acquiring lock on the table
+# which was not used in the transaction before.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create an active SNW lock on t2.
+# We have to use DEBUG_SYNC facility as otherwise SNW lock
+# will be immediately released (or upgraded to X lock).
+insert into t2 values (1), (1);
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t2 add primary key (c1);;
+#
+# Switching to connection 'default'.
+set debug_sync= 'now WAIT_FOR locked';
+# SR lock should be acquired without any waiting.
+select count(*) from t2;
+count(*)
+2
+commit;
+# Now let us check that we will wait in case of SW lock.
+begin;
+select count(*) from t1;
+count(*)
+3
+# Sending:
+insert into t2 values (1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above INSERT is blocked.
+# Unblock ALTER TABLE and thus INSERT.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+# Reap INSERT.
+commit;
+#
+# Now let us see what happens when we are acquiring lock on the table
+# which is already used in transaction.
+#
+# *) First, case when transaction which has SR lock on the table also
+# locked in SNW mode acquires yet another SR lock and then tries
+# to acquire SW lock.
+begin;
+select count(*) from t1;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create an active SNW lock on t1.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'default'.
+set debug_sync= 'now WAIT_FOR locked';
+# We should still be able to get SR lock without waiting.
+select count(*) from t1;
+count(*)
+3
+# Since the above ALTER TABLE is not upgrading SNW lock to X by waiting
+# for SW lock we won't create deadlock.
+# So the below INSERT should not end-up with ER_LOCK_DEADLOCK error.
+# Sending:
+insert into t1 values (1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above INSERT is blocked.
+# Unblock ALTER TABLE and thus INSERT.
+set debug_sync= 'now SIGNAL finish';
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+# Reap INSERT.
+commit;
+#
+# **) Now test in which transaction that has SW lock on the table
+# against which there is pending SNW lock acquires SR and SW
+# locks on this table.
+#
+begin;
+insert into t1 values (1);
+#
+# Switching to connection 'mdl_con1'.
+# Create pending SNW lock on t1.
+# Sending:
+alter table t1 add primary key (c1);;
+#
+# Switching to connection 'default'.
+# Wait until ALTER TABLE starts waiting for SNW lock.
+# We should still be able to get both SW and SR locks without waiting.
+select count(*) from t1;
+count(*)
+5
+delete from t1 limit 1;
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+#
+# Switching to connection 'default'.
+#
+# 2) Now similar tests for active SNW lock which is being upgraded
+# to X lock.
+#
+# Again we start with case when we are acquiring lock on the
+# table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con2'.
+# Start transaction which will prevent SNW -> X upgrade from
+# completing immediately.
+begin;
+select count(*) from t2;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create SNW lock pending upgrade to X on t2.
+# Sending:
+alter table t2 add column c2 int;;
+#
+# Switching to connection 'default'.
+# Wait until ALTER TABLE starts waiting X lock.
+# Check that attempt to acquire SR lock on t2 causes waiting.
+# Sending:
+select count(*) from t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT is blocked.
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+#
+# Switching to connection 'default'.
+# Reap SELECT.
+count(*)
+3
+commit;
+# Do similar check for SW lock.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con2'.
+# Start transaction which will prevent SNW -> X upgrade from
+# completing immediately.
+begin;
+select count(*) from t2;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create SNW lock pending upgrade to X on t2.
+# Sending:
+alter table t2 drop column c2;;
+#
+# Switching to connection 'default'.
+# Wait until ALTER TABLE starts waiting X lock.
+# Check that attempt to acquire SW lock on t2 causes waiting.
+# Sending:
+insert into t2 values (1);;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above INSERT is blocked.
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+#
+# Switching to connection 'default'.
+# Reap INSERT.
+commit;
+#
+# Test for the case in which we are acquiring lock on the table
+# which is already used in transaction.
+#
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con1'.
+# Create SNW lock pending upgrade to X.
+# Sending:
+alter table t1 add column c2 int;;
+#
+# Switching to connection 'default'.
+# Wait until ALTER TABLE starts waiting X lock.
+# Check that transaction is still able to acquire SR lock.
+select count(*) from t1;
+count(*)
+4
+# Waiting trying to acquire SW lock will cause deadlock and
+# therefore should cause an error.
+delete from t1 limit 1;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Unblock ALTER TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap ALTER TABLE.
+#
+# Switching to connection 'default'.
+#
+# 3) Check how various locks used within transactional context
+# interact with active/pending SNRW lock.
+#
+# Once again we start with case when we are acquiring lock on
+# the table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con1'.
+lock table t2 write;
+#
+# Switching to connection 'default'.
+# Attempt to acquire SR should be blocked. It should
+# not cause errors as it does not creates deadlock.
+# Sending:
+select count(*) from t2;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that the above SELECT is blocked
+# Unblock SELECT.
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reap SELECT.
+count(*)
+4
+commit;
+# Repeat the same test for SW lock.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con1'.
+lock table t2 write;
+#
+# Switching to connection 'default'.
+# Again attempt to acquire SW should be blocked and should
+# not cause any errors.
+# Sending:
+delete from t2 limit 1;;
+#
+# Switching to connection 'mdl_con1'.
+# Check that the above DELETE is blocked
+# Unblock DELETE.
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reap DELETE.
+commit;
+#
+# Now coverage for the case in which we are acquiring lock on
+# the table which is already used in transaction and against
+# which there is a pending SNRW lock request.
+#
+# *) Let us start with case when transaction has only a SR lock.
+#
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con1'.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'default'.
+# Wait until LOCK TABLE is blocked creating pending request for X lock.
+# Check that another instance of SR lock is granted without waiting.
+select count(*) from t1;
+count(*)
+4
+# Attempt to wait for SW lock will lead to deadlock, thus
+# the below statement should end with ER_LOCK_DEADLOCK error.
+delete from t1 limit 1;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Unblock LOCK TABLES.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap LOCK TABLES.
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+# **) Now case when transaction has a SW lock.
+#
+begin;
+delete from t1 limit 1;
+#
+# Switching to connection 'mdl_con1'.
+# Sending:
+lock table t1 write;;
+#
+# Switching to connection 'default'.
+# Wait until LOCK TABLE is blocked creating pending request for X lock.
+# Check that both SR and SW locks are granted without waiting
+# and errors.
+select count(*) from t1;
+count(*)
+3
+insert into t1 values (1, 1);
+# Unblock LOCK TABLES.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap LOCK TABLES.
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+# 4) Check how various locks used within transactional context
+# interact with active/pending X lock.
+#
+# As usual we start with case when we are acquiring lock on
+# the table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con2'.
+# Start transaction which will prevent X lock from going away
+# immediately.
+begin;
+select count(*) from t2;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create pending X lock on t2.
+# Sending:
+rename table t2 to t3;;
+#
+# Switching to connection 'default'.
+# Wait until RENAME TABLE starts waiting with pending X lock.
+# Check that attempt to acquire SR lock on t2 causes waiting.
+# Sending:
+select count(*) from t2;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above SELECT is blocked.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap RENAME TABLE.
+#
+# Switching to connection 'default'.
+# Reap SELECT.
+ERROR 42S02: Table 'test.t2' doesn't exist
+commit;
+rename table t3 to t2;
+# The same test for SW lock.
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con2'.
+# Start transaction which will prevent X lock from going away
+# immediately.
+begin;
+select count(*) from t2;
+count(*)
+3
+#
+# Switching to connection 'mdl_con1'.
+# Create pending X lock on t2.
+# Sending:
+rename table t2 to t3;;
+#
+# Switching to connection 'default'.
+# Wait until RENAME TABLE starts waiting with pending X lock.
+# Check that attempt to acquire SW lock on t2 causes waiting.
+# Sending:
+delete from t2 limit 1;;
+#
+# Switching to connection 'mdl_con2'.
+# Check that the above DELETE is blocked.
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap RENAME TABLE.
+#
+# Switching to connection 'default'.
+# Reap DELETE.
+ERROR 42S02: Table 'test.t2' doesn't exist
+commit;
+rename table t3 to t2;
+#
+# Coverage for the case in which we are acquiring lock on
+# the table which is already used in transaction and against
+# which there is a pending X lock request.
+#
+# *) The first case is when transaction has only a SR lock.
+#
+begin;
+select count(*) from t1;
+count(*)
+4
+#
+# Switching to connection 'mdl_con1'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'default'.
+# Wait until RENAME TABLE is blocked creating pending request for X lock.
+# Check that another instance of SR lock is granted without waiting.
+select count(*) from t1;
+count(*)
+4
+# Attempt to wait for SW lock will lead to deadlock, thus
+# the below statement should end with ER_LOCK_DEADLOCK error.
+delete from t1 limit 1;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'default'.
+#
+# **) The second case is when transaction has a SW lock.
+#
+begin;
+delete from t1 limit 1;
+#
+# Switching to connection 'mdl_con1'.
+# Sending:
+rename table t1 to t2;;
+#
+# Switching to connection 'default'.
+# Wait until RENAME TABLE is blocked creating pending request for X lock.
+# Check that both SR and SW locks are granted without waiting
+# and errors.
+select count(*) from t1;
+count(*)
+3
+insert into t1 values (1, 1);
+# Unblock RENAME TABLE.
+commit;
+#
+# Switching to connection 'mdl_con1'.
+# Reap RENAME TABLE.
+ERROR 42S01: Table 't2' already exists
+#
+# Switching to connection 'default'.
+# Clean-up.
+set debug_sync= 'RESET';
+drop table t1, t2;
+#
+# Additional coverage for some scenarios in which not quite
+# correct use of S metadata locks by HANDLER statement might
+# have caused deadlocks.
+#
+drop table if exists t1, t2;
+create table t1 (i int);
+create table t2 (j int);
+insert into t1 values (1);
+#
+# First, check scenario in which we upgrade SNRW lock to X lock
+# on a table while having HANDLER READ trying to acquire TL_READ
+# on the same table.
+#
+handler t1 open;
+#
+# Switching to connection 'handler_con1'.
+lock table t1 write;
+# Upgrade SNRW to X lock.
+# Sending:
+alter table t1 add column j int;;
+#
+# Switching to connection 'handler_con2'.
+# Wait until ALTER is blocked during upgrade.
+#
+# Switching to connection 'default'.
+# The below statement should not cause deadlock.
+handler t1 read first;;
+#
+# Switching to connection 'handler_con1'.
+# Reap ALTER TABLE.
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reap HANDLER READ.
+i j
+1 NULL
+handler t1 close;
+#
+# Now, check scenario in which upgrade of SNRW lock to X lock
+# can be blocked by HANDLER which is open in connection currently
+# waiting to get table-lock owned by connection doing upgrade.
+#
+handler t1 open;
+#
+# Switching to connection 'handler_con1'.
+lock table t1 write, t2 read;
+#
+# Switching to connection 'default'.
+# Execute statement which will be blocked on table-level lock
+# owned by connection 'handler_con1'.
+# Sending:
+insert into t2 values (1);;
+#
+# Switching to connection 'handler_con1'.
+# Wait until INSERT is blocked on table-level lock.
+# The below statement should not cause deadlock.
+alter table t1 drop column j;
+unlock tables;
+#
+# Switching to connection 'default'.
+# Reap INSERT.
+handler t1 close;
+#
+# Then, check the scenario in which upgrade of SNRW lock to X
+# lock is blocked by HANDLER which is open in connection currently
+# waiting to get SW lock on the same table.
+#
+handler t1 open;
+#
+# Switching to connection 'handler_con1'.
+lock table t1 write;
+#
+# Switching to connection 'default'.
+# The below insert should be blocked because active SNRW lock on 't1'.
+# Sending:
+insert into t1 values (1);;
+#
+# Switching to connection 'handler_con1'.
+# Wait until INSERT is blocked because of SNRW lock.
+# The below ALTER TABLE will be blocked because of presence of HANDLER.
+# Sending:
+alter table t1 add column j int;;
+#
+# Switching to connection 'default'.
+# INSERT should be chosen as victim for resolving deadlock.
+# Reaping INSERT.
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Close HANDLER to unblock ALTER TABLE.
+handler t1 close;
+#
+# Switching to connection 'handler_con1'.
+# Reaping ALTER TABLE.
+unlock tables;
+#
+# Switching to connection 'default'.
+#
+# Finally, test in which upgrade of SNRW lock to X lock is blocked
+# by HANDLER which is open in connection currently waiting to get
+# SR lock on the table on which lock is upgraded.
+#
+handler t1 open;
+#
+# Switching to connection 'handler_con1'.
+lock table t1 write, t2 write;
+#
+# Switching to connection 'default'.
+# The below insert should be blocked because active SNRW lock on 't1'.
+# Sending:
+insert into t2 values (1);;
+#
+# Switching to connection 'handler_con1'.
+# Wait until INSERT is blocked because of SNRW lock.
+# The below ALTER TABLE will be blocked because of presence of HANDLER.
+# Sending:
+alter table t1 drop column j;;
+#
+# Switching to connection 'default'.
+# INSERT should be chosen as victim for resolving deadlock.
+# Reaping INSERT.
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+# Close HANDLER to unblock ALTER TABLE.
+handler t1 close;
+#
+# Switching to connection 'handler_con1'.
+# Reaping ALTER TABLE.
+unlock tables;
+#
+# Switching to connection 'default'.
+# Clean-up.
+drop tables t1, t2;
+#
# Test coverage for basic deadlock detection in metadata
# locking subsystem.
#
@@ -118,53 +1852,46 @@ commit;
#
# Switching to connection 'deadlock_con1'.
begin;
-insert into t1 values (1);
-#
-# Switching to connection 'deadlock_con2'.
-begin;
-insert into t3 values (1);
+insert into t2 values (1);
#
# Switching to connection 'default'.
-# Send:
-rename table t2 to t0, t3 to t2, t0 to t3;;
+lock table t1 write;
#
# Switching to connection 'deadlock_con1'.
-# Wait until the above RENAME TABLE is blocked because it has to wait
-# for 'deadlock_con2' which holds shared metadata lock on 't3'.
# The below SELECT statement should wait for metadata lock
-# on table 't2' and should not produce ER_LOCK_DEADLOCK
+# on table 't1' and should not produce ER_LOCK_DEADLOCK
# immediately as no deadlock is possible at the moment.
-select * from t2;;
+select * from t1;;
#
-# Switching to connection 'deadlock_con3'.
-# Wait until the above SELECT * FROM t2 is starts waiting
-# for an exclusive metadata lock to go away.
+# Switching to connection 'deadlock_con2'.
+# Wait until the above SELECT * FROM t1 is starts waiting
+# for an UNRW metadata lock to go away.
# Send RENAME TABLE statement that will deadlock with the
# SELECT statement and thus should abort the latter.
-rename table t1 to t5, t2 to t1, t5 to t2;;
+rename table t1 to t0, t2 to t1, t0 to t2;;
+#
+# Switching to connection 'default'.
+# Wait till above RENAME TABLE is blocked while holding
+# pending X lock on t1.
+# Allow the above RENAME TABLE to acquire lock on t1 and
+# create pending lock on t2 thus creating deadlock.
+unlock tables;
#
# Switching to connection 'deadlock_con1'.
# Since the latest RENAME TABLE entered in deadlock with SELECT
# statement the latter should be aborted and emit ER_LOCK_DEADLOCK
# error.
-# Reap SELECT * FROM t2.
+# Reap SELECT * FROM t1.
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
#
# Again let us check that failure of the SELECT statement has not
-# released metadata lock on table 't1', i.e. that the latest RENAME
+# released metadata lock on table 't2', i.e. that the latest RENAME
# is blocked.
# Commit transaction to unblock this RENAME TABLE.
commit;
#
# Switching to connection 'deadlock_con2'.
-# Commit transaction to unblock the first RENAME TABLE.
-commit;
-#
-# Switching to connection 'default'.
-# Reap RENAME TABLE t2 TO t0 ... .
-#
-# Switching to connection 'deadlock_con3'.
-# Reap RENAME TABLE t1 TO t5 ... .
+# Reap RENAME TABLE ... .
#
# Switching to connection 'default'.
drop tables t1, t2, t3, t4;
@@ -173,10 +1900,19 @@ drop tables t1, t2, t3, t4;
# also takes into account requests for metadata lock upgrade.
#
create table t1 (i int);
+insert into t1 values (1);
+# Avoid race which occurs when SELECT in 'deadlock_con1' connection
+# accesses table before the above INSERT unlocks the table and thus
+# its result becomes visible to other connections.
+select * from t1;
+i
+1
#
# Switching to connection 'deadlock_con1'.
begin;
-insert into t1 values (1);
+select * from t1;
+i
+1
#
# Switching to connection 'default'.
# Send:
@@ -200,42 +1936,6 @@ commit;
# Reap ALTER TABLE ... RENAME.
drop table t2;
#
-# Finally, test case in which deadlock (or potentially livelock) occurs
-# between metadata locking subsystem and table definition cache/table
-# locks, but which should still be detected by our empiric.
-#
-create table t1 (i int);
-#
-# Switching to connection 'deadlock_con1'.
-begin;
-insert into t1 values (1);
-#
-# Switching to connection 'default'.
-lock tables t1 write;
-#
-# Switching to connection 'deadlock_con1'.
-# Send:
-insert into t1 values (2);;
-#
-# Switching to connection 'default'.
-# Wait until INSERT in connection 'deadlock_con1' is blocked on
-# table-level lock.
-# Send:
-alter table t1 add column j int;;
-#
-# Switching to connection 'deadlock_con1'.
-# The above ALTER TABLE statement should cause INSERT statement in
-# this connection to be aborted and emit ER_LOCK_DEADLOCK error.
-# Reap INSERT
-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
-# Commit transaction to unblock ALTER TABLE.
-commit;
-#
-# Switching to connection 'default'.
-# Reap ALTER TABLE.
-unlock tables;
-drop table t1;
-#
# Test for bug #46748 "Assertion in MDL_context::wait_for_locks()
# on INSERT + CREATE TRIGGER".
#
@@ -297,7 +1997,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked';
# Now INSERT has a MDL on the non-existent table t1.
#
# Continue the INSERT once CREATE waits for exclusive lock
-SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish';
# Try to create that table.
CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1));
# Connection 2
@@ -323,7 +2023,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked';
# Now INSERT has a MDL on the non-existent table t1.
#
# Continue the INSERT once CREATE waits for exclusive lock
-SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish';
# Try to create that table.
CREATE TABLE t1 LIKE t2;
# Connection 2
@@ -347,10 +2047,10 @@ create table t1 (i int);
# Let us check that we won't deadlock if during filling
# of I_S table we encounter conflicting metadata lock
# which owner is in its turn waiting for our connection.
-lock tables t1 write;
+lock tables t1 read;
# Switching to connection 'con46044'.
# Sending:
-create table t2 select * from t1;;
+create table t2 select * from t1 for update;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# First let us check that SHOW FIELDS/DESCRIBE doesn't
@@ -386,10 +2086,10 @@ drop table t2;
#
# We check same three queries to I_S in this new situation.
# Switching to connection 'con46044_2'.
-lock tables t1 write;
+lock tables t1 read;
# Switching to connection 'con46044'.
# Sending:
-create table t2 select * from t1;;
+create table t2 select * from t1 for update;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Let us check that SHOW FIELDS/DESCRIBE gets blocked.
@@ -406,10 +2106,10 @@ Field Type Null Key Default Extra
i int(11) YES NULL
drop table t2;
# Switching to connection 'con46044_2'.
-lock tables t1 write;
+lock tables t1 read;
# Switching to connection 'con46044'.
# Sending:
-create table t2 select * from t1;;
+create table t2 select * from t1 for update;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Check that I_S query which reads only .FRMs gets blocked.
@@ -426,10 +2126,10 @@ column_name
i
drop table t2;
# Switching to connection 'con46044_2'.
-lock tables t1 write;
+lock tables t1 read;
# Switching to connection 'con46044'.
# Sending:
-create table t2 select * from t1;;
+create table t2 select * from t1 for update;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Finally, check that I_S query which does full-blown table open
@@ -458,7 +2158,9 @@ set debug_sync= 'RESET';
create table t1 (c1 int primary key, c2 int, c3 int);
insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
begin;
-update t1 set c3=c3+1 where c2=3;
+select * from t1 where c2 = 3;
+c1 c2 c3
+3 3 0
#
# Switching to connection 'con46273'.
set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go';
@@ -466,11 +2168,11 @@ alter table t1 add column e int, rename to t2;;
#
# Switching to connection 'default'.
set debug_sync='now WAIT_FOR alter_table_locked';
-set debug_sync='wait_for_lock SIGNAL alter_go';
+set debug_sync='before_open_table_wait_refresh SIGNAL alter_go';
# The below statement should get ER_LOCK_DEADLOCK error
# (i.e. it should not allow ALTER to proceed, and then
# fail due to 't1' changing its name to 't2').
-update t1 set c3=c3+1 where c2=4;
+update t1 set c3=c3+1 where c2 = 3;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
#
# Let us check that failure of the above statement has not released
diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result
index d974cfb9605..a14d099c673 100644
--- a/mysql-test/r/sp-threads.result
+++ b/mysql-test/r/sp-threads.result
@@ -35,7 +35,7 @@ call bug9486();
show processlist;
Id User Host db Command Time State Info
# root localhost test Sleep # NULL
-# root localhost test Query # Table lock update t1, t2 set val= 1 where id1=id2
+# root localhost test Query # Waiting for table update t1, t2 set val= 1 where id1=id2
# root localhost test Query # NULL show processlist
# root localhost test Sleep # NULL
unlock tables;
diff --git a/mysql-test/r/truncate_coverage.result b/mysql-test/r/truncate_coverage.result
index bb036329f6f..7a5021f55e2 100644
--- a/mysql-test/r/truncate_coverage.result
+++ b/mysql-test/r/truncate_coverage.result
@@ -7,18 +7,20 @@ CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
#
# connection con1
-START TRANSACTION;
-INSERT INTO t1 VALUES (2);
+HANDLER t1 OPEN;
#
# connection default
LOCK TABLE t1 WRITE;
SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting';
TRUNCATE TABLE t1;
#
-# connection con1
+# connection con2
SET DEBUG_SYNC='now WAIT_FOR waiting';
KILL QUERY @id;
-COMMIT;
+#
+# connection con1
+# Release shared metadata lock by closing HANDLER.
+HANDLER t1 CLOSE;
#
# connection default
ERROR 70100: Query execution was interrupted
@@ -29,17 +31,18 @@ CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
#
# connection con1
-START TRANSACTION;
-INSERT INTO t1 VALUES (2);
+HANDLER t1 OPEN;
#
# connection default
LOCK TABLE t1 WRITE;
SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting';
TRUNCATE TABLE t1;
#
-# connection con1
+# connection con2
SET DEBUG_SYNC='now WAIT_FOR waiting';
-COMMIT;
+#
+# connection con1
+HANDLER t1 CLOSE;
#
# connection default
ERROR 42S02: Table 'test.t1' doesn't exist
@@ -55,7 +58,7 @@ START TRANSACTION;
INSERT INTO t1 VALUES (2);
#
# connection default
-SET DEBUG_SYNC='mdl_acquire_exclusive_locks_wait SIGNAL waiting';
+SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
TRUNCATE TABLE t1;
#
# connection con1
diff --git a/mysql-test/suite/funcs_1/datadict/processlist_val.inc b/mysql-test/suite/funcs_1/datadict/processlist_val.inc
index 8b10cfc5e97..6fcaf45c848 100644
--- a/mysql-test/suite/funcs_1/datadict/processlist_val.inc
+++ b/mysql-test/suite/funcs_1/datadict/processlist_val.inc
@@ -425,7 +425,7 @@ echo
# Poll till INFO is no more NULL and State = 'Table Lock'.
;
let $wait_condition= SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST
- WHERE INFO IS NOT NULL AND STATE = 'Table Lock';
+ WHERE INFO IS NOT NULL AND STATE = 'Waiting for table';
--source include/wait_condition.inc
echo
# Expect result:
diff --git a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result
index 34b2e48fc7e..e8ee784bec4 100644
--- a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result
+++ b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result
@@ -195,9 +195,11 @@ SELECT COUNT(*) FROM test.t1;
# Poll till INFO is no more NULL and State = 'Table Lock'.
+Timeout in wait_condition.inc for SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST
+WHERE INFO IS NOT NULL AND STATE = 'Table Lock'
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;
ID USER HOST DB COMMAND TIME STATE INFO
-<ID> test_user <HOST_NAME> information_schema Query <TIME> Table lock SELECT COUNT(*) FROM test.t1
+<ID> test_user <HOST_NAME> information_schema Query <TIME> Waiting for table SELECT COUNT(*) FROM test.t1
<ID> test_user <HOST_NAME> information_schema Sleep <TIME> NULL
<ID> root <HOST_NAME> information_schema Query <TIME> executing SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST
UNLOCK TABLES;
diff --git a/mysql-test/suite/rpl/t/rpl_sp.test b/mysql-test/suite/rpl/t/rpl_sp.test
index 231f0c6bcc0..3d94415fbb5 100644
--- a/mysql-test/suite/rpl/t/rpl_sp.test
+++ b/mysql-test/suite/rpl/t/rpl_sp.test
@@ -655,7 +655,7 @@ connection master;
connection master1;
--echo # Waitng for 'insert into t1 ...' to get blocked on table lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
-where state='Table lock' and info='insert into t1 (a) values (f1())';
+where state='Waiting for table' and info='insert into t1 (a) values (f1())';
--source include/wait_condition.inc
--echo # Sending 'drop function f1'. It will abort the table lock wait.
drop function f1;
diff --git a/mysql-test/t/debug_sync.test b/mysql-test/t/debug_sync.test
index 514e471b603..ebeeec61632 100644
--- a/mysql-test/t/debug_sync.test
+++ b/mysql-test/t/debug_sync.test
@@ -390,7 +390,7 @@ DROP TABLE IF EXISTS t1;
#
# Test.
CREATE TABLE t1 (c1 INT);
-LOCK TABLE t1 WRITE;
+LOCK TABLE t1 READ;
--echo connection con1
connect (con1,localhost,root,,);
# Retain action after use. First used by general_log.
diff --git a/mysql-test/t/innodb-lock.test b/mysql-test/t/innodb-lock.test
index eacf7e562be..d2f630ccaba 100644
--- a/mysql-test/t/innodb-lock.test
+++ b/mysql-test/t/innodb-lock.test
@@ -56,9 +56,12 @@ commit;
drop table t1;
-#
-# Try with old lock method (where LOCK TABLE is ignored by InnoDB)
-#
+--echo #
+--echo # Old lock method (where LOCK TABLE was ignored by InnoDB) no longer
+--echo # works due to fix for bugs #46272 "MySQL 5.4.4, new MDL: unnecessary
+--echo # deadlock" and bug #37346 "innodb does not detect deadlock between
+--echo # update and alter table".
+--echo #
set @@innodb_table_locks=0;
@@ -67,36 +70,38 @@ insert into t1 values(0, 0),(1,1),(2,2);
commit;
SELECT * from t1 where id = 0 FOR UPDATE;
+--echo # Connection 'con2'.
connection con2;
set autocommit=0;
set @@innodb_table_locks=0;
-# The following statement should work becase innodb doesn't check table locks
-lock table t1 write;
+--echo # The following statement should block because SQL-level lock
+--echo # is taken on t1 which will wait until concurrent transaction
+--echo # is commited.
+--echo # Sending:
+--send lock table t1 write;
+--echo # Connection 'con1'.
connection con1;
+--echo # Wait until LOCK TABLE is blocked on SQL-level lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # We should be able to do UPDATEs and SELECTs within transaction.
+update t1 set x=1 where id = 0;
+select * from t1;
+--echo # Unblock LOCK TABLE.
+commit;
-# This will be locked by MySQL
---send
-update t1 set x=10 where id = 2;
---sleep 2
-
+--echo # Connection 'con2'.
connection con2;
-
-# Note that we will get a deadlock if we try to select any rows marked
-# for update by con1 !
-
-SELECT * from t1 where id = 2;
-UPDATE t1 set x=3 where id = 2;
-commit;
-SELECT * from t1;
-commit;
+--echo # Reap LOCK TABLE.
+--reap
unlock tables;
+--echo # Connection 'con1'.
connection con1;
-reap;
-commit;
-select * from t1;
drop table t1;
# End of 4.1 tests
diff --git a/mysql-test/t/innodb_mysql_lock.test b/mysql-test/t/innodb_mysql_lock.test
index c8c38cd1ab1..6469ef2d229 100644
--- a/mysql-test/t/innodb_mysql_lock.test
+++ b/mysql-test/t/innodb_mysql_lock.test
@@ -66,6 +66,60 @@ connection default;
disconnect con1;
disconnect con3;
+
+--echo #
+--echo # Test for bug #37346 "innodb does not detect deadlock between update
+--echo # and alter table".
+--echo #
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+create table t1 (c1 int primary key, c2 int, c3 int) engine=InnoDB;
+insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
+begin;
+--echo # Run statement which acquires X-lock on one of table's rows.
+update t1 set c3=c3+1 where c2=3;
+
+--echo #
+--echo # Switching to connection 'con37346'.
+connect (con37346,localhost,root,,test,,);
+connection con37346;
+--echo # The below ALTER TABLE statement should wait till transaction
+--echo # in connection 'default' is complete and then succeed.
+--echo # It should not deadlock or fail with ER_LOCK_DEADLOCK error.
+--echo # Sending:
+--send alter table t1 add column c4 int;
+
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until the above ALTER TABLE gets blocked because this
+--echo # connection holds SW metadata lock on table to be altered.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c4 int";
+--source include/wait_condition.inc
+
+--echo # The below statement should succeed. It should not
+--echo # deadlock or end with ER_LOCK_DEADLOCK error.
+update t1 set c3=c3+1 where c2=4;
+
+--echo # Unblock ALTER TABLE by committing transaction.
+commit;
+
+--echo #
+--echo # Switching to connection 'con37346'.
+connection con37346;
+--echo # Reaping ALTER TABLE.
+--reap
+
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+disconnect con37346;
+drop table t1;
+
+
--echo #
--echo # Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB
--echo # table cause warnings in errlog
diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test
index bc9d1ea8245..eda3e8451dd 100644
--- a/mysql-test/t/lock.test
+++ b/mysql-test/t/lock.test
@@ -202,6 +202,12 @@ select * from t1;
select * from t2;
--error ER_TABLE_NOT_LOCKED
select * from t3;
+--echo Dropping of implicitly locked table is disallowed.
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+drop table t1;
+unlock tables;
+--echo Now let us also lock table explicitly and drop it.
+lock tables t1 write, v_bug5719 write;
drop table t1;
--echo
--echo sic: left LOCK TABLES mode
@@ -349,6 +355,76 @@ drop table t1;
--echo #
+--echo # Coverage for situations when we try to execute DDL on tables
+--echo # which are locked by LOCK TABLES only implicitly.
+--echo #
+--disable_warnings
+drop tables if exists t1, t2;
+drop view if exists v1;
+drop function if exists f1;
+--enable_warnings
+create table t1 (i int);
+create table t2 (j int);
+--echo #
+--echo # Try to perform DDL on table which is locked through view.
+create view v1 as select * from t2;
+lock tables t1 write, v1 write;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+flush table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+drop table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 add column k int;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+--echo # Repair produces error as part of its result set.
+repair table t2;
+unlock tables;
+drop view v1;
+--echo #
+--echo # Now, try DDL on table which is locked through routine.
+delimiter |;
+create function f1 () returns int
+begin
+ insert into t2 values (1);
+ return 0;
+end|
+delimiter ;|
+create view v1 as select f1() from t1;
+lock tables v1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+flush table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+drop table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 add column k int;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+--echo # Repair produces error as part of its result set.
+repair table t2;
+unlock tables;
+drop view v1;
+drop function f1;
+--echo #
+--echo # Finally, try DDL on table which is locked thanks to trigger.
+create trigger t1_ai after insert on t1 for each row insert into t2 values (1);
+lock tables t1 write;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+flush table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+drop table t2;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+alter table t2 add column k int;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+create trigger t2_bi before insert on t2 for each row set @a:=1;
+--echo # Repair produces error as part of its result set.
+repair table t2;
+unlock tables;
+drop trigger t1_ai;
+drop tables t1, t2;
+
+
+--echo #
--echo # Bug#45035 " Altering table under LOCK TABLES results in
--echo # "Error 1213 Deadlock found..."
--echo #
diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test
index 31a10f89796..b924923233b 100644
--- a/mysql-test/t/lock_multi.test
+++ b/mysql-test/t/lock_multi.test
@@ -8,14 +8,24 @@ drop table if exists t1,t2;
# Test to see if select will get the lock ahead of low priority update
connect (locker,localhost,root,,);
+connect (locker2,localhost,root,,);
connect (reader,localhost,root,,);
connect (writer,localhost,root,,);
connection locker;
create table t1(n int);
insert into t1 values (1);
-lock tables t1 write;
+connection locker2;
+select get_lock("mysqltest_lock", 100);
+connection locker;
+send
+update t1 set n = 2 and get_lock('mysqltest_lock', 100);
connection writer;
+# Wait till above update gets blocked on a user lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "User lock" and info = "update t1 set n = 2 and get_lock('mysqltest_lock', 100)";
+--source include/wait_condition.inc
send
update low_priority t1 set n = 4;
connection reader;
@@ -26,13 +36,16 @@ let $wait_condition=
--source include/wait_condition.inc
send
select n from t1;
-connection locker;
+connection locker2;
# Sleep a bit till the select of connection reader is in work and hangs
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "select n from t1";
--source include/wait_condition.inc
-unlock tables;
+select release_lock("mysqltest_lock");
+connection locker;
+reap;
+select release_lock("mysqltest_lock");
connection writer;
reap;
connection reader;
@@ -42,8 +55,17 @@ drop table t1;
connection locker;
create table t1(n int);
insert into t1 values (1);
-lock tables t1 read;
+connection locker2;
+select get_lock("mysqltest_lock", 100);
+connection locker;
+send
+select n from t1 where get_lock('mysqltest_lock', 100);
connection writer;
+# Wait till above select gets blocked on a user lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "User lock" and info = "select n from t1 where get_lock('mysqltest_lock', 100)";
+--source include/wait_condition.inc
send
update low_priority t1 set n = 4;
connection reader;
@@ -53,8 +75,11 @@ let $wait_condition=
where state = "Table lock" and info = "update low_priority t1 set n = 4";
--source include/wait_condition.inc
select n from t1;
+connection locker2;
+select release_lock("mysqltest_lock");
connection locker;
-unlock tables;
+reap;
+select release_lock("mysqltest_lock");
connection writer;
reap;
drop table t1;
@@ -95,9 +120,10 @@ insert t1 select * from t2;
connection locker;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "insert t1 select * from t2";
+ where state = "Waiting for table" and info = "insert t1 select * from t2";
--source include/wait_condition.inc
drop table t2;
+unlock tables;
connection reader;
--error ER_NO_SUCH_TABLE
reap;
@@ -119,9 +145,10 @@ connection locker;
# Sleep a bit till the insert of connection reader is in work and hangs
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "insert t1 select * from t2";
+ where state = "Waiting for table" and info = "insert t1 select * from t2";
--source include/wait_condition.inc
drop table t2;
+unlock tables;
connection reader;
--error ER_NO_SUCH_TABLE
reap;
@@ -164,7 +191,7 @@ connection locker;
# Sleep a bit till the select of connection reader is in work and hangs
let $wait_condition=
SELECT COUNT(*) = 1 FROM information_schema.processlist
- WHERE state = "Table lock" AND info =
+ WHERE state = "Waiting for table" AND info =
"SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1";
--source include/wait_condition.inc
# Make test case independent from earlier grants.
@@ -299,7 +326,7 @@ connection reader;
# Wait till connection writer is blocked
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "alter table t1 auto_increment=0";
+ where state = "Waiting for table" and info = "alter table t1 auto_increment=0";
--source include/wait_condition.inc
send
alter table t1 auto_increment=0;
@@ -307,7 +334,7 @@ connection locker;
# Wait till connection reader is blocked
let $wait_condition=
select count(*) = 2 from information_schema.processlist
- where state = "Table lock" and info = "alter table t1 auto_increment=0";
+ where state = "Waiting for table" and info = "alter table t1 auto_increment=0";
--source include/wait_condition.inc
unlock tables;
connection writer;
@@ -502,6 +529,7 @@ drop table t1;
# Disconnect sessions used in many subtests above
disconnect locker;
+disconnect locker2;
disconnect reader;
disconnect writer;
@@ -668,6 +696,57 @@ disconnect flush;
--echo #
+--echo # Test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock".
+--echo #
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+create table t1 (c1 int primary key, c2 int, c3 int);
+insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
+begin;
+update t1 set c3=c3+1 where c2=3;
+
+--echo #
+--echo # Switching to connection 'con46272'.
+connect (con46272,localhost,root,,test,,);
+connection con46272;
+--echo # The below ALTER TABLE statement should wait till transaction
+--echo # in connection 'default' is complete and then succeed.
+--echo # It should not deadlock or fail with ER_LOCK_DEADLOCK error.
+--echo # Sending:
+--send alter table t1 add column c4 int;
+
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until the above ALTER TABLE gets blocked because this
+--echo # connection holds SW metadata lock on table to be altered.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c4 int";
+--source include/wait_condition.inc
+
+--echo # The below statement should succeed. It should not
+--echo # deadlock or end with ER_LOCK_DEADLOCK error.
+update t1 set c3=c3+1 where c2=4;
+
+--echo # Unblock ALTER TABLE by committing transaction.
+commit;
+
+--echo #
+--echo # Switching to connection 'con46272'.
+connection con46272;
+--echo # Reaping ALTER TABLE.
+--reap
+
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+disconnect con46272;
+drop table t1;
+
+
+--echo #
--echo # Bug#47249 assert in MDL_global_lock::is_lock_type_compatible
--echo #
@@ -679,21 +758,17 @@ DROP VIEW IF EXISTS v1;
--echo #
--echo # Test 1: LOCK TABLES v1 WRITE, t1 READ;
--echo #
+--echo # Thanks to the fact that we no longer allow DDL on tables
+--echo # which are locked for write implicitly, the exact scenario
+--echo # in which assert was failing is no longer repeatable.
CREATE TABLE t1 ( f1 integer );
CREATE VIEW v1 AS SELECT f1 FROM t1 ;
---echo # Connection 2
-connect (con2,localhost,root);
LOCK TABLES v1 WRITE, t1 READ;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
FLUSH TABLE t1;
-disconnect con2;
---source include/wait_until_disconnected.inc
-
---echo # Connection 1
-connection default;
-LOCK TABLES t1 WRITE;
-FLUSH TABLE t1; # Assertion happened here
+UNLOCK TABLES;
# Cleanup
DROP TABLE t1;
diff --git a/mysql-test/t/lock_sync.test b/mysql-test/t/lock_sync.test
index de8a1d7e43e..460c0175808 100644
--- a/mysql-test/t/lock_sync.test
+++ b/mysql-test/t/lock_sync.test
@@ -21,6 +21,7 @@
--echo # TL_WRITE_ALLOW_READ) on this table might have led to deadlock.
--disable_warnings
drop table if exists t1;
+drop view if exists v1;
--enable_warnings
--echo # Create auxiliary connections used through the test.
connect (con_bug45143_1,localhost,root,,test,,);
@@ -35,6 +36,9 @@ set @old_general_log = @@global.general_log;
set @@global.general_log= OFF;
create table t1 (i int) engine=InnoDB;
+--echo # We have to use view in order to make LOCK TABLES avoid
+--echo # acquiring SNRW metadata lock on table.
+create view v1 as select * from t1;
insert into t1 values (1);
--echo # Prepare user lock which will be used for resuming execution of
--echo # the first statement after it acquires TL_WRITE_ALLOW_WRITE lock.
@@ -65,14 +69,14 @@ connection con_bug45143_3;
--echo # acquiring lock for the the first instance of 't1'.
set debug_sync= 'now WAIT_FOR parked';
--echo # Send LOCK TABLE statement which will try to get TL_WRITE lock on 't1':
---send lock table t1 write;
+--send lock table v1 write;
--echo # Switch to connection 'default'.
connection default;
--echo # Wait until this LOCK TABLES statement starts waiting for table lock.
let $wait_condition= select count(*)= 1 from information_schema.processlist
where state= 'Table lock' and
- info='lock table t1 write';
+ info='lock table v1 write';
--source include/wait_condition.inc
--echo # Allow SELECT ... FOR UPDATE to resume.
--echo # Since it already has TL_WRITE_ALLOW_WRITE lock on the first instance
@@ -110,6 +114,7 @@ disconnect con_bug45143_2;
disconnect con_bug45143_3;
set debug_sync= 'RESET';
set @@global.general_log= @old_general_log;
+drop view v1;
drop table t1;
diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test
index 4cbaa689339..dda9ba991cf 100644
--- a/mysql-test/t/mdl_sync.test
+++ b/mysql-test/t/mdl_sync.test
@@ -44,7 +44,7 @@ set debug_sync= 'now WAIT_FOR parked';
connection con2;
--echo connection: con2
-set debug_sync='mdl_acquire_exclusive_locks_wait SIGNAL go';
+set debug_sync='mdl_acquire_lock_wait SIGNAL go';
--send drop table t1,t2
connection con1;
@@ -74,6 +74,2313 @@ SET DEBUG_SYNC= 'RESET';
--echo #
+--echo # Basic test coverage for type-of-operation aware metadata locks.
+--echo #
+--disable_warnings
+drop table if exists t1, t2, t3;
+--enable_warnings
+connect(mdl_con1,localhost,root,,);
+connect(mdl_con2,localhost,root,,);
+connect(mdl_con3,localhost,root,,);
+connection default;
+set debug_sync= 'RESET';
+create table t1 (c1 int);
+
+--echo #
+--echo # A) First let us check compatibility rules between differend kinds of
+--echo # type-of-operation aware metadata locks.
+--echo # Of course, these rules are already covered by the tests scattered
+--echo # across the test suite. But it still makes sense to have one place
+--echo # which covers all of them.
+--echo #
+
+--echo # 1) Acquire S (simple shared) lock on the table (by using HANDLER):
+--echo #
+handler t1 open;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+insert into t1 values (1), (1);
+--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE
+--echo # which will fail after opening the table and thus obtaining SNW metadata
+--echo # lock.
+--error ER_DUP_ENTRY
+alter table t1 add primary key (c1);
+--echo # Check that SNRW lock is compatible with S lock.
+lock table t1 write;
+insert into t1 values (1);
+unlock tables;
+--echo # Check that X lock is incompatible with S lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME is blocked because of S lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock RENAME TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE.
+--reap
+--echo # Restore the original state of the things.
+rename table t2 to t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+handler t1 open;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that upgrade from SNW to X is blocked by presence of S lock.
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER TABLE is blocked because of S lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock ALTER TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+--echo # Restore the original state of the things.
+alter table t1 drop column c2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+handler t1 open;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that upgrade from SNRW to X is blocked by presence of S lock.
+lock table t1 write;
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above upgrade of SNRW to X in ALTER TABLE is blocked
+--echo # because of S lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock ALTER TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+--echo # Restore the original state of the things.
+alter table t1 drop column c2;
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # 2) Acquire SH (shared high-priority) lock on the table.
+--echo # We have to involve DEBUG_SYNC facility for this as usually
+--echo # such kind of locks are short-lived.
+--echo #
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+insert into t1 values (1);
+--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE
+--echo # which will fail after opening the table and thus obtaining SNW metadata
+--echo # lock.
+--error ER_DUP_ENTRY
+alter table t1 add primary key (c1);
+--echo # Check that SNRW lock is compatible with SH lock.
+lock table t1 write;
+delete from t1 limit 1;
+unlock tables;
+--echo # Check that X lock is incompatible with SH lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME is blocked because of SH lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping SELECT ... FROM I_S.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE.
+--reap
+--echo # Restore the original state of the things.
+rename table t2 to t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that upgrade from SNW to X is blocked by presence of SH lock.
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER TABLE is blocked because of SH lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping SELECT ... FROM I_S.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+--echo # Restore the original state of the things.
+alter table t1 drop column c2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that upgrade from SNRW to X is blocked by presence of S lock.
+lock table t1 write;
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above upgrade of SNRW to X in ALTER TABLE is blocked
+--echo # because of S lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping SELECT ... FROM I_S.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+--echo # Restore the original state of the things.
+alter table t1 drop column c2;
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo #
+--echo # 3) Acquire SR lock on the table.
+--echo #
+--echo #
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+insert into t1 values (1);
+--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE
+--echo # which will fail after opening the table and thus obtaining SNW metadata
+--echo # lock.
+--error ER_DUP_ENTRY
+alter table t1 add primary key (c1);
+--echo # Check that SNRW lock is not compatible with SR lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above LOCK TABLES is blocked because of SR lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Unblock LOCK TABLES.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLES.
+--reap
+delete from t1 limit 1;
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that X lock is incompatible with SR lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME is blocked because of SR lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE.
+--reap
+--echo # Restore the original state of the things.
+rename table t2 to t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that upgrade from SNW to X is blocked by presence of SR lock.
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER TABLE is blocked because of SR lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+--echo # Restore the original state of the things.
+alter table t1 drop column c2;
+--echo #
+--echo # There is no need to check that upgrade from SNRW to X is blocked
+--echo # by presence of SR lock because SNRW is incompatible with SR anyway.
+--echo #
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo #
+--echo # 4) Acquire SW lock on the table.
+--echo #
+--echo #
+begin;
+insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that S, SH, SR and SW locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+insert into t1 values (1);
+--echo # Check that SNW lock is not compatible with SW lock.
+--echo # Again we use ALTER TABLE which fails after opening
+--echo # the table to avoid upgrade of SNW -> X.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above ALTER TABLE is blocked because of SW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+begin;
+insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that SNRW lock is not compatible with SW lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above LOCK TABLES is blocked because of SW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Unblock LOCK TABLES.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLES.
+--reap
+delete from t1 limit 2;
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+begin;
+insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that X lock is incompatible with SW lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME is blocked because of SW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE.
+--reap
+--echo # Restore the original state of the things.
+rename table t2 to t1;
+--echo #
+--echo # There is no need to check that upgrade from SNW/SNRW to X is
+--echo # blocked by presence of SW lock because SNW/SNRW is incompatible
+--echo # with SW anyway.
+--echo #
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo #
+--echo # 5) Acquire SNW lock on the table. We have to use DEBUG_SYNC for
+--echo # this, to prevent SNW from being immediately upgraded to X.
+--echo #
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that S, SH and SR locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+--echo # Check that SW lock is incompatible with SNW lock.
+--echo # Sending:
+--send delete from t1 limit 2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above DELETE is blocked because of SNW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "delete from t1 limit 2";
+--source include/wait_condition.inc
+--echo # Unblock ALTER and thus DELETE.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping DELETE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that SNW lock is incompatible with SNW lock.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER is blocked because of SNW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTERs.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping first ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping another ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that SNRW lock is incompatible with SNW lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above LOCK TABLES is blocked because of SNW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Unblock ALTER and thus LOCK TABLES.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLES
+--reap
+insert into t1 values (1);
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # Check that X lock is incompatible with SNW lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME is blocked because of SNW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Unblock ALTER and thus RENAME TABLE.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE
+--reap
+--echo # Revert back to original state of things.
+rename table t2 to t1;
+--echo #
+--echo # There is no need to check that upgrade from SNW/SNRW to X is
+--echo # blocked by presence of another SNW lock because SNW/SNRW is
+--echo # incompatible with SNW anyway.
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo #
+--echo # 6) Acquire SNRW lock on the table.
+--echo #
+--echo #
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that S and SH locks are compatible with it.
+handler t1 open;
+handler t1 close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+--echo # Check that SR lock is incompatible with SNRW lock.
+--echo # Sending:
+--send select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above SELECT is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "select count(*) from t1";
+--source include/wait_condition.inc
+--echo # Unblock SELECT.
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping SELECT.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that SW lock is incompatible with SNRW lock.
+--echo # Sending:
+--send delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above DELETE is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "delete from t1 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock DELETE.
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping DELETE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that SNW lock is incompatible with SNRW lock.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above ALTER is blocked because of UNWR lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTER.
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that SNRW lock is incompatible with SNRW lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above LOCK TABLES is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Unblock waiting LOCK TABLES.
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLES
+--reap
+insert into t1 values (1);
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that X lock is incompatible with SNRW lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Check that the above RENAME is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME TABLE
+--reap
+--echo # Revert back to original state of things.
+rename table t2 to t1;
+--echo #
+--echo # There is no need to check that upgrade from SNW/SNRW to X is
+--echo # blocked by presence of another SNRW lock because SNW/SNRW is
+--echo # incompatible with SNRW anyway.
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo #
+--echo # 7) Now do the same round of tests for X lock. We use additional
+--echo # table to get long-lived lock of this type.
+--echo #
+create table t2 (c1 int);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Take a lock on t2, so RENAME TABLE t1 TO t2 will get blocked
+--echo # after acquiring X lock on t1.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that S lock in incompatible with X lock.
+--echo # Sending:
+--send handler t1 open;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above HANDLER statement is blocked because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "handler t1 open";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping HANDLER.
+--reap
+handler t1 close;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SH lock in incompatible with X lock.
+--echo # Sending:
+--send select column_name from information_schema.columns where table_schema='test' and table_name='t1';
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT ... FROM I_S ... statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info like "select column_name from information_schema.columns%";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping SELECT ... FROM I_S.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SR lock in incompatible with X lock.
+--echo # Sending:
+--send select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t1";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping SELECT.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SW lock in incompatible with X lock.
+--echo # Sending:
+--send delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above DELETE statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "delete from t1 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping DELETE.
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SNW lock is incompatible with X lock.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SNRW lock is incompatible with X lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above LOCK TABLE statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Prepare for blocking RENAME TABLE.
+lock tables t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that X lock is incompatible with X lock.
+--echo # Sending:
+--send rename table t1 to t3;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above RENAME statement is blocked
+--echo # because of X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t3";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping RENAME.
+--reap
+rename table t3 to t1;
+
+--echo #
+--echo # B) Now let us test compatibility in cases when both locks
+--echo # are pending. I.e. let us test rules for priorities between
+--echo # different types of metadata locks.
+--echo #
+
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo #
+--echo # 1) Check compatibility for pending SNW lock.
+--echo #
+--echo # Acquire SW lock in order to create pending SNW lock later.
+begin;
+insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending SNW lock.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that ALTER TABLE is waiting with pending SNW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Check that S, SH and SR locks are compatible with pending SNW
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+select count(*) from t1;
+--echo # Check that SW is incompatible with pending SNW
+--echo # Sending:
+--send delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above DELETE is blocked because of pending SNW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "delete from t1 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping ALTER.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping DELETE.
+--reap
+--echo #
+--echo # We can't do similar check for SNW, SNRW and X locks because
+--echo # they will also be blocked by active SW lock.
+--echo #
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo #
+--echo # 2) Check compatibility for pending SNRW lock.
+--echo #
+--echo # Acquire SR lock in order to create pending SNRW lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending SNRW lock.
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that LOCK TABLE is waiting with pending SNRW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Check that S and SH locks are compatible with pending SNRW
+handler t1 open t;
+handler t close;
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+--echo # Check that SR is incompatible with pending SNRW
+--echo # Sending:
+--send select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT is blocked because of pending SNRW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t1";
+--source include/wait_condition.inc
+--echo # Unblock LOCK TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping LOCK TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping SELECT.
+--reap
+--echo # Restore pending SNRW lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that LOCK TABLE is waiting with pending SNRW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Check that SW is incompatible with pending SNRW
+--echo # Sending:
+--send insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above INSERT is blocked because of pending SNRW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "insert into t1 values (1)";
+--source include/wait_condition.inc
+--echo # Unblock LOCK TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping LOCK TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping INSERT.
+--reap
+--echo # Restore pending SNRW lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that LOCK TABLE is waiting with pending SNRW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Check that SNW is compatible with pending SNRW
+--echo # So ALTER TABLE statements are not starved by LOCK TABLEs.
+--error ER_DUP_ENTRY
+alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Unblock LOCK TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping LOCK TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # We can't do similar check for SNRW and X locks because
+--echo # they will also be blocked by active SR lock.
+--echo #
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo #
+--echo # 3) Check compatibility for pending X lock.
+--echo #
+--echo # Acquire SR lock in order to create pending X lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending X lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME TABLE is waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SH locks are compatible with pending X
+select column_name from information_schema.columns where
+ table_schema='test' and table_name='t1';
+--echo # Check that S is incompatible with pending X
+--echo # Sending:
+--send handler t1 open;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above HANDLER OPEN is blocked because of pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "handler t1 open";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping HANDLER t1 OPEN.
+--reap
+handler t1 close;
+--echo # Restore pending X lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending X lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME TABLE is waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SR is incompatible with pending X
+--echo # Sending:
+--send select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT is blocked because of pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t1";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping SELECT.
+--reap
+--echo # Restore pending X lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending X lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME TABLE is waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SW is incompatible with pending X
+--echo # Sending:
+--send delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above DELETE is blocked because of pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "delete from t1 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping DELETE.
+--reap
+--echo # Restore pending X lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending X lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME TABLE is waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SNW is incompatible with pending X
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above ALTER TABLE is blocked because of pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo # Restore pending X lock.
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+handler t1 open;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Add pending X lock.
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that RENAME TABLE is waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that SNRW is incompatible with pending X
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'mdl_con3'.
+connection mdl_con3;
+--echo # Check that the above LOCK TABLES is blocked because of pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Unblock RENAME TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reaping RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reaping LOCK TABLES.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+
+--echo #
+--echo #
+--echo # C) Now let us test how type-of-operation locks are handled in
+--echo # transactional context. Obviously we are mostly interested
+--echo # in conflicting types of locks.
+--echo #
+
+--echo #
+--echo # 1) Let us check how various locks used within transactional
+--echo # context interact with active/pending SNW lock.
+--echo #
+--echo # We start with case when we are acquiring lock on the table
+--echo # which was not used in the transaction before.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create an active SNW lock on t2.
+--echo # We have to use DEBUG_SYNC facility as otherwise SNW lock
+--echo # will be immediately released (or upgraded to X lock).
+insert into t2 values (1), (1);
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t2 add primary key (c1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # SR lock should be acquired without any waiting.
+select count(*) from t2;
+commit;
+--echo # Now let us check that we will wait in case of SW lock.
+begin;
+select count(*) from t1;
+--echo # Sending:
+--send insert into t2 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above INSERT is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "insert into t2 values (1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE and thus INSERT.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap INSERT.
+--reap
+commit;
+--echo #
+--echo # Now let us see what happens when we are acquiring lock on the table
+--echo # which is already used in transaction.
+--echo #
+--echo # *) First, case when transaction which has SR lock on the table also
+--echo # locked in SNW mode acquires yet another SR lock and then tries
+--echo # to acquire SW lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create an active SNW lock on t1.
+set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish';
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+set debug_sync= 'now WAIT_FOR locked';
+--echo # We should still be able to get SR lock without waiting.
+select count(*) from t1;
+--echo # Since the above ALTER TABLE is not upgrading SNW lock to X by waiting
+--echo # for SW lock we won't create deadlock.
+--echo # So the below INSERT should not end-up with ER_LOCK_DEADLOCK error.
+--echo # Sending:
+--send insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above INSERT is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "insert into t1 values (1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE and thus INSERT.
+set debug_sync= 'now SIGNAL finish';
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap INSERT.
+--reap
+commit;
+--echo #
+--echo # **) Now test in which transaction that has SW lock on the table
+--echo # against which there is pending SNW lock acquires SR and SW
+--echo # locks on this table.
+--echo #
+begin;
+insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create pending SNW lock on t1.
+--echo # Sending:
+--send alter table t1 add primary key (c1);
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until ALTER TABLE starts waiting for SNW lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t1 add primary key (c1)";
+--source include/wait_condition.inc
+--echo # We should still be able to get both SW and SR locks without waiting.
+select count(*) from t1;
+delete from t1 limit 1;
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--error ER_DUP_ENTRY
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # 2) Now similar tests for active SNW lock which is being upgraded
+--echo # to X lock.
+--echo #
+--echo # Again we start with case when we are acquiring lock on the
+--echo # table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Start transaction which will prevent SNW -> X upgrade from
+--echo # completing immediately.
+begin;
+select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create SNW lock pending upgrade to X on t2.
+--echo # Sending:
+--send alter table t2 add column c2 int;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until ALTER TABLE starts waiting X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t2 add column c2 int";
+--source include/wait_condition.inc
+--echo # Check that attempt to acquire SR lock on t2 causes waiting.
+--echo # Sending:
+--send select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t2";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap SELECT.
+--reap
+commit;
+--echo # Do similar check for SW lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Start transaction which will prevent SNW -> X upgrade from
+--echo # completing immediately.
+begin;
+select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create SNW lock pending upgrade to X on t2.
+--echo # Sending:
+--send alter table t2 drop column c2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until ALTER TABLE starts waiting X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t2 drop column c2";
+--source include/wait_condition.inc
+--echo # Check that attempt to acquire SW lock on t2 causes waiting.
+--echo # Sending:
+--send insert into t2 values (1);
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above INSERT is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "insert into t2 values (1)";
+--source include/wait_condition.inc
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap INSERT.
+--reap
+commit;
+--echo #
+--echo # Test for the case in which we are acquiring lock on the table
+--echo # which is already used in transaction.
+--echo #
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create SNW lock pending upgrade to X.
+--echo # Sending:
+--send alter table t1 add column c2 int;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until ALTER TABLE starts waiting X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "alter table t1 add column c2 int";
+--source include/wait_condition.inc
+--echo # Check that transaction is still able to acquire SR lock.
+select count(*) from t1;
+--echo # Waiting trying to acquire SW lock will cause deadlock and
+--echo # therefore should cause an error.
+--error ER_LOCK_DEADLOCK
+delete from t1 limit 1;
+--echo # Unblock ALTER TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap ALTER TABLE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # 3) Check how various locks used within transactional context
+--echo # interact with active/pending SNRW lock.
+--echo #
+--echo # Once again we start with case when we are acquiring lock on
+--echo # the table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+lock table t2 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Attempt to acquire SR should be blocked. It should
+--echo # not cause errors as it does not creates deadlock.
+--echo # Sending:
+--send select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that the above SELECT is blocked
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t2";
+--source include/wait_condition.inc
+--echo # Unblock SELECT.
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap SELECT.
+--reap
+commit;
+--echo # Repeat the same test for SW lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+lock table t2 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Again attempt to acquire SW should be blocked and should
+--echo # not cause any errors.
+--echo # Sending:
+--send delete from t2 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Check that the above DELETE is blocked
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "delete from t2 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock DELETE.
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap DELETE.
+--reap
+commit;
+--echo #
+--echo # Now coverage for the case in which we are acquiring lock on
+--echo # the table which is already used in transaction and against
+--echo # which there is a pending SNRW lock request.
+--echo #
+--echo # *) Let us start with case when transaction has only a SR lock.
+--echo #
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until LOCK TABLE is blocked creating pending request for X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Check that another instance of SR lock is granted without waiting.
+select count(*) from t1;
+--echo # Attempt to wait for SW lock will lead to deadlock, thus
+--echo # the below statement should end with ER_LOCK_DEADLOCK error.
+--error ER_LOCK_DEADLOCK
+delete from t1 limit 1;
+--echo # Unblock LOCK TABLES.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap LOCK TABLES.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # **) Now case when transaction has a SW lock.
+--echo #
+begin;
+delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Sending:
+--send lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until LOCK TABLE is blocked creating pending request for X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "lock table t1 write";
+--source include/wait_condition.inc
+--echo # Check that both SR and SW locks are granted without waiting
+--echo # and errors.
+select count(*) from t1;
+insert into t1 values (1, 1);
+--echo # Unblock LOCK TABLES.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap LOCK TABLES.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # 4) Check how various locks used within transactional context
+--echo # interact with active/pending X lock.
+--echo #
+--echo # As usual we start with case when we are acquiring lock on
+--echo # the table which was not used in the transaction before.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Start transaction which will prevent X lock from going away
+--echo # immediately.
+begin;
+select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create pending X lock on t2.
+--echo # Sending:
+--send rename table t2 to t3;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until RENAME TABLE starts waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t2 to t3";
+--source include/wait_condition.inc
+--echo # Check that attempt to acquire SR lock on t2 causes waiting.
+--echo # Sending:
+--send select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above SELECT is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "select count(*) from t2";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap RENAME TABLE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap SELECT.
+--error ER_NO_SUCH_TABLE
+--reap
+commit;
+rename table t3 to t2;
+--echo # The same test for SW lock.
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Start transaction which will prevent X lock from going away
+--echo # immediately.
+begin;
+select count(*) from t2;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Create pending X lock on t2.
+--echo # Sending:
+--send rename table t2 to t3;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until RENAME TABLE starts waiting with pending X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t2 to t3";
+--source include/wait_condition.inc
+--echo # Check that attempt to acquire SW lock on t2 causes waiting.
+--echo # Sending:
+--send delete from t2 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con2'.
+connection mdl_con2;
+--echo # Check that the above DELETE is blocked.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "delete from t2 limit 1";
+--source include/wait_condition.inc
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap RENAME TABLE.
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap DELETE.
+--error ER_NO_SUCH_TABLE
+--reap
+commit;
+rename table t3 to t2;
+--echo #
+--echo # Coverage for the case in which we are acquiring lock on
+--echo # the table which is already used in transaction and against
+--echo # which there is a pending X lock request.
+--echo #
+--echo # *) The first case is when transaction has only a SR lock.
+--echo #
+begin;
+select count(*) from t1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until RENAME TABLE is blocked creating pending request for X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that another instance of SR lock is granted without waiting.
+select count(*) from t1;
+--echo # Attempt to wait for SW lock will lead to deadlock, thus
+--echo # the below statement should end with ER_LOCK_DEADLOCK error.
+--error ER_LOCK_DEADLOCK
+delete from t1 limit 1;
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo #
+--echo # **) The second case is when transaction has a SW lock.
+--echo #
+begin;
+delete from t1 limit 1;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Sending:
+--send rename table t1 to t2;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait until RENAME TABLE is blocked creating pending request for X lock.
+let $wait_condition=
+select count(*) = 1 from information_schema.processlist
+where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--echo # Check that both SR and SW locks are granted without waiting
+--echo # and errors.
+select count(*) from t1;
+insert into t1 values (1, 1);
+--echo # Unblock RENAME TABLE.
+commit;
+--echo #
+--echo # Switching to connection 'mdl_con1'.
+connection mdl_con1;
+--echo # Reap RENAME TABLE.
+--error ER_TABLE_EXISTS_ERROR
+--reap
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+
+--echo # Clean-up.
+disconnect mdl_con1;
+disconnect mdl_con2;
+disconnect mdl_con3;
+set debug_sync= 'RESET';
+drop table t1, t2;
+
+
+--echo #
+--echo # Additional coverage for some scenarios in which not quite
+--echo # correct use of S metadata locks by HANDLER statement might
+--echo # have caused deadlocks.
+--echo #
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+connect(handler_con1,localhost,root,,);
+connect(handler_con2,localhost,root,,);
+connection default;
+create table t1 (i int);
+create table t2 (j int);
+insert into t1 values (1);
+
+--echo #
+--echo # First, check scenario in which we upgrade SNRW lock to X lock
+--echo # on a table while having HANDLER READ trying to acquire TL_READ
+--echo # on the same table.
+--echo #
+handler t1 open;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+lock table t1 write;
+--echo # Upgrade SNRW to X lock.
+--echo # Sending:
+--send alter table t1 add column j int;
+--echo #
+--echo # Switching to connection 'handler_con2'.
+connection handler_con2;
+--echo # Wait until ALTER is blocked during upgrade.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "alter table t1 add column j int";
+--source include/wait_condition.inc
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # The below statement should not cause deadlock.
+--send handler t1 read first;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Reap ALTER TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap HANDLER READ.
+--reap
+handler t1 close;
+
+--echo #
+--echo # Now, check scenario in which upgrade of SNRW lock to X lock
+--echo # can be blocked by HANDLER which is open in connection currently
+--echo # waiting to get table-lock owned by connection doing upgrade.
+--echo #
+handler t1 open;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+lock table t1 write, t2 read;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Execute statement which will be blocked on table-level lock
+--echo # owned by connection 'handler_con1'.
+--echo # Sending:
+--send insert into t2 values (1);
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Wait until INSERT is blocked on table-level lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Table lock" and info = "insert into t2 values (1)";
+--source include/wait_condition.inc
+--echo # The below statement should not cause deadlock.
+alter table t1 drop column j;
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Reap INSERT.
+--reap
+handler t1 close;
+
+--echo #
+--echo # Then, check the scenario in which upgrade of SNRW lock to X
+--echo # lock is blocked by HANDLER which is open in connection currently
+--echo # waiting to get SW lock on the same table.
+--echo #
+handler t1 open;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+lock table t1 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # The below insert should be blocked because active SNRW lock on 't1'.
+--echo # Sending:
+--send insert into t1 values (1);
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Wait until INSERT is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "insert into t1 values (1)";
+--echo # The below ALTER TABLE will be blocked because of presence of HANDLER.
+--echo # Sending:
+--send alter table t1 add column j int;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # INSERT should be chosen as victim for resolving deadlock.
+--echo # Reaping INSERT.
+--error ER_LOCK_DEADLOCK
+--reap
+--echo # Close HANDLER to unblock ALTER TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+
+--echo #
+--echo # Finally, test in which upgrade of SNRW lock to X lock is blocked
+--echo # by HANDLER which is open in connection currently waiting to get
+--echo # SR lock on the table on which lock is upgraded.
+--echo #
+handler t1 open;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+lock table t1 write, t2 write;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # The below insert should be blocked because active SNRW lock on 't1'.
+--echo # Sending:
+--send insert into t2 values (1);
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Wait until INSERT is blocked because of SNRW lock.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "insert into t2 values (1)";
+--echo # The below ALTER TABLE will be blocked because of presence of HANDLER.
+--echo # Sending:
+--send alter table t1 drop column j;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # INSERT should be chosen as victim for resolving deadlock.
+--echo # Reaping INSERT.
+--error ER_LOCK_DEADLOCK
+--reap
+--echo # Close HANDLER to unblock ALTER TABLE.
+handler t1 close;
+--echo #
+--echo # Switching to connection 'handler_con1'.
+connection handler_con1;
+--echo # Reaping ALTER TABLE.
+--reap
+unlock tables;
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+
+--echo # Clean-up.
+disconnect handler_con1;
+disconnect handler_con2;
+drop tables t1, t2;
+
+
+--echo #
--echo # Test coverage for basic deadlock detection in metadata
--echo # locking subsystem.
--echo #
@@ -236,47 +2543,47 @@ connection default;
--echo # Switching to connection 'deadlock_con1'.
connection deadlock_con1;
begin;
-insert into t1 values (1);
-
---echo #
---echo # Switching to connection 'deadlock_con2'.
-connection deadlock_con2;
-begin;
-insert into t3 values (1);
+insert into t2 values (1);
--echo #
--echo # Switching to connection 'default'.
connection default;
---echo # Send:
---send rename table t2 to t0, t3 to t2, t0 to t3;
+lock table t1 write;
--echo #
--echo # Switching to connection 'deadlock_con1'.
connection deadlock_con1;
---echo # Wait until the above RENAME TABLE is blocked because it has to wait
---echo # for 'deadlock_con2' which holds shared metadata lock on 't3'.
-let $wait_condition=
- select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info = "rename table t2 to t0, t3 to t2, t0 to t3";
---source include/wait_condition.inc
--echo # The below SELECT statement should wait for metadata lock
---echo # on table 't2' and should not produce ER_LOCK_DEADLOCK
+--echo # on table 't1' and should not produce ER_LOCK_DEADLOCK
--echo # immediately as no deadlock is possible at the moment.
---send select * from t2;
+--send select * from t1;
--echo #
---echo # Switching to connection 'deadlock_con3'.
-connection deadlock_con3;
---echo # Wait until the above SELECT * FROM t2 is starts waiting
---echo # for an exclusive metadata lock to go away.
+--echo # Switching to connection 'deadlock_con2'.
+connection deadlock_con2;
+--echo # Wait until the above SELECT * FROM t1 is starts waiting
+--echo # for an UNRW metadata lock to go away.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info = "select * from t2";
+ where state = "Waiting for table" and info = "select * from t1";
--source include/wait_condition.inc
--echo # Send RENAME TABLE statement that will deadlock with the
--echo # SELECT statement and thus should abort the latter.
---send rename table t1 to t5, t2 to t1, t5 to t2;
+--send rename table t1 to t0, t2 to t1, t0 to t2;
+
+--echo #
+--echo # Switching to connection 'default'.
+connection default;
+--echo # Wait till above RENAME TABLE is blocked while holding
+--echo # pending X lock on t1.
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t0, t2 to t1, t0 to t2";
+--source include/wait_condition.inc
+--echo # Allow the above RENAME TABLE to acquire lock on t1 and
+--echo # create pending lock on t2 thus creating deadlock.
+unlock tables;
--echo #
--echo # Switching to connection 'deadlock_con1'.
@@ -284,17 +2591,17 @@ connection deadlock_con1;
--echo # Since the latest RENAME TABLE entered in deadlock with SELECT
--echo # statement the latter should be aborted and emit ER_LOCK_DEADLOCK
--echo # error.
---echo # Reap SELECT * FROM t2.
+--echo # Reap SELECT * FROM t1.
--error ER_LOCK_DEADLOCK
--reap
--echo #
--echo # Again let us check that failure of the SELECT statement has not
---echo # released metadata lock on table 't1', i.e. that the latest RENAME
+--echo # released metadata lock on table 't2', i.e. that the latest RENAME
--echo # is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info = "rename table t1 to t5, t2 to t1, t5 to t2";
+ where state = "Waiting for table" and info = "rename table t1 to t0, t2 to t1, t0 to t2";
--source include/wait_condition.inc
--echo # Commit transaction to unblock this RENAME TABLE.
commit;
@@ -302,19 +2609,7 @@ commit;
--echo #
--echo # Switching to connection 'deadlock_con2'.
connection deadlock_con2;
---echo # Commit transaction to unblock the first RENAME TABLE.
-commit;
-
---echo #
---echo # Switching to connection 'default'.
-connection default;
---echo # Reap RENAME TABLE t2 TO t0 ... .
---reap
-
---echo #
---echo # Switching to connection 'deadlock_con3'.
-connection deadlock_con3;
---echo # Reap RENAME TABLE t1 TO t5 ... .
+--echo # Reap RENAME TABLE ... .
--reap;
--echo #
@@ -328,12 +2623,17 @@ drop tables t1, t2, t3, t4;
--echo # also takes into account requests for metadata lock upgrade.
--echo #
create table t1 (i int);
+insert into t1 values (1);
+--echo # Avoid race which occurs when SELECT in 'deadlock_con1' connection
+--echo # accesses table before the above INSERT unlocks the table and thus
+--echo # its result becomes visible to other connections.
+select * from t1;
--echo #
--echo # Switching to connection 'deadlock_con1'.
connection deadlock_con1;
begin;
-insert into t1 values (1);
+select * from t1;
--echo #
--echo # Switching to connection 'default'.
@@ -376,62 +2676,6 @@ connection default;
drop table t2;
---echo #
---echo # Finally, test case in which deadlock (or potentially livelock) occurs
---echo # between metadata locking subsystem and table definition cache/table
---echo # locks, but which should still be detected by our empiric.
---echo #
-create table t1 (i int);
-
---echo #
---echo # Switching to connection 'deadlock_con1'.
-connection deadlock_con1;
-begin;
-insert into t1 values (1);
-
---echo #
---echo # Switching to connection 'default'.
-connection default;
-lock tables t1 write;
-
---echo #
---echo # Switching to connection 'deadlock_con1'.
-connection deadlock_con1;
---echo # Send:
---send insert into t1 values (2);
-
---echo #
---echo # Switching to connection 'default'.
-connection default;
---echo # Wait until INSERT in connection 'deadlock_con1' is blocked on
---echo # table-level lock.
-let $wait_condition=
- select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "insert into t1 values (2)";
---source include/wait_condition.inc
-
---echo # Send:
---send alter table t1 add column j int;
-
---echo #
---echo # Switching to connection 'deadlock_con1'.
-connection deadlock_con1;
---echo # The above ALTER TABLE statement should cause INSERT statement in
---echo # this connection to be aborted and emit ER_LOCK_DEADLOCK error.
---echo # Reap INSERT
---error ER_LOCK_DEADLOCK
---reap
---echo # Commit transaction to unblock ALTER TABLE.
-commit;
-
---echo #
---echo # Switching to connection 'default'.
-connection default;
---echo # Reap ALTER TABLE.
---reap
-unlock tables;
-
-drop table t1;
disconnect deadlock_con1;
disconnect deadlock_con2;
disconnect deadlock_con3;
@@ -535,7 +2779,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked';
--echo #
--echo # Continue the INSERT once CREATE waits for exclusive lock
-SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish';
--echo # Try to create that table.
--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1))
@@ -575,7 +2819,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked';
--echo #
--echo # Continue the INSERT once CREATE waits for exclusive lock
-SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish';
--echo # Try to create that table.
--send CREATE TABLE t1 LIKE t2
@@ -615,19 +2859,19 @@ create table t1 (i int);
--echo # Let us check that we won't deadlock if during filling
--echo # of I_S table we encounter conflicting metadata lock
--echo # which owner is in its turn waiting for our connection.
-lock tables t1 write;
+lock tables t1 read;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
---send create table t2 select * from t1;
+--send create table t2 select * from t1 for update;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "create table t2 select * from t1";
+ where state = "Table lock" and info = "create table t2 select * from t1 for update";
--source include/wait_condition.inc
--echo # First let us check that SHOW FIELDS/DESCRIBE doesn't
@@ -668,19 +2912,19 @@ drop table t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
-lock tables t1 write;
+lock tables t1 read;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
---send create table t2 select * from t1;
+--send create table t2 select * from t1 for update;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "create table t2 select * from t1";
+ where state = "Table lock" and info = "create table t2 select * from t1 for update";
--source include/wait_condition.inc
--echo # Let us check that SHOW FIELDS/DESCRIBE gets blocked.
@@ -710,19 +2954,19 @@ drop table t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
-lock tables t1 write;
+lock tables t1 read;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
---send create table t2 select * from t1;
+--send create table t2 select * from t1 for update;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "create table t2 select * from t1";
+ where state = "Table lock" and info = "create table t2 select * from t1 for update";
--source include/wait_condition.inc
--echo # Check that I_S query which reads only .FRMs gets blocked.
@@ -753,19 +2997,19 @@ drop table t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
-lock tables t1 write;
+lock tables t1 read;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
---send create table t2 select * from t1;
+--send create table t2 select * from t1 for update;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Table lock" and info = "create table t2 select * from t1";
+ where state = "Table lock" and info = "create table t2 select * from t1 for update";
--source include/wait_condition.inc
--echo # Finally, check that I_S query which does full-blown table open
@@ -817,7 +3061,7 @@ create table t1 (c1 int primary key, c2 int, c3 int);
insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0);
begin;
-update t1 set c3=c3+1 where c2=3;
+select * from t1 where c2 = 3;
--echo #
--echo # Switching to connection 'con46273'.
@@ -829,12 +3073,12 @@ set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR
--echo # Switching to connection 'default'.
connection default;
set debug_sync='now WAIT_FOR alter_table_locked';
-set debug_sync='wait_for_lock SIGNAL alter_go';
+set debug_sync='before_open_table_wait_refresh SIGNAL alter_go';
--echo # The below statement should get ER_LOCK_DEADLOCK error
--echo # (i.e. it should not allow ALTER to proceed, and then
--echo # fail due to 't1' changing its name to 't2').
--error ER_LOCK_DEADLOCK
-update t1 set c3=c3+1 where c2=4;
+update t1 set c3=c3+1 where c2 = 3;
--echo #
--echo # Let us check that failure of the above statement has not released
diff --git a/mysql-test/t/multi_update.test b/mysql-test/t/multi_update.test
index 6f8cc94e6b7..68b44a33428 100644
--- a/mysql-test/t/multi_update.test
+++ b/mysql-test/t/multi_update.test
@@ -474,7 +474,8 @@ drop table t1,t2;
#
# Test alter table and a concurrent multi update
-# (This will force update to reopen tables)
+# (Before we have introduced data-lock-aware metadata locks
+# this test case forced update to reopen tables).
#
create table t1 (a int, b int);
@@ -494,9 +495,9 @@ send alter table t1 add column c int default 100 after a;
connect (updater,localhost,root,,test);
connection updater;
# Wait till "alter table t1 ..." of session changer is in work.
-# = There is one session is in state "Locked".
+# = There is one session waiting.
let $wait_condition= select count(*)= 1 from information_schema.processlist
- where state= 'Table lock';
+ where state= 'Waiting for table';
--source include/wait_condition.inc
send update t1, v1 set t1.b=t1.a+t1.b+v1.b where t1.a=v1.a;
@@ -505,9 +506,9 @@ connection locker;
# - "alter table t1 ..." of session changer and
# - "update t1, v1 ..." of session updater
# are in work.
-# = There are two session is in state "Locked".
+# = There are two session waiting.
let $wait_condition= select count(*)= 2 from information_schema.processlist
- where state= 'Table lock';
+ where state= 'Waiting for table';
--source include/wait_condition.inc
unlock tables;
diff --git a/mysql-test/t/truncate_coverage.test b/mysql-test/t/truncate_coverage.test
index 9870fbb5ebf..b7c08b03c8b 100644
--- a/mysql-test/t/truncate_coverage.test
+++ b/mysql-test/t/truncate_coverage.test
@@ -23,14 +23,14 @@ DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
#
-# Start a transaction and execute a DML in it. Since 5.4.4 this leaves
-# a shared meta data lock (MDL) behind. TRUNCATE shall block on it.
+# Acquire a shared metadata lock on table by opening HANDLER for it and wait.
+# TRUNCATE shall block on this metadata lock.
+# We can't use normal DML as such statements would also block LOCK TABLES.
#
--echo #
--echo # connection con1
--connect (con1, localhost, root,,)
-START TRANSACTION;
-INSERT INTO t1 VALUES (2);
+HANDLER t1 OPEN;
#
# Get connection id of default connection.
# Lock the table and start TRUNCATE, which will block on MDL upgrade.
@@ -48,12 +48,17 @@ send TRUNCATE TABLE t1;
# from wait_while_table_is_used().
#
--echo #
---echo # connection con1
---connection con1
+--echo # connection con2
+--connect (con2, localhost, root,,)
SET DEBUG_SYNC='now WAIT_FOR waiting';
let $invisible_assignment_in_select = `SELECT @id := $ID`;
KILL QUERY @id;
-COMMIT;
+--disconnect con2
+--echo #
+--echo # connection con1
+--connection con1
+--echo # Release shared metadata lock by closing HANDLER.
+HANDLER t1 CLOSE;
--disconnect con1
--echo #
--echo # connection default
@@ -69,14 +74,14 @@ SET DEBUG_SYNC='RESET';
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
#
-# Start a transaction and execute a DML in it. Since 5.4.4 this leaves
-# a shared meta data lock (MDL) behind. TRUNCATE shall block on it.
+# Acquire a shared metadata lock on table by opening HANDLER for it and wait.
+# TRUNCATE shall block on this metadata lock.
+# We can't use normal DML as such statements would also block LOCK TABLES.
#
--echo #
--echo # connection con1
--connect (con1, localhost, root,,)
-START TRANSACTION;
-INSERT INTO t1 VALUES (2);
+HANDLER t1 OPEN;
#
# Lock the table and start TRUNCATE, which will block on MDL upgrade.
#
@@ -91,11 +96,15 @@ send TRUNCATE TABLE t1;
# Commit to let TRUNCATE continue.
#
--echo #
---echo # connection con1
---connection con1
+--echo # connection con2
+--connect (con2, localhost, root,,)
SET DEBUG_SYNC='now WAIT_FOR waiting';
--remove_file $MYSQLD_DATADIR/test/t1.frm
-COMMIT;
+--disconnect con2
+--echo #
+--echo # connection con1
+--connection con1
+HANDLER t1 CLOSE;
--disconnect con1
--echo #
--echo # connection default
@@ -129,7 +138,7 @@ INSERT INTO t1 VALUES (2);
--echo # connection default
--connection default
let $ID= `SELECT @id := CONNECTION_ID()`;
-SET DEBUG_SYNC='mdl_acquire_exclusive_locks_wait SIGNAL waiting';
+SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
send TRUNCATE TABLE t1;
#
# Get the default connection ID into a variable in an invisible statement.
diff --git a/sql/handler.cc b/sql/handler.cc
index 66b0450d8d6..4becdaa3f4f 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -1164,7 +1164,7 @@ int ha_commit_trans(THD *thd, bool all)
rw_trans= is_real_trans && (rw_ha_count > 0);
if (rw_trans &&
- wait_if_global_read_lock(thd, 0, 0))
+ thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE))
{
ha_rollback_trans(thd, all);
DBUG_RETURN(1);
@@ -1223,7 +1223,7 @@ int ha_commit_trans(THD *thd, bool all)
RUN_HOOK(transaction, after_commit, (thd, FALSE));
end:
if (rw_trans)
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
}
/* Free resources and perform other cleanup even for 'empty' transactions. */
else if (is_real_trans)
diff --git a/sql/lock.cc b/sql/lock.cc
index 9d794b07418..bd4068046ca 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -271,7 +271,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
Someone has issued LOCK ALL TABLES FOR READ and we want a write lock
Wait until the lock is gone
*/
- if (wait_if_global_read_lock(thd, 1, 1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, 1, 1))
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data_and_free(&sql_lock);
@@ -940,7 +940,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
@note This function assumes that no metadata locks were acquired
before calling it. Also it cannot be called while holding
LOCK_open mutex. Both these invariants are enforced by asserts
- in MDL_context::acquire_exclusive_locks().
+ in MDL_context::acquire_locks().
@retval FALSE Success.
@retval TRUE Failure (OOM or thread was killed).
@@ -962,14 +962,11 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list)
mdl_requests.push_front(&lock_table->mdl_request);
}
- if (thd->mdl_context.acquire_global_intention_exclusive_lock(&global_request))
- return 1;
+ mdl_requests.push_front(&global_request);
- if (thd->mdl_context.acquire_exclusive_locks(&mdl_requests))
- {
- thd->mdl_context.release_lock(global_request.ticket);
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
return 1;
- }
+
return 0;
}
@@ -1002,7 +999,7 @@ void unlock_table_names(THD *thd)
This function assumes that no metadata locks were acquired
before calling it. Additionally, it cannot be called while
holding LOCK_open mutex. Both these invariants are enforced by
- asserts in MDL_context::acquire_exclusive_locks().
+ asserts in MDL_context::acquire_locks().
To avoid deadlocks, we do not try to obtain exclusive metadata
locks in LOCK TABLES mode, since in this mode there may be
other metadata locks already taken by the current connection,
@@ -1019,6 +1016,7 @@ bool lock_routine_name(THD *thd, bool is_function,
MDL_key::enum_mdl_namespace mdl_type= (is_function ?
MDL_key::FUNCTION :
MDL_key::PROCEDURE);
+ MDL_request_list mdl_requests;
MDL_request global_request;
MDL_request mdl_request;
@@ -1035,14 +1033,11 @@ bool lock_routine_name(THD *thd, bool is_function,
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE);
- if (thd->mdl_context.acquire_global_intention_exclusive_lock(&global_request))
- return TRUE;
+ mdl_requests.push_front(&mdl_request);
+ mdl_requests.push_front(&global_request);
- if (thd->mdl_context.acquire_exclusive_lock(&mdl_request))
- {
- thd->mdl_context.release_lock(global_request.ticket);
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
return TRUE;
- }
DEBUG_SYNC(thd, "after_wait_locked_pname");
return FALSE;
@@ -1161,9 +1156,6 @@ volatile uint global_read_lock_blocks_commit=0;
static volatile uint protect_against_global_read_lock=0;
static volatile uint waiting_for_read_lock=0;
-#define GOT_GLOBAL_READ_LOCK 1
-#define MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT 2
-
/**
Take global read lock, wait if there is protection against lock.
@@ -1177,12 +1169,13 @@ static volatile uint waiting_for_read_lock=0;
@retval True Failure, thread was killed.
*/
-bool lock_global_read_lock(THD *thd)
+bool Global_read_lock::lock_global_read_lock(THD *thd)
{
DBUG_ENTER("lock_global_read_lock");
- if (!thd->global_read_lock)
+ if (!m_state)
{
+ MDL_request mdl_request;
const char *old_message;
const char *new_message= "Waiting to get readlock";
(void) pthread_mutex_lock(&LOCK_global_read_lock);
@@ -1224,7 +1217,7 @@ bool lock_global_read_lock(THD *thd)
thd->exit_cond(old_message);
DBUG_RETURN(1);
}
- thd->global_read_lock= GOT_GLOBAL_READ_LOCK;
+ m_state= GRL_ACQUIRED;
global_read_lock++;
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
/*
@@ -1241,7 +1234,12 @@ bool lock_global_read_lock(THD *thd)
redundancy between metadata locks, global read lock and DDL
blocker (see WL#4399 and WL#4400).
*/
- if (thd->mdl_context.acquire_global_shared_lock())
+
+ DBUG_ASSERT(! thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_SHARED));
+ mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED);
+
+ if (thd->mdl_context.acquire_lock(&mdl_request))
{
/* Our thread was killed -- return back to initial state. */
pthread_mutex_lock(&LOCK_global_read_lock);
@@ -1251,9 +1249,11 @@ bool lock_global_read_lock(THD *thd)
pthread_cond_broadcast(&COND_global_read_lock);
}
pthread_mutex_unlock(&LOCK_global_read_lock);
- thd->global_read_lock= 0;
+ m_state= GRL_NONE;
DBUG_RETURN(1);
}
+ thd->mdl_context.move_ticket_after_trans_sentinel(mdl_request.ticket);
+ m_mdl_global_shared_lock= mdl_request.ticket;
}
/*
We DON'T set global_read_lock_blocks_commit now, it will be set after
@@ -1277,7 +1277,7 @@ bool lock_global_read_lock(THD *thd)
@param thd Reference to thread.
*/
-void unlock_global_read_lock(THD *thd)
+void Global_read_lock::unlock_global_read_lock(THD *thd)
{
uint tmp;
DBUG_ENTER("unlock_global_read_lock");
@@ -1285,11 +1285,14 @@ void unlock_global_read_lock(THD *thd)
("global_read_lock: %u global_read_lock_blocks_commit: %u",
global_read_lock, global_read_lock_blocks_commit));
- thd->mdl_context.release_global_shared_lock();
+ DBUG_ASSERT(m_mdl_global_shared_lock && m_state);
+
+ thd->mdl_context.release_lock(m_mdl_global_shared_lock);
+ m_mdl_global_shared_lock= NULL;
pthread_mutex_lock(&LOCK_global_read_lock);
tmp= --global_read_lock;
- if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT)
+ if (m_state == GRL_ACQUIRED_AND_BLOCKS_COMMIT)
--global_read_lock_blocks_commit;
pthread_mutex_unlock(&LOCK_global_read_lock);
/* Send the signal outside the mutex to avoid a context switch */
@@ -1298,7 +1301,7 @@ void unlock_global_read_lock(THD *thd)
DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
pthread_cond_broadcast(&COND_global_read_lock);
}
- thd->global_read_lock= 0;
+ m_state= GRL_NONE;
DBUG_VOID_RETURN;
}
@@ -1326,8 +1329,9 @@ void unlock_global_read_lock(THD *thd)
(is_not_commit || \
global_read_lock_blocks_commit))
-bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
- bool is_not_commit)
+bool Global_read_lock::
+wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
+ bool is_not_commit)
{
const char *UNINIT_VAR(old_message);
bool result= 0, need_exit_cond;
@@ -1337,10 +1341,10 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
If we already have protection against global read lock,
just increment the counter.
*/
- if (unlikely(thd->global_read_lock_protection > 0))
+ if (unlikely(m_protection_count > 0))
{
if (!abort_on_refresh)
- thd->global_read_lock_protection++;
+ m_protection_count++;
DBUG_RETURN(FALSE);
}
/*
@@ -1353,7 +1357,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
(void) pthread_mutex_lock(&LOCK_global_read_lock);
if ((need_exit_cond= must_wait))
{
- if (thd->global_read_lock) // This thread had the read locks
+ if (m_state) // This thread had the read locks
{
if (is_not_commit)
my_message(ER_CANT_UPDATE_WITH_READLOCK,
@@ -1380,7 +1384,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
}
if (!abort_on_refresh && !result)
{
- thd->global_read_lock_protection++;
+ m_protection_count++;
protect_against_global_read_lock++;
DBUG_PRINT("sql_lock", ("protect_against_global_read_lock incr: %u",
protect_against_global_read_lock));
@@ -1408,7 +1412,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
@param thd Reference to thread.
*/
-void start_waiting_global_read_lock(THD *thd)
+void Global_read_lock::start_waiting_global_read_lock(THD *thd)
{
bool tmp;
DBUG_ENTER("start_waiting_global_read_lock");
@@ -1416,13 +1420,13 @@ void start_waiting_global_read_lock(THD *thd)
Ignore request if we do not have protection against global read lock.
(Note that this is a violation of the interface contract, hence the assert).
*/
- DBUG_ASSERT(thd->global_read_lock_protection > 0);
- if (unlikely(thd->global_read_lock_protection == 0))
+ DBUG_ASSERT(m_protection_count > 0);
+ if (unlikely(m_protection_count == 0))
DBUG_VOID_RETURN;
/* Decrement local read lock protection counter, return if we still have it */
- if (unlikely(--thd->global_read_lock_protection > 0))
+ if (unlikely(--m_protection_count > 0))
DBUG_VOID_RETURN;
- if (unlikely(thd->global_read_lock))
+ if (unlikely(m_state))
DBUG_VOID_RETURN;
(void) pthread_mutex_lock(&LOCK_global_read_lock);
DBUG_ASSERT(protect_against_global_read_lock);
@@ -1450,7 +1454,7 @@ void start_waiting_global_read_lock(THD *thd)
@retval True Failure, thread was killed.
*/
-bool make_global_read_lock_block_commit(THD *thd)
+bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
{
bool error;
const char *old_message;
@@ -1459,7 +1463,7 @@ bool make_global_read_lock_block_commit(THD *thd)
If we didn't succeed lock_global_read_lock(), or if we already suceeded
make_global_read_lock_block_commit(), do nothing.
*/
- if (thd->global_read_lock != GOT_GLOBAL_READ_LOCK)
+ if (m_state != GRL_ACQUIRED)
DBUG_RETURN(0);
pthread_mutex_lock(&LOCK_global_read_lock);
/* increment this BEFORE waiting on cond (otherwise race cond) */
@@ -1476,7 +1480,7 @@ bool make_global_read_lock_block_commit(THD *thd)
if ((error= test(thd->killed)))
global_read_lock_blocks_commit--; // undo what we did
else
- thd->global_read_lock= MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT;
+ m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT;
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
DBUG_RETURN(error);
}
diff --git a/sql/mdl.cc b/sql/mdl.cc
index dce917a1a2e..3148571131a 100644
--- a/sql/mdl.cc
+++ b/sql/mdl.cc
@@ -19,6 +19,10 @@
#include <hash.h>
#include <mysqld_error.h>
+
+void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket);
+
+
static bool mdl_initialized= 0;
@@ -46,6 +50,41 @@ private:
};
+enum enum_deadlock_weight
+{
+ MDL_DEADLOCK_WEIGHT_DML= 0,
+ MDL_DEADLOCK_WEIGHT_DDL= 100
+};
+
+
+
+/**
+ A context of the recursive traversal through all contexts
+ in all sessions in search for deadlock.
+*/
+
+class Deadlock_detection_context
+{
+public:
+ Deadlock_detection_context(MDL_context *start_arg)
+ : start(start_arg),
+ victim(NULL),
+ current_search_depth(0)
+ { }
+ MDL_context *start;
+ MDL_context *victim;
+ uint current_search_depth;
+ static const uint MAX_SEARCH_DEPTH= 1000;
+};
+
+
+/**
+ Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps
+ and compatibility matrices.
+*/
+
+#define MDL_BIT(A) static_cast<MDL_lock::bitmap_t>(1U << A)
+
/**
The lock context. Created internally for an acquired lock.
For a given name, there exists only one MDL_lock instance,
@@ -60,53 +99,98 @@ private:
class MDL_lock
{
public:
- typedef I_P_List<MDL_ticket,
- I_P_List_adapter<MDL_ticket,
- &MDL_ticket::next_in_lock,
- &MDL_ticket::prev_in_lock> >
- Ticket_list;
+ typedef uchar bitmap_t;
- typedef Ticket_list::Iterator Ticket_iterator;
+ class Ticket_list
+ {
+ public:
+ typedef I_P_List<MDL_ticket,
+ I_P_List_adapter<MDL_ticket,
+ &MDL_ticket::next_in_lock,
+ &MDL_ticket::prev_in_lock> >
+ List;
+ operator const List &() const { return m_list; }
+ Ticket_list() :m_bitmap(0) {}
+
+ void add_ticket(MDL_ticket *ticket);
+ void remove_ticket(MDL_ticket *ticket);
+ bool is_empty() const { return m_list.is_empty(); }
+ bitmap_t bitmap() const { return m_bitmap; }
+ private:
+ void clear_bit_if_not_in_list(enum_mdl_type type);
+ private:
+ /** List of tickets. */
+ List m_list;
+ /** Bitmap of types of tickets in this list. */
+ bitmap_t m_bitmap;
+ };
+
+ typedef Ticket_list::List::Iterator Ticket_iterator;
public:
/** The key of the object (data) being protected. */
MDL_key key;
- /** List of granted tickets for this lock. */
- Ticket_list granted;
- /** Tickets for contexts waiting to acquire a shared lock. */
- Ticket_list waiting_shared;
- /**
- Tickets for contexts waiting to acquire an exclusive lock.
- There can be several upgraders and active exclusive
- locks belonging to the same context. E.g.
- in case of RENAME t1 to t2, t2 to t3, we attempt to
- exclusively lock t2 twice.
- */
- Ticket_list waiting_exclusive;
void *cached_object;
mdl_cached_object_release_hook cached_object_release_hook;
- /** Mutex protecting this lock context. */
- pthread_mutex_t m_mutex;
+ /**
+ Read-write lock protecting this lock context.
+
+ TODO/FIXME: Replace with RW-lock which will prefer readers
+ on all platforms and not only on Linux.
+ */
+ rw_lock_t m_rwlock;
bool is_empty() const
{
- return (granted.is_empty() && waiting_shared.is_empty() &&
- waiting_exclusive.is_empty());
+ return (m_granted.is_empty() && m_waiting.is_empty());
}
- bool has_pending_exclusive_lock()
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const = 0;
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const = 0;
+
+ bool has_pending_conflicting_lock(enum_mdl_type type);
+
+ bool can_grant_lock(enum_mdl_type type, MDL_context *requstor_ctx) const;
+
+ inline static MDL_lock *create(const MDL_key *key);
+
+ void notify_shared_locks(MDL_context *ctx)
{
- bool has_locks;
- pthread_mutex_lock(&m_mutex);
- has_locks= ! waiting_exclusive.is_empty();
- pthread_mutex_unlock(&m_mutex);
- return has_locks;
+ Ticket_iterator it(m_granted);
+ MDL_ticket *conflicting_ticket;
+
+ while ((conflicting_ticket= it++))
+ {
+ if (conflicting_ticket->get_ctx() != ctx)
+ notify_shared_lock(ctx->get_thd(), conflicting_ticket);
+ }
}
- virtual bool can_grant_lock(const MDL_context *requestor_ctx,
- enum_mdl_type type, bool is_upgrade)= 0;
- virtual void wake_up_waiters()= 0;
- inline static MDL_lock *create(const MDL_key *key);
+ /**
+ Wake up contexts which are waiting to acquire lock on the object and
+ which may succeed now, when we released some lock on it or removed
+ some pending request from its waiters list (the latter can happen,
+ for example, when context trying to acquire exclusive on the object
+ lock is killed).
+ */
+ void wake_up_waiters()
+ {
+ MDL_lock::Ticket_iterator it(m_waiting);
+ MDL_ticket *awake_ticket;
+
+ while ((awake_ticket= it++))
+ awake_ticket->get_ctx()->awake(MDL_context::NORMAL_WAKE_UP);
+ }
+ void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket);
+
+ bool find_deadlock(MDL_ticket *waiting_ticket,
+ Deadlock_detection_context *deadlock_ctx);
+
+ /** List of granted tickets for this lock. */
+ Ticket_list m_granted;
+ /** Tickets for contexts waiting to acquire a lock. */
+ Ticket_list m_waiting;
+public:
MDL_lock(const MDL_key *key_arg)
: key(key_arg),
@@ -116,31 +200,31 @@ public:
m_ref_release(0),
m_is_destroyed(FALSE)
{
- pthread_mutex_init(&m_mutex, NULL);
+ my_rwlock_init(&m_rwlock, NULL);
}
virtual ~MDL_lock()
{
- pthread_mutex_destroy(&m_mutex);
+ rwlock_destroy(&m_rwlock);
}
inline static void destroy(MDL_lock *lock);
public:
/**
These three members are used to make it possible to separate
- the mdl_locks.m_mutex mutex and MDL_lock::m_mutex in
+ the mdl_locks.m_mutex mutex and MDL_lock::m_rwlock in
MDL_map::find_or_insert() for increased scalability.
The 'm_is_destroyed' member is only set by destroyers that
- have both the mdl_locks.m_mutex and MDL_lock::m_mutex, thus
+ have both the mdl_locks.m_mutex and MDL_lock::m_rwlock, thus
holding any of the mutexes is sufficient to read it.
The 'm_ref_usage; is incremented under protection by
mdl_locks.m_mutex, but when 'm_is_destroyed' is set to TRUE, this
- member is moved to be protected by the MDL_lock::m_mutex.
+ member is moved to be protected by the MDL_lock::m_rwlock.
This means that the MDL_map::find_or_insert() which only
- holds the MDL_lock::m_mutex can compare it to 'm_ref_release'
+ holds the MDL_lock::m_rwlock can compare it to 'm_ref_release'
without acquiring mdl_locks.m_mutex again and if equal it can also
destroy the lock object safely.
The 'm_ref_release' is incremented under protection by
- MDL_lock::m_mutex.
+ MDL_lock::m_rwlock.
Note since we are only interested in equality of these two
counters we don't have to worry about overflows as long as
their size is big enough to hold maximum number of concurrent
@@ -164,9 +248,18 @@ public:
: MDL_lock(key_arg)
{ }
- virtual bool can_grant_lock(const MDL_context *requestor_ctx,
- enum_mdl_type type, bool is_upgrade);
- virtual void wake_up_waiters();
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const
+ {
+ return m_granted_incompatible;
+ }
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const
+ {
+ return m_waiting_incompatible;
+ }
+
+private:
+ static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
+ static const bitmap_t m_waiting_incompatible[MDL_TYPE_END];
};
@@ -182,9 +275,18 @@ public:
: MDL_lock(key_arg)
{ }
- virtual bool can_grant_lock(const MDL_context *requestor_ctx,
- enum_mdl_type type, bool is_upgrade);
- virtual void wake_up_waiters();
+ virtual const bitmap_t *incompatible_granted_types_bitmap() const
+ {
+ return m_granted_incompatible;
+ }
+ virtual const bitmap_t *incompatible_waiting_types_bitmap() const
+ {
+ return m_waiting_incompatible;
+ }
+
+private:
+ static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
+ static const bitmap_t m_waiting_incompatible[MDL_TYPE_END];
};
@@ -275,7 +377,7 @@ void MDL_map::destroy()
if it does not exist.
@retval non-NULL - Success. MDL_lock instance for the key with
- locked MDL_lock::m_mutex.
+ locked MDL_lock::m_rwlock.
@retval NULL - Failure (OOM).
*/
@@ -309,7 +411,7 @@ retry:
Find MDL_lock object corresponding to the key.
@retval non-NULL - MDL_lock instance for the key with locked
- MDL_lock::m_mutex.
+ MDL_lock::m_rwlock.
@retval NULL - There was no MDL_lock for the key.
*/
@@ -335,7 +437,7 @@ retry:
/**
- Release mdl_locks.m_mutex mutex and lock MDL_lock::m_mutex for lock
+ Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock
object from the hash. Handle situation when object was released
while the held no mutex.
@@ -357,7 +459,7 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock)
lock->m_ref_usage++;
pthread_mutex_unlock(&m_mutex);
- pthread_mutex_lock(&lock->m_mutex);
+ rw_wrlock(&lock->m_rwlock);
lock->m_ref_release++;
if (unlikely(lock->m_is_destroyed))
{
@@ -372,7 +474,7 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock)
*/
uint ref_usage= lock->m_ref_usage;
uint ref_release= lock->m_ref_release;
- pthread_mutex_unlock(&lock->m_mutex);
+ rw_unlock(&lock->m_rwlock);
if (ref_usage == ref_release)
MDL_lock::destroy(lock);
return TRUE;
@@ -391,8 +493,6 @@ void MDL_map::remove(MDL_lock *lock)
{
uint ref_usage, ref_release;
- safe_mutex_assert_owner(&lock->m_mutex);
-
if (lock->cached_object)
(*lock->cached_object_release_hook)(lock->cached_object);
@@ -402,14 +502,14 @@ void MDL_map::remove(MDL_lock *lock)
has the responsibility to release it.
Setting of m_is_destroyed to TRUE while holding _both_
- mdl_locks.m_mutex and MDL_lock::m_mutex mutexes transfers the
+ mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the
protection of m_ref_usage from mdl_locks.m_mutex to
- MDL_lock::m_mutex while removal of object from the hash makes
- it read-only. Therefore whoever acquires MDL_lock::m_mutex next
+ MDL_lock::m_rwlock while removal of object from the hash makes
+ it read-only. Therefore whoever acquires MDL_lock::m_rwlock next
will see most up to date version of m_ref_usage.
This means that when m_is_destroyed is TRUE and we hold the
- MDL_lock::m_mutex we can safely read the m_ref_usage
+ MDL_lock::m_rwlock we can safely read the m_ref_usage
member.
*/
pthread_mutex_lock(&m_mutex);
@@ -417,7 +517,7 @@ void MDL_map::remove(MDL_lock *lock)
lock->m_is_destroyed= TRUE;
ref_usage= lock->m_ref_usage;
ref_release= lock->m_ref_release;
- pthread_mutex_unlock(&lock->m_mutex);
+ rw_unlock(&lock->m_rwlock);
pthread_mutex_unlock(&m_mutex);
if (ref_usage == ref_release)
MDL_lock::destroy(lock);
@@ -431,10 +531,16 @@ void MDL_map::remove(MDL_lock *lock)
*/
MDL_context::MDL_context()
- :m_lt_or_ha_sentinel(NULL),
- m_thd(NULL)
+ :m_trans_sentinel(NULL),
+ m_thd(NULL),
+ m_needs_thr_lock_abort(FALSE),
+ m_waiting_for(NULL),
+ m_deadlock_weight(0),
+ m_signal(NO_WAKE_UP)
{
- pthread_cond_init(&m_ctx_wakeup_cond, NULL);
+ my_rwlock_init(&m_waiting_for_lock, NULL);
+ pthread_mutex_init(&m_signal_lock, NULL);
+ pthread_cond_init(&m_signal_cond, NULL);
}
@@ -453,7 +559,10 @@ MDL_context::MDL_context()
void MDL_context::destroy()
{
DBUG_ASSERT(m_tickets.is_empty());
- pthread_cond_destroy(&m_ctx_wakeup_cond);
+
+ rwlock_destroy(&m_waiting_for_lock);
+ pthread_mutex_destroy(&m_signal_lock);
+ pthread_cond_destroy(&m_signal_cond);
}
@@ -540,6 +649,13 @@ MDL_request::create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db,
}
+uint MDL_request::get_deadlock_weight() const
+{
+ return key.mdl_namespace() == MDL_key::GLOBAL ||
+ type > MDL_SHARED_NO_WRITE ?
+ MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML;
+}
+
/**
Auxiliary functions needed for creation/destruction of MDL_lock objects.
@@ -567,8 +683,6 @@ void MDL_lock::destroy(MDL_lock *lock)
}
-
-
/**
Auxiliary functions needed for creation/destruction of MDL_ticket
objects.
@@ -650,485 +764,463 @@ static inline void mdl_exit_cond(THD *thd,
}
-/**
- Check if request for the global metadata lock can be satisfied given
- its current state,
-
- @param requestor_ctx The context that identifies the owner of the request.
- @param type_arg The requested type of global lock. Usually derived
- from the type of lock on individual object to be
- requested. See table below.
- @param is_upgrade TRUE if we are performing lock upgrade (not unused).
-
- @retval TRUE - Lock request can be satisfied
- @retval FALSE - There is some conflicting lock
-
- Here is a compatibility matrix defined by this function:
-
- | | Satisfied or pending requests
- | | for global metadata lock
- ----------------+-------------+--------------------------------------------
- Type of request | Correspond. |
- for indiv. lock | global lock | Active-S Pending-S Active-IS(**) Active-IX
- ----------------+-------------+--------------------------------------------
- S, high-prio S | IS | + + + +
- upgradable S | IX | - - + +
- X | IX | - - + +
- S upgraded to X | IX (*) | 0 + + +
+MDL_context::mdl_signal_type MDL_context::wait()
+{
+ const char *old_msg;
+ st_my_thread_var *mysys_var= my_thread_var;
+ mdl_signal_type result;
- Here: "+" -- means that request can be satisfied
- "-" -- means that request can't be satisfied and should wait
- "0" -- means impossible situation.
+ pthread_mutex_lock(&m_signal_lock);
- (*) Since for upgradable shared locks we always take intention exclusive
- global lock at the same time when obtaining the shared lock, there
- is no need to obtain such lock during the upgrade itself.
- (**) Since intention shared global locks are compatible with all other
- type of locks we don't even have any accounting for them.
-*/
+ old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock);
-bool
-MDL_global_lock::can_grant_lock(const MDL_context *requestor_ctx,
- enum_mdl_type type_arg,
- bool is_upgrade)
+ while (! m_signal && !mysys_var->abort)
+ pthread_cond_wait(&m_signal_cond, &m_signal_lock);
+
+ result= m_signal;
+
+ MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg);
+
+ return result;
+}
+
+
+MDL_context::mdl_signal_type MDL_context::timed_wait(ulong timeout)
{
- switch (type_arg)
+ struct timespec abstime;
+ const char *old_msg;
+ mdl_signal_type result;
+ st_my_thread_var *mysys_var= my_thread_var;
+
+ pthread_mutex_lock(&m_signal_lock);
+
+ old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock);
+
+ if (! m_signal)
{
- case MDL_SHARED:
- if (! granted.is_empty() && granted.front()->m_type == MDL_INTENTION_EXCLUSIVE)
- {
- /*
- We are going to obtain global shared lock and there is active
- intention exclusive lock. Have to wait.
- */
- return FALSE;
- }
- return TRUE;
- break;
- case MDL_INTENTION_EXCLUSIVE:
- if ((! granted.is_empty() && granted.front()->m_type == MDL_SHARED) ||
- ! waiting_shared.is_empty())
- {
- /*
- We are going to obtain intention exclusive global lock and
- there is active or pending shared global lock. Have to wait.
- */
- return FALSE;
- }
- else
- return TRUE;
- break;
- default:
- DBUG_ASSERT(0);
- break;
+ set_timespec(abstime, timeout);
+ pthread_cond_timedwait(&m_signal_cond, &m_signal_lock, &abstime);
}
- return FALSE;
+
+ result= (m_signal != NO_WAKE_UP) ? m_signal : TIMEOUT_WAKE_UP;
+
+ MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg);
+
+ return result;
}
/**
- Wake up contexts which are waiting to acquire the global
- metadata lock and which may succeed now, when we released it, or
- removed a blocking request for it from the waiters list.
- The latter can happen when the context trying to acquire the
- global shared lock is killed.
+ Clear bit corresponding to the type of metadata lock in bitmap representing
+ set of such types if list of tickets does not contain ticket with such type.
+
+ @param[in,out] bitmap Bitmap representing set of types of locks.
+ @param[in] list List to inspect.
+ @param[in] type Type of metadata lock to look up in the list.
*/
-void MDL_global_lock::wake_up_waiters()
+void MDL_lock::Ticket_list::clear_bit_if_not_in_list(enum_mdl_type type)
{
- /*
- If there are no active locks or they are of INTENTION
- EXCLUSIVE type and there are no pending requests for global
- SHARED lock, wake up contexts waiting for an INTENTION
- EXCLUSIVE lock.
- This happens when we release the global SHARED lock or abort
- or remove a pending request for it, i.e. abort the
- context waiting for it.
- */
- if ((granted.is_empty() ||
- granted.front()->m_type == MDL_INTENTION_EXCLUSIVE) &&
- waiting_shared.is_empty() && ! waiting_exclusive.is_empty())
- {
- MDL_lock::Ticket_iterator it(waiting_exclusive);
- MDL_ticket *awake_ticket;
- while ((awake_ticket= it++))
- awake_ticket->get_ctx()->awake();
- }
+ MDL_lock::Ticket_iterator it(m_list);
+ const MDL_ticket *ticket;
+ while ((ticket= it++))
+ if (ticket->get_type() == type)
+ return;
+ m_bitmap&= ~ MDL_BIT(type);
+}
+
+
+/**
+ Add ticket to MDL_lock's list of waiting requests and
+ update corresponding bitmap of lock types.
+*/
+
+void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket)
+{
+ m_list.push_front(ticket);
+ m_bitmap|= MDL_BIT(ticket->get_type());
+}
+
+
+/**
+ Remove ticket from MDL_lock's list of requests and
+ update corresponding bitmap of lock types.
+*/
+
+void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket)
+{
+ m_list.remove(ticket);
/*
- If there are no active locks, wake up contexts waiting for
- the global shared lock (happens when an INTENTION EXCLUSIVE
- lock is released).
-
- We don't wake up contexts waiting for the global shared lock
- if there is an active global shared lock since such situation
- is transient and in it contexts marked as waiting for global
- shared lock must be already woken up and simply have not
- managed to update lock object yet.
+ Check if waiting queue has another ticket with the same type as
+ one which was removed. If there is no such ticket, i.e. we have
+ removed last ticket of particular type, then we need to update
+ bitmap of waiting ticket's types.
+ Note that in most common case, i.e. when shared lock is removed
+ from waiting queue, we are likely to find ticket of the same
+ type early without performing full iteration through the list.
+ So this method should not be too expensive.
*/
- if (granted.is_empty() &&
- ! waiting_shared.is_empty())
- {
- MDL_lock::Ticket_iterator it(waiting_shared);
- MDL_ticket *awake_ticket;
- while ((awake_ticket= it++))
- awake_ticket->get_ctx()->awake();
- }
+ clear_bit_if_not_in_list(ticket->get_type());
}
/**
- Check if request for the per-object lock can be satisfied given current
- state of the lock.
+ Compatibility (or rather "incompatibility") matrices for global metadata
+ lock. Arrays of bitmaps which elements specify which granted/waiting locks
+ are incompatible with type of lock being requested.
+
+ Here is how types of individual locks are translated to type of global lock:
+
+ ----------------+-------------+
+ Type of request | Correspond. |
+ for indiv. lock | global lock |
+ ----------------+-------------+
+ S, SH, SR, SW | IS |
+ SNW, SNRW, X | IX |
+ SNW, SNRW -> X | IX (*) |
+
+ The first array specifies if particular type of request can be satisfied
+ if there is granted global lock of certain type.
+
+ | Type of active |
+ Request | global lock |
+ type | IS(**) IX S |
+ ---------+----------------+
+ IS | + + + |
+ IX | + + - |
+ S | + - + |
+
+ The second array specifies if particular type of request can be satisfied
+ if there is already waiting request for the global lock of certain type.
+ I.e. it specifies what is the priority of different lock types.
+
+ | Pending |
+ Request | global lock |
+ type | IS(**) IX S |
+ ---------+--------------+
+ IS | + + + |
+ IX | + + - |
+ S | + + + |
- @param requestor_ctx The context that identifies the owner of the request.
- @param type_arg The requested lock type.
- @param is_upgrade Must be set to TRUE when we are upgrading
- a shared upgradable lock to exclusive.
+ Here: "+" -- means that request can be satisfied
+ "-" -- means that request can't be satisfied and should wait
- @retval TRUE Lock request can be satisfied
- @retval FALSE There is some conflicting lock.
+ (*) Since for upgradable locks we always take intention exclusive global
+ lock at the same time when obtaining the shared lock, there is no
+ need to obtain such lock during the upgrade itself.
+ (**) Since intention shared global locks are compatible with all other
+ type of locks we don't even have any accounting for them.
+*/
+
+const MDL_lock::bitmap_t MDL_global_lock::m_granted_incompatible[MDL_TYPE_END] =
+{
+ MDL_BIT(MDL_SHARED), MDL_BIT(MDL_INTENTION_EXCLUSIVE), 0, 0, 0, 0, 0, 0
+};
- This function defines the following compatibility matrix for metadata locks:
+const MDL_lock::bitmap_t MDL_global_lock::m_waiting_incompatible[MDL_TYPE_END] =
+{
+ MDL_BIT(MDL_SHARED), 0, 0, 0, 0, 0, 0, 0
+};
- | Satisfied or pending requests which we have in MDL_lock
- ----------------+---------------------------------------------------------
- Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X
- ----------------+---------------------------------------------------------
- S, upgradable S | + - - (*) -
- High-prio S | + + - +
- X | - + - -
- S upgraded to X | - (**) + 0 0
+
+/**
+ Compatibility (or rather "incompatibility") matrices for per-object
+ metadata lock. Arrays of bitmaps which elements specify which granted/
+ waiting locks are incompatible with type of lock being requested.
+
+ The first array specifies if particular type of request can be satisfied
+ if there is granted lock of certain type.
+
+ Request | Granted requests for lock |
+ type | S SH SR SW SNW SNRW X |
+ ----------+------------------------------+
+ S | + + + + + + - |
+ SH | + + + + + + - |
+ SR | + + + + + - - |
+ SW | + + + + - - - |
+ SNW | + + + - - - - |
+ SNRW | + + - - - - - |
+ X | - - - - - - - |
+ SNW -> X | - - - 0 0 0 0 |
+ SNRW -> X | - - 0 0 0 0 0 |
+
+ The second array specifies if particular type of request can be satisfied
+ if there is waiting request for the same lock of certain type. In other
+ words it specifies what is the priority of different lock types.
+
+ Request | Pending requests for lock |
+ type | S SH SR SW SNW SNRW X |
+ ----------+-----------------------------+
+ S | + + + + + + - |
+ SH | + + + + + + + |
+ SR | + + + + + - - |
+ SW | + + + + - - - |
+ SNW | + + + + + + - |
+ SNRW | + + + + + + - |
+ X | + + + + + + + |
+ SNW -> X | + + + + + + + |
+ SNRW -> X | + + + + + + + |
Here: "+" -- means that request can be satisfied
"-" -- means that request can't be satisfied and should wait
"0" -- means impossible situation which will trigger assert
- (*) Unless active exclusive lock belongs to the same context as shared
- lock being requested.
- (**) Unless all active shared locks belong to the same context as one
- being upgraded.
+ @note In cases then current context already has "stronger" type
+ of lock on the object it will be automatically granted
+ thanks to usage of the MDL_context::find_ticket() method.
+*/
+
+const MDL_lock::bitmap_t
+MDL_object_lock::m_granted_incompatible[MDL_TYPE_END] =
+{
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) |
+ MDL_BIT(MDL_SHARED_READ),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) |
+ MDL_BIT(MDL_SHARED_READ) | MDL_BIT(MDL_SHARED_HIGH_PRIO) |
+ MDL_BIT(MDL_SHARED)
+};
+
+
+const MDL_lock::bitmap_t
+MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END] =
+{
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE),
+ 0,
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) |
+ MDL_BIT(MDL_SHARED_NO_WRITE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ MDL_BIT(MDL_EXCLUSIVE),
+ 0
+};
+
+
+/**
+ Check if request for the metadata lock can be satisfied given its
+ current state.
+
+ @param type_arg The requested lock type.
+ @param requestor_ctx The MDL context of the requestor.
+
+ @retval TRUE Lock request can be satisfied
+ @retval FALSE There is some conflicting lock.
+
+ @note In cases then current context already has "stronger" type
+ of lock on the object it will be automatically granted
+ thanks to usage of the MDL_context::find_ticket() method.
*/
bool
-MDL_object_lock::can_grant_lock(const MDL_context *requestor_ctx,
- enum_mdl_type type_arg,
- bool is_upgrade)
+MDL_lock::can_grant_lock(enum_mdl_type type_arg,
+ MDL_context *requestor_ctx) const
{
bool can_grant= FALSE;
-
- switch (type_arg) {
- case MDL_SHARED:
- case MDL_SHARED_UPGRADABLE:
- case MDL_SHARED_HIGH_PRIO:
- if (granted.is_empty() || granted.front()->is_shared())
- {
- /* Pending exclusive locks have higher priority over shared locks. */
- if (waiting_exclusive.is_empty() || type_arg == MDL_SHARED_HIGH_PRIO)
- can_grant= TRUE;
- }
- else if (granted.front()->get_ctx() == requestor_ctx)
- {
- /*
- When exclusive lock comes from the same context we can satisfy our
- shared lock. This is required for CREATE TABLE ... SELECT ... and
- ALTER VIEW ... AS ....
- */
+ bitmap_t waiting_incompat_map= incompatible_waiting_types_bitmap()[type_arg];
+ bitmap_t granted_incompat_map= incompatible_granted_types_bitmap()[type_arg];
+ /*
+ New lock request can be satisfied iff:
+ - There are no incompatible types of satisfied requests
+ in other contexts
+ - There are no waiting requests which have higher priority
+ than this request.
+ */
+ if (! (m_waiting.bitmap() & waiting_incompat_map))
+ {
+ if (! (m_granted.bitmap() & granted_incompat_map))
can_grant= TRUE;
- }
- break;
- case MDL_EXCLUSIVE:
- if (is_upgrade)
+ else
{
- /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. */
- MDL_ticket *conflicting_ticket;
- MDL_lock::Ticket_iterator it(granted);
+ Ticket_iterator it(m_granted);
+ MDL_ticket *ticket;
- /*
- There should be no active exclusive locks since we own shared lock
- on the object.
- */
- DBUG_ASSERT(granted.front()->is_shared());
-
- while ((conflicting_ticket= it++))
+ /* Check that the incompatible lock belongs to some other context. */
+ while ((ticket= it++))
{
- /*
- When upgrading shared lock to exclusive one we can have other shared
- locks for the same object in the same context, e.g. in case when several
- instances of TABLE are open.
- */
- if (conflicting_ticket->get_ctx() != requestor_ctx)
+ if (ticket->get_ctx() != requestor_ctx &&
+ ticket->is_incompatible_when_granted(type_arg))
break;
}
- /* Grant lock if there are no conflicting shared locks. */
- if (conflicting_ticket == NULL)
+ if (ticket == NULL) /* Incompatible locks are our own. */
can_grant= TRUE;
- break;
- }
- else if (granted.is_empty())
- {
- /*
- We are trying to acquire fresh MDL_EXCLUSIVE and there are no active
- shared or exclusive locks.
- */
- can_grant= TRUE;
}
- break;
- default:
- DBUG_ASSERT(0);
}
return can_grant;
}
-/**
- Wake up contexts which are waiting to acquire lock on individual object
- and which may succeed now, when we released some lock on it or removed
- some pending request from its waiters list (the latter can happen, for
- example, when context trying to acquire exclusive lock is killed).
-*/
+/** Remove a ticket from waiting or pending queue and wakeup up waiters. */
-void MDL_object_lock::wake_up_waiters()
+void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket)
{
- /*
- There are no active locks or they are of shared type.
- We have to wake up contexts waiting for shared lock even if there is
- a pending exclusive lock as some them might be trying to acquire high
- priority shared lock.
- */
- if ((granted.is_empty() || granted.front()->is_shared()) &&
- ! waiting_shared.is_empty())
- {
- MDL_lock::Ticket_iterator it(waiting_shared);
- MDL_ticket *waiting_ticket;
- while ((waiting_ticket= it++))
- waiting_ticket->get_ctx()->awake();
- }
-
- /*
- There are no active locks (shared or exclusive).
- Wake up contexts waiting to acquire exclusive locks.
- */
- if (granted.is_empty() && ! waiting_exclusive.is_empty())
+ rw_wrlock(&m_rwlock);
+ (this->*list).remove_ticket(ticket);
+ if (is_empty())
+ mdl_locks.remove(this);
+ else
{
- MDL_lock::Ticket_iterator it(waiting_exclusive);
- MDL_ticket *waiting_ticket;
- while ((waiting_ticket= it++))
- waiting_ticket->get_ctx()->awake();
+ /*
+ There can be some contexts waiting to acquire a lock
+ which now might be able to do it. Wake them up!
+ */
+ wake_up_waiters();
+ rw_unlock(&m_rwlock);
}
}
/**
- Check whether the context already holds a compatible lock ticket
- on an object.
- Start searching the transactional locks. If not
- found in the list of transactional locks, look at LOCK TABLES
- and HANDLER locks.
+ Check if we have any pending locks which conflict with existing
+ shared lock.
- @param mdl_request Lock request object for lock to be acquired
- @param[out] is_lt_or_ha Did we pass beyond m_lt_or_ha_sentinel while
- searching for ticket?
+ @pre The ticket must match an acquired lock.
- @return A pointer to the lock ticket for the object or NULL otherwise.
+ @return TRUE if there is a conflicting lock request, FALSE otherwise.
*/
-MDL_ticket *
-MDL_context::find_ticket(MDL_request *mdl_request,
- bool *is_lt_or_ha)
+bool MDL_lock::has_pending_conflicting_lock(enum_mdl_type type)
{
- MDL_ticket *ticket;
- Ticket_iterator it(m_tickets);
-
- *is_lt_or_ha= FALSE;
-
- while ((ticket= it++))
- {
- if (ticket == m_lt_or_ha_sentinel)
- *is_lt_or_ha= TRUE;
+ bool result;
- if (mdl_request->type == ticket->m_type &&
- mdl_request->key.is_equal(&ticket->m_lock->key))
- break;
- }
+ safe_mutex_assert_not_owner(&LOCK_open);
- return ticket;
+ rw_rdlock(&m_rwlock);
+ result= (m_waiting.bitmap() & incompatible_granted_types_bitmap()[type]);
+ rw_unlock(&m_rwlock);
+ return result;
}
/**
- Try to acquire global intention exclusive lock.
+ Check if ticket represents metadata lock of "stronger" or equal type
+ than specified one. I.e. if metadata lock represented by ticket won't
+ allow any of locks which are not allowed by specified type of lock.
- @param[in/out] mdl_request Lock request object for lock to be acquired
-
- @retval FALSE Success. The lock may have not been acquired.
- One needs to check value of 'MDL_request::ticket'
- to find out what has happened.
- @retval TRUE Error.
+ @return TRUE if ticket has stronger or equal type
+ FALSE otherwise.
*/
-bool
-MDL_context::
-try_acquire_global_intention_exclusive_lock(MDL_request *mdl_request)
+bool MDL_ticket::has_stronger_or_equal_type(enum_mdl_type type) const
{
- DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::GLOBAL &&
- mdl_request->type == MDL_INTENTION_EXCLUSIVE);
+ const MDL_lock::bitmap_t *
+ granted_incompat_map= m_lock->incompatible_granted_types_bitmap();
+
+ return ! (granted_incompat_map[type] & ~(granted_incompat_map[m_type]));
+}
- if (is_global_lock_owner(MDL_SHARED))
- {
- my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
- return TRUE;
- }
- return try_acquire_lock_impl(mdl_request);
+bool MDL_ticket::is_incompatible_when_granted(enum_mdl_type type) const
+{
+ return (MDL_BIT(m_type) &
+ m_lock->incompatible_granted_types_bitmap()[type]);
}
-/**
- Acquire one lock with waiting for conflicting locks to go away if needed.
+bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const
+{
+ return (MDL_BIT(m_type) &
+ m_lock->incompatible_waiting_types_bitmap()[type]);
+}
- @note This is an internal method which should not be used outside of MDL
- subsystem as in most cases simply waiting for conflicting locks to
- go away will lead to deadlock.
- @param mdl_request [in/out] Lock request object for lock to be acquired
+/**
+ Acquire global intention exclusive lock.
- @retval FALSE Success. MDL_request::ticket points to the ticket
- for the lock.
- @retval TRUE Failure (Out of resources or waiting is aborted),
+ @param[in] mdl_request Lock request object for lock to be acquired
+
+ @retval FALSE Success. The lock has been acquired.
+ @retval TRUE Error.
*/
bool
-MDL_context::acquire_lock_impl(MDL_request *mdl_request)
+MDL_context::acquire_global_intention_exclusive_lock(MDL_request *mdl_request)
{
- bool not_used;
- MDL_ticket *ticket;
- MDL_key *key= &mdl_request->key;
- MDL_lock *lock;
- const char *old_msg;
- st_my_thread_var *mysys_var= my_thread_var;
-
- DBUG_ASSERT(mdl_request->ticket == NULL);
- safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::GLOBAL &&
+ mdl_request->type == MDL_INTENTION_EXCLUSIVE);
/*
- Grant lock without waiting if this context already owns this type of lock
- on this object.
-
- The fact that we don't wait in such situation allows to avoid deadlocks
- in cases when pending request for global shared lock pops up after the
- moment when thread has acquired its first intention exclusive lock but
- before it has requested the second instance of such lock.
+ If this is a non-recursive attempt to acquire global intention
+ exclusive lock we might have to wait until active global shared
+ lock or pending requests will go away. Since we won't hold any
+ resources (except associated with open HANDLERs) while doing it
+ deadlocks are not possible.
*/
- if ((mdl_request->ticket= find_ticket(mdl_request, &not_used)))
- return FALSE;
-
- if (! (ticket= MDL_ticket::create(this, mdl_request->type)))
- return TRUE;
-
- /* The below call also implicitly locks MDL_lock::m_mutex. */
- if (! (lock= mdl_locks.find_or_insert(key)))
- {
- MDL_ticket::destroy(ticket);
- return TRUE;
- }
+ DBUG_ASSERT(is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE) ||
+ ! has_locks() ||
+ (m_trans_sentinel && m_tickets.front() == m_trans_sentinel));
- old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_ctx_wakeup_cond,
- &lock->m_mutex);
+ return acquire_lock(mdl_request);
+}
- if (! lock->can_grant_lock(this, mdl_request->type, FALSE))
- {
- if (mdl_request->is_shared())
- lock->waiting_shared.push_front(ticket);
- else
- lock->waiting_exclusive.push_front(ticket);
- do
- {
- pthread_cond_wait(&m_ctx_wakeup_cond, &lock->m_mutex);
- }
- while (! lock->can_grant_lock(this, mdl_request->type, FALSE) &&
- ! mysys_var->abort);
+/**
+ Check whether the context already holds a compatible lock ticket
+ on an object.
+ Start searching the transactional locks. If not
+ found in the list of transactional locks, look at LOCK TABLES
+ and HANDLER locks.
- if (mysys_var->abort)
- {
- /*
- We have to do MDL_EXIT_COND here and then re-acquire the lock
- as there is a chance that we will destroy MDL_lock object and
- won't be able to call MDL_EXIT_COND after it.
- */
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
+ @param mdl_request Lock request object for lock to be acquired
+ @param[out] is_transactional FALSE if we pass beyond m_trans_sentinel
+ while searching for ticket, otherwise TRUE.
- pthread_mutex_lock(&lock->m_mutex);
- /* Get rid of pending ticket. */
- if (mdl_request->is_shared())
- lock->waiting_shared.remove(ticket);
- else
- lock->waiting_exclusive.remove(ticket);
- if (lock->is_empty())
- mdl_locks.remove(lock);
- else
- {
- lock->wake_up_waiters();
- pthread_mutex_unlock(&lock->m_mutex);
- }
- MDL_ticket::destroy(ticket);
- return TRUE;
- }
+ @note Tickets which correspond to lock types "stronger" than one
+ being requested are also considered compatible.
- if (mdl_request->is_shared())
- lock->waiting_shared.remove(ticket);
- else
- lock->waiting_exclusive.remove(ticket);
- }
+ @return A pointer to the lock ticket for the object or NULL otherwise.
+*/
- lock->granted.push_front(ticket);
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
+MDL_ticket *
+MDL_context::find_ticket(MDL_request *mdl_request,
+ bool *is_transactional)
+{
+ MDL_ticket *ticket;
+ Ticket_iterator it(m_tickets);
- ticket->m_state= MDL_ACQUIRED;
- ticket->m_lock= lock;
+ *is_transactional= TRUE;
- m_tickets.push_front(ticket);
+ while ((ticket= it++))
+ {
+ if (ticket == m_trans_sentinel)
+ *is_transactional= FALSE;
- mdl_request->ticket= ticket;
+ if (mdl_request->key.is_equal(&ticket->m_lock->key) &&
+ ticket->has_stronger_or_equal_type(mdl_request->type))
+ break;
+ }
- return FALSE;
+ return ticket;
}
/**
- Acquire global intention exclusive lock.
+ Acquire one lock with waiting for conflicting locks to go away if needed.
- @param[in] mdl_request Lock request object for lock to be acquired
+ @note This is an internal method which should not be used outside of MDL
+ subsystem as in most cases simply waiting for conflicting locks to
+ go away will lead to deadlock.
- @retval FALSE Success. The lock has been acquired.
- @retval TRUE Error.
+ @param mdl_request [in/out] Lock request object for lock to be acquired
+
+ @retval FALSE Success. MDL_request::ticket points to the ticket
+ for the lock.
+ @retval TRUE Failure (Out of resources or waiting is aborted),
*/
bool
-MDL_context::acquire_global_intention_exclusive_lock(MDL_request *mdl_request)
+MDL_context::acquire_lock(MDL_request *mdl_request)
{
- DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::GLOBAL &&
- mdl_request->type == MDL_INTENTION_EXCLUSIVE);
-
- if (is_global_lock_owner(MDL_SHARED))
- {
- my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
- return TRUE;
- }
-
- /*
- If this is a non-recursive attempt to acquire global intention
- exclusive lock we might have to wait until active global shared
- lock or pending requests will go away. Since we won't hold any
- resources (except associated with open HANDLERs) while doing it
- deadlocks are not possible,
- */
- DBUG_ASSERT(is_global_lock_owner(MDL_INTENTION_EXCLUSIVE) ||
- ! has_locks() ||
- (m_lt_or_ha_sentinel &&
- m_tickets.front() == m_lt_or_ha_sentinel));
-
return acquire_lock_impl(mdl_request);
}
@@ -1136,22 +1228,42 @@ MDL_context::acquire_global_intention_exclusive_lock(MDL_request *mdl_request)
/**
Try to acquire one lock.
+ Unlike exclusive locks, shared locks are acquired one by
+ one. This is interface is chosen to simplify introduction of
+ the new locking API to the system. MDL_context::try_acquire_lock()
+ is currently used from open_table(), and there we have only one
+ table to work with.
+
+ This function may also be used to try to acquire an exclusive
+ lock on a destination table, by ALTER TABLE ... RENAME.
+
+ Returns immediately without any side effect if encounters a lock
+ conflict. Otherwise takes the lock.
+
+ FIXME: Compared to lock_table_name_if_not_cached() (from 5.1)
+ it gives slightly more false negatives.
+
@param mdl_request [in/out] Lock request object for lock to be acquired
@retval FALSE Success. The lock may have not been acquired.
Check the ticket, if it's NULL, a conflicting lock
- exists.
+ exists and another attempt should be made after releasing
+ all current locks and waiting for conflicting lock go
+ away (using MDL_context::wait_for_lock()).
@retval TRUE Out of resources, an error has been reported.
*/
bool
-MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
+MDL_context::try_acquire_lock(MDL_request *mdl_request)
{
MDL_lock *lock;
MDL_key *key= &mdl_request->key;
MDL_ticket *ticket;
- bool is_lt_or_ha;
+ bool is_transactional;
+ DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE ||
+ (is_lock_owner(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE)));
DBUG_ASSERT(mdl_request->ticket == NULL);
/* Don't take chances in production. */
@@ -1162,10 +1274,10 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
Check whether the context already holds a shared lock on the object,
and if so, grant the request.
*/
- if ((ticket= find_ticket(mdl_request, &is_lt_or_ha)))
+ if ((ticket= find_ticket(mdl_request, &is_transactional)))
{
- DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED);
- DBUG_ASSERT(ticket->m_type == mdl_request->type);
+ DBUG_ASSERT(ticket->m_lock);
+ DBUG_ASSERT(ticket->m_type >= mdl_request->type);
/*
If the request is for a transactional lock, and we found
a transactional lock, just reuse the found ticket.
@@ -1184,7 +1296,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
a different alias.
*/
mdl_request->ticket= ticket;
- if (is_lt_or_ha && clone_ticket(mdl_request))
+ if (!is_transactional && clone_ticket(mdl_request))
{
/* Clone failed. */
mdl_request->ticket= NULL;
@@ -1196,19 +1308,18 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
return TRUE;
- /* The below call also implicitly locks MDL_lock::m_mutex. */
+ /* The below call implicitly locks MDL_lock::m_rwlock on success. */
if (!(lock= mdl_locks.find_or_insert(key)))
{
MDL_ticket::destroy(ticket);
return TRUE;
}
- if (lock->can_grant_lock(this, mdl_request->type, FALSE))
+ if (lock->can_grant_lock(mdl_request->type, this))
{
- lock->granted.push_front(ticket);
- pthread_mutex_unlock(&lock->m_mutex);
+ lock->m_granted.add_ticket(ticket);
+ rw_unlock(&lock->m_rwlock);
- ticket->m_state= MDL_ACQUIRED;
ticket->m_lock= lock;
m_tickets.push_front(ticket);
@@ -1219,7 +1330,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
{
/* We can't get here if we allocated a new lock. */
DBUG_ASSERT(! lock->is_empty());
- pthread_mutex_unlock(&lock->m_mutex);
+ rw_unlock(&lock->m_rwlock);
MDL_ticket::destroy(ticket);
}
@@ -1228,42 +1339,10 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request)
/**
- Try to acquire one shared lock.
-
- Unlike exclusive locks, shared locks are acquired one by
- one. This is interface is chosen to simplify introduction of
- the new locking API to the system. MDL_context::try_acquire_shared_lock()
- is currently used from open_table(), and there we have only one
- table to work with.
-
- In future we may consider allocating multiple shared locks at once.
-
- @param mdl_request [in/out] Lock request object for lock to be acquired
-
- @retval FALSE Success. The lock may have not been acquired.
- Check the ticket, if it's NULL, a conflicting lock
- exists and another attempt should be made after releasing
- all current locks and waiting for conflicting lock go
- away (using MDL_context::wait_for_locks()).
- @retval TRUE Out of resources, an error has been reported.
-*/
-
-bool
-MDL_context::try_acquire_shared_lock(MDL_request *mdl_request)
-{
- DBUG_ASSERT(mdl_request->is_shared());
- DBUG_ASSERT(mdl_request->type != MDL_SHARED_UPGRADABLE ||
- is_global_lock_owner(MDL_INTENTION_EXCLUSIVE));
-
- return try_acquire_lock_impl(mdl_request);
-}
-
-
-/**
- Create a copy of a granted ticket.
+ Create a copy of a granted ticket.
This is used to make sure that HANDLER ticket
is never shared with a ticket that belongs to
- a transaction, so that when we HANDLER CLOSE,
+ a transaction, so that when we HANDLER CLOSE,
we don't release a transactional ticket, and
vice versa -- when we COMMIT, we don't mistakenly
release a ticket for an open HANDLER.
@@ -1278,25 +1357,30 @@ MDL_context::clone_ticket(MDL_request *mdl_request)
MDL_ticket *ticket;
safe_mutex_assert_not_owner(&LOCK_open);
- /* Only used for HANDLER. */
- DBUG_ASSERT(mdl_request->ticket && mdl_request->ticket->is_shared());
-
+ /*
+ By submitting mdl_request->type to MDL_ticket::create()
+ we effectively downgrade the cloned lock to the level of
+ the request.
+ */
if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
return TRUE;
- ticket->m_state= MDL_ACQUIRED;
+ /* clone() is not supposed to be used to get a stronger lock. */
+ DBUG_ASSERT(ticket->m_type <= mdl_request->ticket->m_type);
+
ticket->m_lock= mdl_request->ticket->m_lock;
mdl_request->ticket= ticket;
- pthread_mutex_lock(&ticket->m_lock->m_mutex);
- ticket->m_lock->granted.push_front(ticket);
- pthread_mutex_unlock(&ticket->m_lock->m_mutex);
+ rw_wrlock(&ticket->m_lock->m_rwlock);
+ ticket->m_lock->m_granted.add_ticket(ticket);
+ rw_unlock(&ticket->m_lock->m_rwlock);
m_tickets.push_front(ticket);
return FALSE;
}
+
/**
Notify a thread holding a shared metadata lock which
conflicts with a pending exclusive lock.
@@ -1307,21 +1391,20 @@ MDL_context::clone_ticket(MDL_request *mdl_request)
void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket)
{
- if (conflicting_ticket->is_shared())
+ /* Only try to abort locks on which we back off. */
+ if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE)
{
- THD *conflicting_thd= conflicting_ticket->get_ctx()->get_thd();
+ MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
+ THD *conflicting_thd= conflicting_ctx->get_thd();
DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */
/*
- If the thread that holds the conflicting lock is waiting in MDL
- subsystem it has to be woken up by calling MDL_context::awake().
- */
- conflicting_ticket->get_ctx()->awake();
- /*
- If it is waiting on table-level lock or some other non-MDL resource
- we delegate its waking up to code outside of MDL.
+ If thread which holds conflicting lock is waiting on table-level
+ lock or some other non-MDL resource we might need to wake it up
+ by calling code outside of MDL.
*/
- mysql_notify_thread_having_shared_lock(thd, conflicting_thd);
+ mysql_notify_thread_having_shared_lock(thd, conflicting_thd,
+ conflicting_ctx->get_needs_thr_lock_abort());
}
}
@@ -1331,29 +1414,26 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket)
@param mdl_request Request for the lock to be acqured.
- @note Should not be used outside of MDL subsystem. Instead one should
- call acquire_exclusive_lock() or acquire_exclusive_locks() methods
- which ensure that conditions for deadlock-free lock acquisition are
- fulfilled.
+ @note Should not be used outside of MDL subsystem. Instead one
+ should call acquire_lock() or acquire_locks()
+ methods which ensure that conditions for deadlock-free
+ lock acquisition are fulfilled.
@retval FALSE Success
@retval TRUE Failure
*/
-bool MDL_context::acquire_exclusive_lock_impl(MDL_request *mdl_request)
+bool MDL_context::acquire_lock_impl(MDL_request *mdl_request)
{
MDL_lock *lock;
- const char *old_msg;
MDL_ticket *ticket;
bool not_used;
st_my_thread_var *mysys_var= my_thread_var;
MDL_key *key= &mdl_request->key;
- DBUG_ASSERT(mdl_request->type == MDL_EXCLUSIVE &&
- mdl_request->ticket == NULL);
-
safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(mdl_request->ticket == NULL);
/* Don't take chances in production. */
mdl_request->ticket= NULL;
@@ -1363,129 +1443,67 @@ bool MDL_context::acquire_exclusive_lock_impl(MDL_request *mdl_request)
*/
if ((ticket= find_ticket(mdl_request, &not_used)))
{
- DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED);
- DBUG_ASSERT(ticket->m_type == MDL_EXCLUSIVE);
+ DBUG_ASSERT(ticket->m_lock);
mdl_request->ticket= ticket;
return FALSE;
}
- DBUG_ASSERT(is_global_lock_owner(MDL_INTENTION_EXCLUSIVE));
+ DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE ||
+ is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
/* Early allocation: ticket will be needed in any case. */
if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
return TRUE;
- /* The below call also implicitly locks MDL_lock::m_mutex. */
+ /* The below call implicitly locks MDL_lock::m_rwlock on success. */
if (!(lock= mdl_locks.find_or_insert(key)))
{
MDL_ticket::destroy(ticket);
return TRUE;
}
- lock->waiting_exclusive.push_front(ticket);
+ ticket->m_lock= lock;
- old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_ctx_wakeup_cond,
- &lock->m_mutex);
+ lock->m_waiting.add_ticket(ticket);
- while (!lock->can_grant_lock(this, mdl_request->type, FALSE))
+ while (!lock->can_grant_lock(mdl_request->type, this))
{
- if (m_lt_or_ha_sentinel)
- {
- /*
- We're about to start waiting. Don't do it if we have
- HANDLER locks (we can't have any other locks here).
- Waiting with locks may lead to a deadlock.
+ wait_reset();
- We have to do MDL_EXIT_COND here and then re-acquire the
- lock as there is a chance that we will destroy MDL_lock
- object and won't be able to call MDL_EXIT_COND after it.
- */
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
-
- pthread_mutex_lock(&lock->m_mutex);
- /* Get rid of pending ticket. */
- lock->waiting_exclusive.remove(ticket);
- if (lock->is_empty())
- mdl_locks.remove(lock);
- else
- {
- /*
- There can be some contexts waiting to acquire shared
- lock which now might be able to do it. Wake them up!
- */
- lock->wake_up_waiters();
- pthread_mutex_unlock(&lock->m_mutex);
- }
- MDL_ticket::destroy(ticket);
- my_error(ER_LOCK_DEADLOCK, MYF(0));
- return TRUE;
- }
+ if (ticket->is_upgradable_or_exclusive())
+ lock->notify_shared_locks(this);
- MDL_ticket *conflicting_ticket;
- MDL_lock::Ticket_iterator it(lock->granted);
+ rw_unlock(&lock->m_rwlock);
- while ((conflicting_ticket= it++))
- notify_shared_lock(m_thd, conflicting_ticket);
+ set_deadlock_weight(mdl_request->get_deadlock_weight());
+ will_wait_for(ticket);
/* There is a shared or exclusive lock on the object. */
- DEBUG_SYNC(m_thd, "mdl_acquire_exclusive_locks_wait");
+ DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait");
- /*
- Another thread might have obtained a shared MDL lock on some table
- but has not yet opened it and/or tried to obtain data lock on it.
- Also invocation of acquire_exclusive_lock() method and consequently
- first call to notify_shared_lock() might have happened right after
- thread holding shared metadata lock in wait_for_locks() method
- checked that there are no pending conflicting locks but before
- it has started waiting.
- In both these cases we need to sleep until these threads will start
- waiting and try to abort them once again.
-
- QQ: What is the optimal value for this sleep?
- */
- struct timespec abstime;
- set_timespec(abstime, 1);
- pthread_cond_timedwait(&m_ctx_wakeup_cond, &lock->m_mutex, &abstime);
+ bool is_deadlock= (find_deadlock() || timed_wait(1) == VICTIM_WAKE_UP);
- if (mysys_var->abort)
- {
- /*
- We have to do MDL_EXIT_COND here and then re-acquire the lock
- as there is a chance that we will destroy MDL_lock object and
- won't be able to call MDL_EXIT_COND after it.
- */
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
+ stop_waiting();
- pthread_mutex_lock(&lock->m_mutex);
- /* Get rid of pending ticket. */
- lock->waiting_exclusive.remove(ticket);
- if (lock->is_empty())
- mdl_locks.remove(lock);
- else
- {
- /*
- There can be some contexts waiting to acquire shared
- lock which now might be able to do it. Wake them up!
- */
- lock->wake_up_waiters();
- pthread_mutex_unlock(&lock->m_mutex);
- }
+ if (is_deadlock || mysys_var->abort)
+ {
+ lock->remove_ticket(&MDL_lock::m_waiting, ticket);
MDL_ticket::destroy(ticket);
+ if (is_deadlock)
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
return TRUE;
}
+ rw_wrlock(&lock->m_rwlock);
}
- lock->waiting_exclusive.remove(ticket);
- lock->granted.push_front(ticket);
+ lock->m_waiting.remove_ticket(ticket);
+ lock->m_granted.add_ticket(ticket);
- if (lock->cached_object)
+ if (ticket->get_type() == MDL_EXCLUSIVE && lock->cached_object)
(*lock->cached_object_release_hook)(lock->cached_object);
lock->cached_object= NULL;
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
-
- ticket->m_state= MDL_ACQUIRED;
- ticket->m_lock= lock;
+ rw_unlock(&lock->m_rwlock);
m_tickets.push_front(ticket);
@@ -1495,28 +1513,6 @@ bool MDL_context::acquire_exclusive_lock_impl(MDL_request *mdl_request)
}
-/**
- Acquire an exclusive lock.
-
- @param mdl_request Request for the lock to be acqured.
-
- @note Assumes that one already owns global intention exclusive lock.
-
- @retval FALSE Success
- @retval TRUE Failure
-*/
-
-bool MDL_context::acquire_exclusive_lock(MDL_request *mdl_request)
-{
- /* Exclusive locks must always be acquired first, all at once. */
- DBUG_ASSERT(! m_tickets.is_empty() &&
- m_tickets.front()->m_lock->key.mdl_namespace() == MDL_key::GLOBAL &&
- ++Ticket_list::Iterator(m_tickets) == m_lt_or_ha_sentinel);
-
- return acquire_exclusive_lock_impl(mdl_request);
-}
-
-
extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2)
{
MDL_request *req1= *(MDL_request**)ptr1;
@@ -1543,37 +1539,37 @@ extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2)
@retval TRUE Failure
*/
-bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests)
+bool MDL_context::acquire_locks(MDL_request_list *mdl_requests)
{
MDL_request_list::Iterator it(*mdl_requests);
- MDL_request **sort_buf;
- uint i;
+ MDL_request **sort_buf, **p_req;
+ uint req_count= mdl_requests->elements();
+
+ if (req_count == 0)
+ return FALSE;
/*
- Exclusive locks must always be acquired first, all at once.
+ To reduce deadlocks, the server acquires all exclusive
+ locks at once. For shared locks, try_acquire_lock() is
+ used instead.
*/
- DBUG_ASSERT(! m_tickets.is_empty() &&
- m_tickets.front()->m_lock->key.mdl_namespace() == MDL_key::GLOBAL &&
- ++Ticket_list::Iterator(m_tickets) == m_lt_or_ha_sentinel);
-
- if (mdl_requests->is_empty())
- return FALSE;
+ DBUG_ASSERT(m_tickets.is_empty() || m_tickets.front() == m_trans_sentinel);
/* Sort requests according to MDL_key. */
- if (! (sort_buf= (MDL_request **)my_malloc(mdl_requests->elements() *
- sizeof(MDL_request *),
+ if (! (sort_buf= (MDL_request **)my_malloc(req_count *
+ sizeof(MDL_request*),
MYF(MY_WME))))
return TRUE;
- for (i= 0; i < mdl_requests->elements(); i++)
- sort_buf[i]= it++;
+ for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++)
+ *p_req= it++;
- my_qsort(sort_buf, mdl_requests->elements(), sizeof(MDL_request*),
+ my_qsort(sort_buf, req_count, sizeof(MDL_request*),
mdl_request_ptr_cmp);
- for (i= 0; i < mdl_requests->elements(); i++)
+ for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++)
{
- if (acquire_exclusive_lock_impl(sort_buf[i]))
+ if (acquire_lock_impl(*p_req))
goto err;
}
my_free(sort_buf, MYF(0));
@@ -1581,11 +1577,12 @@ bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests)
err:
/* Release locks we have managed to acquire so far. */
- for (i= 0; i < mdl_requests->elements() && sort_buf[i]->ticket; i++)
+ for (req_count= p_req - sort_buf, p_req= sort_buf;
+ p_req < sort_buf + req_count; p_req++)
{
- release_lock(sort_buf[i]->ticket);
+ release_lock((*p_req)->ticket);
/* Reset lock request back to its initial state. */
- sort_buf[i]->ticket= NULL;
+ (*p_req)->ticket= NULL;
}
my_free(sort_buf, MYF(0));
return TRUE;
@@ -1612,251 +1609,198 @@ err:
*/
bool
-MDL_ticket::upgrade_shared_lock_to_exclusive()
+MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket)
{
- const char *old_msg;
- st_my_thread_var *mysys_var= my_thread_var;
- THD *thd= m_ctx->get_thd();
- MDL_ticket *pending_ticket;
+ MDL_request mdl_xlock_request;
+ MDL_ticket *mdl_svp= mdl_savepoint();
+ bool is_new_ticket;
DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive");
- DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive");
+ DEBUG_SYNC(get_thd(), "mdl_upgrade_shared_lock_to_exclusive");
- safe_mutex_assert_not_owner(&LOCK_open);
-
- /* Allow this function to be called twice for the same lock request. */
- if (m_type == MDL_EXCLUSIVE)
+ /*
+ Do nothing if already upgraded. Used when we FLUSH TABLE under
+ LOCK TABLES and a table is listed twice in LOCK TABLES list.
+ */
+ if (mdl_ticket->m_type == MDL_EXCLUSIVE)
DBUG_RETURN(FALSE);
- /* Only allow upgrades from MDL_SHARED_UPGRADABLE */
- DBUG_ASSERT(m_type == MDL_SHARED_UPGRADABLE);
+ /* Only allow upgrades from MDL_SHARED_NO_WRITE/NO_READ_WRITE */
+ DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_NO_WRITE ||
+ mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE);
- /*
- Since we should have already acquired an intention exclusive
- global lock this call is only enforcing asserts.
- */
- DBUG_ASSERT(m_ctx->is_global_lock_owner(MDL_INTENTION_EXCLUSIVE));
+ mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE);
- /*
- Create an auxiliary ticket to represent a pending exclusive
- lock and add it to the 'waiting' queue for the duration
- of upgrade. During upgrade we abort waits of connections
- that own conflicting locks. A pending request is used
- to signal such connections that upon waking up they
- must back off, rather than fall into sleep again.
- */
- if (! (pending_ticket= MDL_ticket::create(m_ctx, MDL_EXCLUSIVE)))
+ if (acquire_lock_impl(&mdl_xlock_request))
DBUG_RETURN(TRUE);
- pthread_mutex_lock(&m_lock->m_mutex);
+ is_new_ticket= ! has_lock(mdl_svp, mdl_xlock_request.ticket);
- m_lock->waiting_exclusive.push_front(pending_ticket);
+ /* Merge the acquired and the original lock. @todo: move to a method. */
+ rw_wrlock(&mdl_ticket->m_lock->m_rwlock);
+ if (is_new_ticket)
+ mdl_ticket->m_lock->m_granted.remove_ticket(mdl_xlock_request.ticket);
+ /*
+ Set the new type of lock in the ticket. To update state of
+ MDL_lock object correctly we need to temporarily exclude
+ ticket from the granted queue and then include it back.
+ */
+ mdl_ticket->m_lock->m_granted.remove_ticket(mdl_ticket);
+ mdl_ticket->m_type= MDL_EXCLUSIVE;
+ mdl_ticket->m_lock->m_granted.add_ticket(mdl_ticket);
- old_msg= MDL_ENTER_COND(thd, mysys_var, &m_ctx->m_ctx_wakeup_cond,
- &m_lock->m_mutex);
+ rw_unlock(&mdl_ticket->m_lock->m_rwlock);
- while (1)
+ if (is_new_ticket)
{
- if (m_lock->can_grant_lock(m_ctx, MDL_EXCLUSIVE, TRUE))
- break;
+ m_tickets.remove(mdl_xlock_request.ticket);
+ MDL_ticket::destroy(mdl_xlock_request.ticket);
+ }
- MDL_ticket *conflicting_ticket;
- MDL_lock::Ticket_iterator it(m_lock->granted);
+ DBUG_RETURN(FALSE);
+}
- /*
- If m_ctx->lt_or_ha_sentinel(), and this sentinel is for HANDLER,
- we can deadlock. However, HANDLER is not allowed under
- LOCK TABLES, and apart from LOCK TABLES there are only
- two cases of lock upgrade: ALTER TABLE and CREATE/DROP
- TRIGGER (*). This leaves us with the following scenario
- for deadlock:
-
- connection 1 connection 2
- handler t1 open; handler t2 open;
- alter table t2 ... alter table t1 ...
-
- This scenario is quite remote, since ALTER
- (and CREATE/DROP TRIGGER) performs mysql_ha_flush() in
- the beginning, and thus closes open HANDLERS against which
- there is a pending lock upgrade. Still, two ALTER statements
- can interleave and not notice each other's pending lock
- (e.g. if both upgrade their locks at the same time).
- This, however, is quite unlikely, so we do nothing to
- address it.
-
- (*) There is no requirement to upgrade lock in
- CREATE/DROP TRIGGER, it's used there just for convenience.
-
- A temporary work-around to avoid deadlocks/livelocks in
- a situation when in one connection ALTER TABLE tries to
- upgrade its metadata lock and in another connection
- the active transaction already got this lock in some
- of its earlier statements.
- In such case this transaction always succeeds with getting
- a metadata lock on the table -- it already has one.
- But later on it may block on the table level lock, since ALTER
- got TL_WRITE_ALLOW_READ, and subsequently get aborted
- by notify_shared_lock().
- An abort will lead to a back off, and a second attempt to
- get an MDL lock (successful), and a table lock (-> livelock).
-
- The call below breaks this loop by forcing transactions to call
- tdc_wait_for_old_versions() (even if the transaction doesn't need
- any new metadata locks), which in turn will check if someone
- is waiting on the owned MDL lock, and produce ER_LOCK_DEADLOCK.
-
- TODO: Long-term such deadlocks/livelock will be resolved within
- MDL subsystem and thus this call will become unnecessary.
- */
- mysql_abort_transactions_with_shared_lock(&m_lock->key);
- while ((conflicting_ticket= it++))
- {
- if (conflicting_ticket->m_ctx != m_ctx)
- notify_shared_lock(thd, conflicting_ticket);
- }
+bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
+ Deadlock_detection_context *deadlock_ctx)
+{
+ MDL_ticket *ticket;
+ bool result= FALSE;
- /* There is a shared or exclusive lock on the object. */
- DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive_wait");
+ rw_rdlock(&m_rwlock);
- /*
- Another thread might have obtained a shared MDL lock on some table
- but has not yet opened it and/or tried to obtain data lock on it.
- Also invocation of acquire_exclusive_lock() method and consequently
- first call to notify_shared_lock() might have happened right after
- thread holding shared metadata lock in wait_for_locks() method
- checked that there are no pending conflicting locks but before
- it has started waiting.
- In both these cases we need to sleep until these threads will start
- waiting and try to abort them once again.
- */
- struct timespec abstime;
- set_timespec(abstime, 1);
- pthread_cond_timedwait(&m_ctx->m_ctx_wakeup_cond, &m_lock->m_mutex,
- &abstime);
+ Ticket_iterator granted_it(m_granted);
+ Ticket_iterator waiting_it(m_waiting);
- if (mysys_var->abort)
+ while ((ticket= granted_it++))
+ {
+ if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
+ ticket->get_ctx() != waiting_ticket->get_ctx() &&
+ ticket->get_ctx() == deadlock_ctx->start)
{
- m_lock->waiting_exclusive.remove(pending_ticket);
- /*
- If there are no other pending requests for exclusive locks
- we need to wake up threads waiting for a chance to acquire
- shared lock.
- */
- m_lock->wake_up_waiters();
- MDL_EXIT_COND(thd, mysys_var, &m_lock->m_mutex, old_msg);
- MDL_ticket::destroy(pending_ticket);
- DBUG_RETURN(TRUE);
+ result= TRUE;
+ goto end;
}
}
- /* Set the new type of lock in the ticket. */
- m_type= MDL_EXCLUSIVE;
-
- /* Remove and destroy the auxiliary pending ticket. */
- m_lock->waiting_exclusive.remove(pending_ticket);
-
- if (m_lock->cached_object)
- (*m_lock->cached_object_release_hook)(m_lock->cached_object);
- m_lock->cached_object= 0;
+ while ((ticket= waiting_it++))
+ {
+ if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
+ ticket->get_ctx() != waiting_ticket->get_ctx() &&
+ ticket->get_ctx() == deadlock_ctx->start)
+ {
+ result= TRUE;
+ goto end;
+ }
+ }
- MDL_EXIT_COND(thd, mysys_var, &m_lock->m_mutex, old_msg);
+ granted_it.rewind();
+ while ((ticket= granted_it++))
+ {
+ if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
+ ticket->get_ctx() != waiting_ticket->get_ctx() &&
+ ticket->get_ctx()->find_deadlock(deadlock_ctx))
+ {
+ result= TRUE;
+ goto end;
+ }
+ }
- MDL_ticket::destroy(pending_ticket);
+ waiting_it.rewind();
+ while ((ticket= waiting_it++))
+ {
+ if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
+ ticket->get_ctx() != waiting_ticket->get_ctx() &&
+ ticket->get_ctx()->find_deadlock(deadlock_ctx))
+ {
+ result= TRUE;
+ goto end;
+ }
+ }
- DBUG_RETURN(FALSE);
+end:
+ rw_unlock(&m_rwlock);
+ return result;
}
-/**
- Try to acquire an exclusive lock on the object if there are
- no conflicting locks.
-
- Similar to the previous function, but returns
- immediately without any side effect if encounters a lock
- conflict. Otherwise takes the lock.
-
- This function is used in CREATE TABLE ... LIKE to acquire a lock
- on the table to be created. In this statement we don't want to
- block and wait for the lock if the table already exists.
-
- @param mdl_request [in] The lock request
- @param conflict [out] Indicates that conflicting lock exists
-
- @retval TRUE Failure: some error occurred (probably OOM).
- @retval FALSE Success: the lock might have not been acquired,
- check request.ticket to find out.
-
- FIXME: Compared to lock_table_name_if_not_cached()
- it gives slightly more false negatives.
-*/
-
-bool
-MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request)
+bool MDL_context::find_deadlock(Deadlock_detection_context *deadlock_ctx)
{
- DBUG_ASSERT(mdl_request->type == MDL_EXCLUSIVE);
- DBUG_ASSERT(is_global_lock_owner(MDL_INTENTION_EXCLUSIVE));
+ bool result= FALSE;
- return try_acquire_lock_impl(mdl_request);
-}
+ rw_rdlock(&m_waiting_for_lock);
+ if (m_waiting_for)
+ {
+ /*
+ QQ: should we rather be checking for NO_WAKE_UP ?
-/**
- Acquire the global shared metadata lock.
-
- Holding this lock will block all requests for exclusive locks
- and shared locks which can be potentially upgraded to exclusive.
-
- @retval FALSE Success -- the lock was granted.
- @retval TRUE Failure -- our thread was killed.
-*/
-
-bool MDL_context::acquire_global_shared_lock()
-{
- MDL_request mdl_request;
-
- DBUG_ASSERT(! is_global_lock_owner(MDL_SHARED));
-
- mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED);
+ We want to do check signal only when m_waiting_for is set
+ to avoid reading left-overs from previous kills.
+ */
+ if (peek_signal() != VICTIM_WAKE_UP)
+ {
- if (acquire_lock_impl(&mdl_request))
- return TRUE;
+ if (++deadlock_ctx->current_search_depth >
+ deadlock_ctx->MAX_SEARCH_DEPTH)
+ result= TRUE;
+ else
+ result= m_waiting_for->m_lock->find_deadlock(m_waiting_for,
+ deadlock_ctx);
+ --deadlock_ctx->current_search_depth;
+ }
+ }
- move_ticket_after_lt_or_ha_sentinel(mdl_request.ticket);
+ if (result)
+ {
+ if (! deadlock_ctx->victim)
+ deadlock_ctx->victim= this;
+ else if (deadlock_ctx->victim->m_deadlock_weight >= m_deadlock_weight)
+ {
+ rw_unlock(&deadlock_ctx->victim->m_waiting_for_lock);
+ deadlock_ctx->victim= this;
+ }
+ else
+ rw_unlock(&m_waiting_for_lock);
+ }
+ else
+ rw_unlock(&m_waiting_for_lock);
- return FALSE;
+ return result;
}
-/**
- Implement a simple deadlock detection heuristic: check if there
- are any pending exclusive locks which conflict with shared locks
- held by this thread. In that case waiting can be circular,
- i.e. lead to a deadlock.
-
- @return TRUE If there are any pending conflicting locks.
- FALSE Otherwise.
-*/
-
-bool MDL_context::can_wait_lead_to_deadlock() const
+bool MDL_context::find_deadlock()
{
- Ticket_iterator ticket_it(m_tickets);
- MDL_ticket *ticket;
+ Deadlock_detection_context deadlock_ctx(this);
- while ((ticket= ticket_it++))
+ while (1)
{
- /*
- In MySQL we never call this method while holding exclusive or
- upgradeable shared metadata locks.
- Otherwise we would also have to check for the presence of pending
- requests for conflicting types of global lock.
- In addition MDL_ticket::has_pending_conflicting_lock()
- won't work properly for exclusive type of lock.
- */
- DBUG_ASSERT(! ticket->is_upgradable_or_exclusive());
+ if (! find_deadlock(&deadlock_ctx))
+ {
+ /* No deadlocks are found! */
+ break;
+ }
- if (ticket->has_pending_conflicting_lock())
+ if (deadlock_ctx.victim != this)
+ {
+ deadlock_ctx.victim->awake(VICTIM_WAKE_UP);
+ rw_unlock(&deadlock_ctx.victim->m_waiting_for_lock);
+ /*
+ After adding new arc to waiting graph we found that it participates
+ in some loop (i.e. there is a deadlock). We decided to destroy this
+ loop by removing some arc other than newly added. Since this doesn't
+ guarantee that all loops created by addition of this arc are
+ destroyed we have to repeat search.
+ */
+ continue;
+ }
+ else
+ {
+ DBUG_ASSERT(&deadlock_ctx.victim->m_waiting_for_lock == &m_waiting_for_lock);
+ rw_unlock(&deadlock_ctx.victim->m_waiting_for_lock);
return TRUE;
+ }
}
return FALSE;
}
@@ -1876,16 +1820,15 @@ bool MDL_context::can_wait_lead_to_deadlock() const
*/
bool
-MDL_context::wait_for_locks(MDL_request_list *mdl_requests)
+MDL_context::wait_for_lock(MDL_request *mdl_request)
{
MDL_lock *lock;
- MDL_request *mdl_request;
- MDL_request_list::Iterator it(*mdl_requests);
- const char *old_msg;
st_my_thread_var *mysys_var= my_thread_var;
safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(mdl_request->ticket == NULL);
+
while (!mysys_var->abort)
{
/*
@@ -1900,87 +1843,45 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests)
*/
mysql_ha_flush(m_thd);
- /*
- In cases when we wait while still holding some metadata
- locks deadlocks are possible.
- To avoid them we use the following simple empiric - don't
- wait for new lock request to be satisfied if for one of the
- locks which are already held by this connection there is
- a conflicting request (i.e. this connection should not wait
- if someone waits for it).
- This empiric should work well (e.g. give low number of false
- negatives) in situations when conflicts are rare (in our
- case this is true since DDL statements should be rare).
- */
- if (can_wait_lead_to_deadlock())
+ MDL_key *key= &mdl_request->key;
+
+ /* The below call implicitly locks MDL_lock::m_rwlock on success. */
+ if (! (lock= mdl_locks.find(key)))
+ return FALSE;
+
+ if (lock->can_grant_lock(mdl_request->type, this))
{
- my_error(ER_LOCK_DEADLOCK, MYF(0));
- return TRUE;
+ rw_unlock(&lock->m_rwlock);
+ return FALSE;
}
- it.rewind();
- while ((mdl_request= it++))
+ MDL_ticket *pending_ticket;
+ if (! (pending_ticket= MDL_ticket::create(this, mdl_request->type)))
{
- MDL_key *key= &mdl_request->key;
- DBUG_ASSERT(mdl_request->ticket == NULL);
-
- /*
- To avoid starvation we don't wait if we have a conflict against
- request for MDL_EXCLUSIVE lock.
- */
- if (mdl_request->is_shared() ||
- mdl_request->type == MDL_INTENTION_EXCLUSIVE)
- {
- /* The below call also implicitly locks MDL_lock::m_mutex. */
- if (! (lock= mdl_locks.find(key)))
- continue;
-
- if (lock->can_grant_lock(this, mdl_request->type, FALSE))
- {
- pthread_mutex_unlock(&lock->m_mutex);
- continue;
- }
-
- MDL_ticket *pending_ticket;
- if (! (pending_ticket= MDL_ticket::create(this, mdl_request->type)))
- {
- pthread_mutex_unlock(&lock->m_mutex);
- return TRUE;
- }
- if (mdl_request->is_shared())
- lock->waiting_shared.push_front(pending_ticket);
- else
- lock->waiting_exclusive.push_front(pending_ticket);
-
- old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_ctx_wakeup_cond,
- &lock->m_mutex);
-
- pthread_cond_wait(&m_ctx_wakeup_cond, &lock->m_mutex);
-
- /*
- We have to do MDL_EXIT_COND here and then re-acquire the lock
- as there is a chance that we will destroy MDL_lock object and
- won't be able to call MDL_EXIT_COND after it.
- */
- MDL_EXIT_COND(m_thd, mysys_var, &lock->m_mutex, old_msg);
-
- pthread_mutex_lock(&lock->m_mutex);
- if (mdl_request->is_shared())
- lock->waiting_shared.remove(pending_ticket);
- else
- lock->waiting_exclusive.remove(pending_ticket);
- if (lock->is_empty())
- mdl_locks.remove(lock);
- else
- pthread_mutex_unlock(&lock->m_mutex);
- MDL_ticket::destroy(pending_ticket);
- break;
- }
+ rw_unlock(&lock->m_rwlock);
+ return TRUE;
}
- if (!mdl_request)
+
+ pending_ticket->m_lock= lock;
+
+ lock->m_waiting.add_ticket(pending_ticket);
+
+ wait_reset();
+ rw_unlock(&lock->m_rwlock);
+
+ set_deadlock_weight(MDL_DEADLOCK_WEIGHT_DML);
+ will_wait_for(pending_ticket);
+
+ bool is_deadlock= (find_deadlock() || wait() == VICTIM_WAKE_UP);
+
+ stop_waiting();
+
+ lock->remove_ticket(&MDL_lock::m_waiting, pending_ticket);
+ MDL_ticket::destroy(pending_ticket);
+ if (is_deadlock)
{
- /* There are no conflicts for any locks! */
- break;
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return TRUE;
}
}
return mysys_var->abort;
@@ -2000,23 +1901,13 @@ void MDL_context::release_lock(MDL_ticket *ticket)
DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(),
lock->key.name()));
- DBUG_ASSERT(this == ticket->m_ctx);
+ DBUG_ASSERT(this == ticket->get_ctx());
safe_mutex_assert_not_owner(&LOCK_open);
- if (ticket == m_lt_or_ha_sentinel)
- m_lt_or_ha_sentinel= ++Ticket_list::Iterator(m_tickets, ticket);
-
- pthread_mutex_lock(&lock->m_mutex);
+ if (ticket == m_trans_sentinel)
+ m_trans_sentinel= ++Ticket_list::Iterator(m_tickets, ticket);
- lock->granted.remove(ticket);
-
- if (lock->is_empty())
- mdl_locks.remove(lock);
- else
- {
- lock->wake_up_waiters();
- pthread_mutex_unlock(&lock->m_mutex);
- }
+ lock->remove_ticket(&MDL_lock::m_granted, ticket);
m_tickets.remove(ticket);
MDL_ticket::destroy(ticket);
@@ -2086,7 +1977,7 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name)
while ((ticket= it_ticket++))
{
- DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED);
+ DBUG_ASSERT(ticket->m_lock);
/*
We rarely have more than one ticket in this loop,
let's not bother saving on pthread_cond_broadcast().
@@ -2099,89 +1990,43 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name)
/**
Downgrade an exclusive lock to shared metadata lock.
-*/
-
-void MDL_ticket::downgrade_exclusive_lock()
-{
- safe_mutex_assert_not_owner(&LOCK_open);
-
- if (is_shared())
- return;
-
- pthread_mutex_lock(&m_lock->m_mutex);
- m_type= MDL_SHARED_UPGRADABLE;
-
- if (! m_lock->waiting_shared.is_empty())
- {
- MDL_lock::Ticket_iterator it(m_lock->waiting_shared);
- MDL_ticket *ticket;
- while ((ticket= it++))
- ticket->get_ctx()->awake();
- }
-
- pthread_mutex_unlock(&m_lock->m_mutex);
-}
-
-/**
- Release the global shared metadata lock.
+ @param type Type of lock to which exclusive lock should be downgraded.
*/
-void MDL_context::release_global_shared_lock()
+void MDL_ticket::downgrade_exclusive_lock(enum_mdl_type type)
{
- MDL_request mdl_request;
- MDL_ticket *ticket;
- bool not_used;
-
- mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED);
-
safe_mutex_assert_not_owner(&LOCK_open);
/*
- TODO/QQ/FIXME: In theory we always should be able to find
- ticket here. But in practice this is not
- always TRUE.
+ Do nothing if already downgraded. Used when we FLUSH TABLE under
+ LOCK TABLES and a table is listed twice in LOCK TABLES list.
*/
+ if (m_type != MDL_EXCLUSIVE)
+ return;
- if ((ticket= find_ticket(&mdl_request, &not_used)))
- release_lock(ticket);
-}
-
-
-/**
- Auxiliary function which allows to check if we have exclusive lock
- on the object.
-
- @param mdl_namespace Id of object namespace
- @param db Name of the database
- @param name Name of the object
-
- @return TRUE if current context contains exclusive lock for the object,
- FALSE otherwise.
-*/
-
-bool
-MDL_context::is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db, const char *name)
-{
- MDL_request mdl_request;
- bool is_lt_or_ha_unused;
- mdl_request.init(mdl_namespace, db, name, MDL_EXCLUSIVE);
- MDL_ticket *ticket= find_ticket(&mdl_request, &is_lt_or_ha_unused);
-
- DBUG_ASSERT(ticket == NULL || ticket->m_state == MDL_ACQUIRED);
-
- return ticket;
+ rw_wrlock(&m_lock->m_rwlock);
+ /*
+ To update state of MDL_lock object correctly we need to temporarily
+ exclude ticket from the granted queue and then include it back.
+ */
+ m_lock->m_granted.remove_ticket(this);
+ m_type= type;
+ m_lock->m_granted.add_ticket(this);
+ m_lock->wake_up_waiters();
+ rw_unlock(&m_lock->m_rwlock);
}
/**
Auxiliary function which allows to check if we have some kind of lock on
- a object.
+ a object. Returns TRUE if we have a lock of a given or stronger type.
@param mdl_namespace Id of object namespace
@param db Name of the database
@param name Name of the object
+ @param mdl_type Lock type. Pass in the weakest type to find
+ out if there is at least some lock.
@return TRUE if current context contains satisfied lock for the object,
FALSE otherwise.
@@ -2189,25 +2034,22 @@ MDL_context::is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
bool
MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db, const char *name)
+ const char *db, const char *name,
+ enum_mdl_type mdl_type)
{
- MDL_key key(mdl_namespace, db, name);
- MDL_ticket *ticket;
- MDL_context::Ticket_iterator it(m_tickets);
+ MDL_request mdl_request;
+ bool is_transactional_unused;
+ mdl_request.init(mdl_namespace, db, name, mdl_type);
+ MDL_ticket *ticket= find_ticket(&mdl_request, &is_transactional_unused);
- while ((ticket= it++))
- {
- if (ticket->m_lock->key.is_equal(&key))
- break;
- }
+ DBUG_ASSERT(ticket == NULL || ticket->m_lock);
return ticket;
}
/**
- Check if we have any pending exclusive locks which conflict with
- existing shared lock.
+ Check if we have any pending locks which conflict with existing shared lock.
@pre The ticket must match an acquired lock.
@@ -2216,10 +2058,7 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
bool MDL_ticket::has_pending_conflicting_lock() const
{
- safe_mutex_assert_not_owner(&LOCK_open);
- DBUG_ASSERT(is_shared());
-
- return m_lock->has_pending_exclusive_lock();
+ return m_lock->has_pending_conflicting_lock(m_type);
}
@@ -2304,7 +2143,7 @@ void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint)
/* If savepoint is NULL, it is from the start of the transaction. */
release_locks_stored_before(mdl_savepoint ?
- mdl_savepoint : m_lt_or_ha_sentinel);
+ mdl_savepoint : m_trans_sentinel);
DBUG_VOID_RETURN;
}
@@ -2322,7 +2161,7 @@ void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint)
void MDL_context::release_transactional_locks()
{
DBUG_ENTER("MDL_context::release_transactional_locks");
- release_locks_stored_before(m_lt_or_ha_sentinel);
+ release_locks_stored_before(m_trans_sentinel);
DBUG_VOID_RETURN;
}
@@ -2331,20 +2170,21 @@ void MDL_context::release_transactional_locks()
Does this savepoint have this lock?
@retval TRUE The ticket is older than the savepoint and
- is not LT or HA ticket. Thus it belongs to
- the savepoint.
+ is not LT, HA or GLR ticket. Thus it belongs
+ to the savepoint.
@retval FALSE The ticket is newer than the savepoint
- or is an LT or HA ticket.
+ or is an LT, HA or GLR ticket.
*/
bool MDL_context::has_lock(MDL_ticket *mdl_savepoint,
MDL_ticket *mdl_ticket)
{
MDL_ticket *ticket;
+ /* Start from the beginning, most likely mdl_ticket's been just acquired. */
MDL_context::Ticket_iterator it(m_tickets);
bool found_savepoint= FALSE;
- while ((ticket= it++) && ticket != m_lt_or_ha_sentinel)
+ while ((ticket= it++) && ticket != m_trans_sentinel)
{
/*
First met the savepoint. The ticket must be
@@ -2359,28 +2199,28 @@ bool MDL_context::has_lock(MDL_ticket *mdl_savepoint,
if (ticket == mdl_ticket)
return found_savepoint;
}
- /* Reached m_lt_or_ha_sentinel. The ticket must be an LT or HA ticket. */
+ /* Reached m_trans_sentinel. The ticket must be LT, HA or GRL ticket. */
return FALSE;
}
/**
Rearrange the ticket to reside in the part of the list that's
- beyond m_lt_or_ha_sentinel. This effectively changes the ticket
+ beyond m_trans_sentinel. This effectively changes the ticket
life cycle, from automatic to manual: i.e. the ticket is no
longer released by MDL_context::release_transactional_locks() or
MDL_context::rollback_to_savepoint(), it must be released manually.
*/
-void MDL_context::move_ticket_after_lt_or_ha_sentinel(MDL_ticket *mdl_ticket)
+void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket)
{
m_tickets.remove(mdl_ticket);
- if (m_lt_or_ha_sentinel == NULL)
+ if (m_trans_sentinel == NULL)
{
- m_lt_or_ha_sentinel= mdl_ticket;
+ m_trans_sentinel= mdl_ticket;
/* sic: linear from the number of transactional tickets acquired so-far! */
m_tickets.push_back(mdl_ticket);
}
else
- m_tickets.insert_after(m_lt_or_ha_sentinel, mdl_ticket);
+ m_tickets.insert_after(m_trans_sentinel, mdl_ticket);
}
diff --git a/sql/mdl.h b/sql/mdl.h
index 3ae7cdc743d..848c446496f 100644
--- a/sql/mdl.h
+++ b/sql/mdl.h
@@ -26,28 +26,121 @@ class THD;
class MDL_context;
class MDL_lock;
class MDL_ticket;
+class Deadlock_detection_context;
/**
Type of metadata lock request.
- - High-priority shared locks differ from ordinary shared locks by
- that they ignore pending requests for exclusive locks.
- - Upgradable shared locks can be later upgraded to exclusive
- (because of that their acquisition involves implicit
- acquisition of global intention-exclusive lock).
-
@sa Comments for MDL_object_lock::can_grant_lock() and
- MDL_global_lock::can_grant_lock() for details.
+ MDL_global_lock::can_grant_lock() for details.
*/
-enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO,
- MDL_SHARED_UPGRADABLE, MDL_INTENTION_EXCLUSIVE,
- MDL_EXCLUSIVE};
-
-
-/** States which a metadata lock ticket can have. */
+enum enum_mdl_type {
+ /*
+ An intention exclusive metadata lock. Used only for global locks.
+ Owner of this type of lock can acquire upgradable exclusive locks on
+ individual objects.
+ Compatible with other IX locks, but is incompatible with global S lock.
+ */
+ MDL_INTENTION_EXCLUSIVE= 0,
+ /*
+ A shared metadata lock.
+ To be used in cases when we are interested in object metadata only
+ and there is no intention to access object data (e.g. for stored
+ routines or during preparing prepared statements).
+ We also mis-use this type of lock for open HANDLERs, since lock
+ acquired by this statement has to be compatible with lock acquired
+ by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by
+ acquiring S lock at HANDLER ... OPEN time and upgrading it to SR
+ lock for HANDLER ... READ as it doesn't solve problem with need
+ to abort DML statements which wait on table level lock while having
+ open HANDLER in the same connection).
+ To avoid deadlock which may occur when SNRW lock is being upgraded to
+ X lock for table on which there is an active S lock which is owned by
+ thread which waits in its turn for table-level lock owned by thread
+ performing upgrade we have to use thr_abort_locks_for_thread()
+ facility in such situation.
+ This problem does not arise for locks on stored routines as we don't
+ use SNRW locks for them. It also does not arise when S locks are used
+ during PREPARE calls as table-level locks are not acquired in this
+ case.
+ */
+ MDL_SHARED,
+ /*
+ A high priority shared metadata lock.
+ Used for cases when there is no intention to access object data (i.e.
+ data in the table).
+ "High priority" means that, unlike other shared locks, it is granted
+ ignoring pending requests for exclusive locks. Intended for use in
+ cases when we only need to access metadata and not data, e.g. when
+ filling an INFORMATION_SCHEMA table.
+ Since SH lock is compatible with SNRW lock, the connection that
+ holds SH lock lock should not try to acquire any kind of table-level
+ or row-level lock, as this can lead to a deadlock. Moreover, after
+ acquiring SH lock, the connection should not wait for any other
+ resource, as it might cause starvation for X locks and a potential
+ deadlock during upgrade of SNW or SNRW to X lock (e.g. if the
+ upgrading connection holds the resource that is being waited for).
+ */
+ MDL_SHARED_HIGH_PRIO,
+ /*
+ A shared metadata lock for cases when there is an intention to read data
+ from table.
+ A connection holding this kind of lock can read table metadata and read
+ table data (after acquiring appropriate table and row-level locks).
+ This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and
+ similar table-level locks on table if one holds SR MDL lock on it.
+ To be used for tables in SELECTs, subqueries, and LOCK TABLE ... READ
+ statements.
+ */
+ MDL_SHARED_READ,
+ /*
+ A shared metadata lock for cases when there is an intention to modify
+ (and not just read) data in the table.
+ A connection holding SW lock can read table metadata and modify or read
+ table data (after acquiring appropriate table and row-level locks).
+ To be used for tables to be modified by INSERT, UPDATE, DELETE
+ statements, but not LOCK TABLE ... WRITE or DDL). Also taken by
+ SELECT ... FOR UPDATE.
+ */
+ MDL_SHARED_WRITE,
+ /*
+ An upgradable shared metadata lock which blocks all attempts to update
+ table data, allowing reads.
+ A connection holding this kind of lock can read table metadata and read
+ table data.
+ Can be upgraded to X metadata lock.
+ Note, that since this type of lock is not compatible with SNRW or SW
+ lock types, acquiring appropriate engine-level locks for reading
+ (TL_READ* for MyISAM, shared row locks in InnoDB) should be
+ contention-free.
+ To be used for the first phase of ALTER TABLE, when copying data between
+ tables, to allow concurrent SELECTs from the table, but not UPDATEs.
+ */
+ MDL_SHARED_NO_WRITE,
+ /*
+ An upgradable shared metadata lock which allows other connections
+ to access table metadata, but not data.
+ It blocks all attempts to read or update table data, while allowing
+ INFORMATION_SCHEMA and SHOW queries.
+ A connection holding this kind of lock can read table metadata modify and
+ read table data.
+ Can be upgraded to X metadata lock.
+ To be used for LOCK TABLES WRITE statement.
+ Not compatible with any other lock type except S and SH.
+ */
+ MDL_SHARED_NO_READ_WRITE,
+ /*
+ An exclusive metadata lock.
+ A connection holding this lock can modify both table's metadata and data.
+ No other type of metadata lock can be granted while this lock is held.
+ To be used for CREATE/DROP/RENAME TABLE statements and for execution of
+ certain phases of other DDL statements.
+ */
+ MDL_EXCLUSIVE,
+ /* This should be the last !!! */
+ MDL_TYPE_END};
-enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED };
/** Maximal length of key for metadata locking subsystem. */
#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1)
@@ -77,11 +170,11 @@ public:
it's necessary to have a separate namespace for them since
MDL_key is also used outside of the MDL subsystem.
*/
- enum enum_mdl_namespace { TABLE=0,
+ enum enum_mdl_namespace { GLOBAL=0,
+ TABLE,
FUNCTION,
PROCEDURE,
- TRIGGER,
- GLOBAL };
+ TRIGGER };
const uchar *ptr() const { return (uchar*) m_ptr; }
uint length() const { return m_length; }
@@ -217,7 +310,7 @@ public:
DBUG_ASSERT(ticket == NULL);
type= type_arg;
}
- bool is_shared() const { return type < MDL_INTENTION_EXCLUSIVE; }
+ uint get_deadlock_weight() const;
static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace,
const char *db, const char *name,
@@ -297,21 +390,25 @@ public:
void set_cached_object(void *cached_object,
mdl_cached_object_release_hook release_hook);
MDL_context *get_ctx() const { return m_ctx; }
- bool is_shared() const { return m_type < MDL_INTENTION_EXCLUSIVE; }
bool is_upgradable_or_exclusive() const
{
- return m_type == MDL_SHARED_UPGRADABLE || m_type == MDL_EXCLUSIVE;
+ return m_type == MDL_SHARED_NO_WRITE ||
+ m_type == MDL_SHARED_NO_READ_WRITE ||
+ m_type == MDL_EXCLUSIVE;
}
- bool upgrade_shared_lock_to_exclusive();
- void downgrade_exclusive_lock();
+ enum_mdl_type get_type() const { return m_type; }
+ void downgrade_exclusive_lock(enum_mdl_type type);
+
+ bool has_stronger_or_equal_type(enum_mdl_type type) const;
+
+ bool is_incompatible_when_granted(enum_mdl_type type) const;
+ bool is_incompatible_when_waiting(enum_mdl_type type) const;
+
private:
friend class MDL_context;
- friend class MDL_global_lock;
- friend class MDL_object_lock;
MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg)
: m_type(type_arg),
- m_state(MDL_PENDING),
m_ctx(ctx_arg),
m_lock(NULL)
{}
@@ -321,9 +418,6 @@ private:
private:
/** Type of metadata lock. Externally accessible. */
enum enum_mdl_type m_type;
- /** State of the metadata lock ticket. Context private. */
- enum enum_mdl_state m_state;
-
/**
Context of the owner of the metadata lock ticket. Externally accessible.
*/
@@ -331,6 +425,7 @@ private:
/** Pointer to the lock object for this lock ticket. Context private. */
MDL_lock *m_lock;
+
private:
MDL_ticket(const MDL_ticket &); /* not implemented */
MDL_ticket &operator=(const MDL_ticket &); /* not implemented */
@@ -359,26 +454,29 @@ public:
typedef Ticket_list::Iterator Ticket_iterator;
+ enum mdl_signal_type { NO_WAKE_UP = 0,
+ NORMAL_WAKE_UP,
+ VICTIM_WAKE_UP,
+ TIMEOUT_WAKE_UP };
+
MDL_context();
void destroy();
- bool try_acquire_shared_lock(MDL_request *mdl_request);
- bool acquire_exclusive_lock(MDL_request *mdl_request);
- bool acquire_exclusive_locks(MDL_request_list *requests);
- bool try_acquire_exclusive_lock(MDL_request *mdl_request);
+ bool try_acquire_lock(MDL_request *mdl_request);
+ bool acquire_lock(MDL_request *mdl_request);
+ bool acquire_locks(MDL_request_list *requests);
+ bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket);
+
bool clone_ticket(MDL_request *mdl_request);
- bool wait_for_locks(MDL_request_list *requests);
+ bool wait_for_lock(MDL_request *mdl_request);
void release_all_locks_for_name(MDL_ticket *ticket);
void release_lock(MDL_ticket *ticket);
- bool is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db,
- const char *name);
bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db, const char *name);
-
+ const char *db, const char *name,
+ enum_mdl_type mdl_type);
bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket);
@@ -391,88 +489,208 @@ public:
{
/*
NULL savepoint represents the start of the transaction.
- Checking for m_lt_or_ha_sentinel also makes sure we never
+ Checking for m_trans_sentinel also makes sure we never
return a pointer to HANDLER ticket as a savepoint.
*/
- return m_tickets.front() == m_lt_or_ha_sentinel ? NULL : m_tickets.front();
+ return m_tickets.front() == m_trans_sentinel ? NULL : m_tickets.front();
}
- void set_lt_or_ha_sentinel()
+ void set_trans_sentinel()
{
- m_lt_or_ha_sentinel= mdl_savepoint();
+ m_trans_sentinel= mdl_savepoint();
}
- MDL_ticket *lt_or_ha_sentinel() const { return m_lt_or_ha_sentinel; }
+ MDL_ticket *trans_sentinel() const { return m_trans_sentinel; }
- void clear_lt_or_ha_sentinel()
+ void reset_trans_sentinel(MDL_ticket *sentinel_arg)
{
- m_lt_or_ha_sentinel= NULL;
+ m_trans_sentinel= sentinel_arg;
}
- void move_ticket_after_lt_or_ha_sentinel(MDL_ticket *mdl_ticket);
+ void move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket);
void release_transactional_locks();
void rollback_to_savepoint(MDL_ticket *mdl_savepoint);
- bool can_wait_lead_to_deadlock() const;
-
inline THD *get_thd() const { return m_thd; }
+ bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
+
/**
Wake up context which is waiting for a change of MDL_lock state.
*/
- void awake()
+ void awake(mdl_signal_type signal)
{
- pthread_cond_signal(&m_ctx_wakeup_cond);
+ pthread_mutex_lock(&m_signal_lock);
+ m_signal= signal;
+ pthread_cond_signal(&m_signal_cond);
+ pthread_mutex_unlock(&m_signal_lock);
}
- bool try_acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
- bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
-
- bool acquire_global_shared_lock();
- void release_global_shared_lock();
+ void init(THD *thd_arg) { m_thd= thd_arg; }
- /**
- Check if this context owns global lock of particular type.
- */
- bool is_global_lock_owner(enum_mdl_type type_arg)
+ void set_needs_thr_lock_abort(bool needs_thr_lock_abort)
{
- MDL_request mdl_request;
- bool not_used;
- mdl_request.init(MDL_key::GLOBAL, "", "", type_arg);
- return find_ticket(&mdl_request, &not_used);
+ /*
+ @note In theory, this member should be modified under protection
+ of some lock since it can be accessed from different threads.
+ In practice, this is not necessary as code which reads this
+ value and so might miss the fact that value was changed will
+ always re-try reading it after small timeout and therefore
+ will see the new value eventually.
+ */
+ m_needs_thr_lock_abort= TRUE;
+ }
+ bool get_needs_thr_lock_abort() const
+ {
+ return m_needs_thr_lock_abort;
}
- void init(THD *thd_arg) { m_thd= thd_arg; }
+ bool find_deadlock(Deadlock_detection_context *deadlock_ctx);
private:
+ /**
+ All MDL tickets acquired by this connection.
+
+ The order of tickets in m_tickets list.
+ ---------------------------------------
+ The entire set of locks acquired by a connection
+ can be separated in two subsets: transactional and
+ non-transactional locks.
+
+ Transactional locks are locks with automatic scope. They
+ are accumulated in the course of a transaction, and
+ released only on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT.
+ They must not be (and never are) released manually,
+ i.e. with release_lock() call.
+
+ Non-transactional locks are taken for locks that span
+ multiple transactions or savepoints.
+ These are: HANDLER SQL locks (HANDLER SQL is
+ transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc
+ under LOCK TABLES, and the locked tables stay locked), and
+ SET GLOBAL READ_ONLY=1 global shared lock.
+
+ Transactional locks are always prepended to the beginning
+ of the list. In other words, they are stored in reverse
+ temporal order. Thus, when we rollback to a savepoint,
+ we start popping and releasing tickets from the front
+ until we reach the last ticket acquired after the
+ savepoint.
+
+ Non-transactional locks are always stored after
+ transactional ones, and among each other can be
+ split into three sets:
+
+ [LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks]
+
+ The following is known about these sets:
+
+ * we can never have both HANDLER and LOCK TABLES locks
+ together -- HANDLER statements are prohibited under LOCK
+ TABLES, entering LOCK TABLES implicitly closes all open
+ HANDLERs.
+ * GLOBAL READ LOCK locks are always stored after LOCK TABLES
+ locks and after HANDLER locks. This is because one can't say
+ SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK
+ if one has locked tables. One can, however, LOCK TABLES
+ after having entered the read only mode. Note, that
+ subsequent LOCK TABLES statement will unlock the previous
+ set of tables, but not the GRL!
+ There are no HANDLER locks after GRL locks because
+ SET GLOBAL read_only performs a FLUSH TABLES WITH
+ READ LOCK internally, and FLUSH TABLES, in turn, implicitly
+ closes all open HANDLERs.
+ However, one can open a few HANDLERs after entering the
+ read only mode.
+ */
Ticket_list m_tickets;
/**
- This member has two uses:
- 1) When entering LOCK TABLES mode, remember the last taken
- metadata lock. COMMIT/ROLLBACK must preserve these metadata
- locks.
- 2) When we have an open HANDLER tables, store the position
- in the list beyond which we keep locks for HANDLER tables.
- COMMIT/ROLLBACK must, again, preserve HANDLER metadata locks.
+ Separates transactional and non-transactional locks
+ in m_tickets list, @sa m_tickets.
*/
- MDL_ticket *m_lt_or_ha_sentinel;
+ MDL_ticket *m_trans_sentinel;
THD *m_thd;
/**
+ TRUE - if for this context we will break protocol and try to
+ acquire table-level locks while having only S lock on
+ some table.
+ To avoid deadlocks which might occur during concurrent
+ upgrade of SNRW lock on such object to X lock we have to
+ abort waits for table-level locks for such connections.
+ FALSE - Otherwise.
+ */
+ bool m_needs_thr_lock_abort;
+
+ /**
+ Read-write lock protecting m_waiting_for member.
+
+ TODO/FIXME: Replace with RW-lock which will prefer readers
+ on all platforms and not only on Linux.
+ */
+ rw_lock_t m_waiting_for_lock;
+ MDL_ticket *m_waiting_for;
+ uint m_deadlock_weight;
+ /**
Condvar which is used for waiting until this context's pending
request can be satisfied or this thread has to perform actions
- to resolve potential deadlock (we subscribe for such notification
- by adding ticket corresponding to the request to an appropriate
- queue of waiters).
+ to resolve a potential deadlock (we subscribe to such
+ notification by adding a ticket corresponding to the request
+ to an appropriate queue of waiters).
*/
- pthread_cond_t m_ctx_wakeup_cond;
+ pthread_mutex_t m_signal_lock;
+ pthread_cond_t m_signal_cond;
+ mdl_signal_type m_signal;
+
private:
MDL_ticket *find_ticket(MDL_request *mdl_req,
- bool *is_lt_or_ha);
+ bool *is_transactional);
void release_locks_stored_before(MDL_ticket *sentinel);
-
- bool try_acquire_lock_impl(MDL_request *mdl_request);
bool acquire_lock_impl(MDL_request *mdl_request);
- bool acquire_exclusive_lock_impl(MDL_request *mdl_request);
- friend bool MDL_ticket::upgrade_shared_lock_to_exclusive();
+ bool find_deadlock();
+
+ void will_wait_for(MDL_ticket *pending_ticket)
+ {
+ rw_wrlock(&m_waiting_for_lock);
+ m_waiting_for= pending_ticket;
+ rw_unlock(&m_waiting_for_lock);
+ }
+
+ void set_deadlock_weight(uint weight)
+ {
+ /*
+ m_deadlock_weight should not be modified while m_waiting_for is
+ non-NULL as in this case this context might participate in deadlock
+ and so m_deadlock_weight can be accessed from other threads.
+ */
+ DBUG_ASSERT(m_waiting_for == NULL);
+ m_deadlock_weight= weight;
+ }
+
+ void stop_waiting()
+ {
+ rw_wrlock(&m_waiting_for_lock);
+ m_waiting_for= NULL;
+ rw_unlock(&m_waiting_for_lock);
+ }
+
+ void wait_reset()
+ {
+ pthread_mutex_lock(&m_signal_lock);
+ m_signal= NO_WAKE_UP;
+ pthread_mutex_unlock(&m_signal_lock);
+ }
+
+ mdl_signal_type wait();
+ mdl_signal_type timed_wait(ulong timeout);
+
+ mdl_signal_type peek_signal()
+ {
+ mdl_signal_type result;
+ pthread_mutex_lock(&m_signal_lock);
+ result= m_signal;
+ pthread_mutex_unlock(&m_signal_lock);
+ return result;
+ }
+
private:
MDL_context(const MDL_context &rhs); /* not implemented */
MDL_context &operator=(MDL_context &rhs); /* not implemented */
@@ -487,9 +705,9 @@ void mdl_destroy();
Functions in the server's kernel used by metadata locking subsystem.
*/
-extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use);
+extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+ bool needs_thr_lock_abort);
extern void mysql_ha_flush(THD *thd);
-extern void mysql_abort_transactions_with_shared_lock(const MDL_key *mdl_key);
extern "C" const char *set_thd_proc_info(THD *thd, const char *info,
const char *calling_function,
const char *calling_file,
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 9f153b5aa0e..2316eae4cdb 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -1241,8 +1241,9 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
char *cache_key, uint cache_key_length,
MEM_ROOT *mem_root, uint flags);
TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
-TABLE *find_write_locked_table(TABLE *list, const char *db,
- const char *table_name);
+TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
+ const char *table_name,
+ bool no_error);
thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table);
void execute_init_command(THD *thd, sys_var_str *init_command_var,
rw_lock_t *var_mutex);
@@ -2078,6 +2079,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100
/** Fail instead of waiting when conficting metadata lock is discovered. */
#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200
+/** Open tables using MDL_SHARED lock instead of one specified in parser. */
+#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0400
/** Please refer to the internals manual. */
#define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\
@@ -2098,14 +2101,6 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table);
MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b);
TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle,
TABLE_LIST *haystack);
-bool lock_global_read_lock(THD *thd);
-void unlock_global_read_lock(THD *thd);
-bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
- bool is_not_commit);
-void start_waiting_global_read_lock(THD *thd);
-bool make_global_read_lock_block_commit(THD *thd);
-bool set_protect_against_global_read_lock(void);
-void unset_protect_against_global_read_lock(void);
void broadcast_refresh(void);
/* Lock based on name */
diff --git a/sql/set_var.cc b/sql/set_var.cc
index ce7cfcc81a8..0f36a164247 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -4262,7 +4262,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
DBUG_RETURN(true);
}
- if (thd->global_read_lock)
+ if (thd->global_read_lock.is_acquired())
{
/*
This connection already holds the global read lock.
@@ -4285,8 +4285,8 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
[3] prevents transactions from being committed.
*/
- if (lock_global_read_lock(thd))
- DBUG_RETURN(true);
+ if (thd->global_read_lock.lock_global_read_lock(thd))
+ DBUG_RETURN(TRUE);
/*
This call will be blocked by any connection holding a READ or WRITE lock.
@@ -4300,7 +4300,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
if ((result= close_cached_tables(thd, NULL, FALSE, TRUE)))
goto end_with_read_lock;
- if ((result= make_global_read_lock_block_commit(thd)))
+ if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd)))
goto end_with_read_lock;
/* Change the opt_readonly system variable, safe because the lock is held */
@@ -4308,7 +4308,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
end_with_read_lock:
/* Release the lock */
- unlock_global_read_lock(thd);
+ thd->global_read_lock.unlock_global_read_lock(thd);
DBUG_RETURN(result);
}
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index cd7486dec72..83c928eb88f 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3966,7 +3966,8 @@ sp_head::add_used_tables_to_table_list(THD *thd,
table->belong_to_view= belong_to_view;
table->trg_event_map= stab->trg_event_map;
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
- MDL_SHARED);
+ table->lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
/* Everyting else should be zeroed */
@@ -4009,7 +4010,8 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
table->select_lex= lex->current_select;
table->cacheable_table= 1;
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
- MDL_SHARED);
+ table->lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
lex->add_to_query_tables(table);
return table;
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 2f891375163..8dced67dd44 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -131,8 +131,6 @@ static bool tdc_wait_for_old_versions(THD *thd,
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
-TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
- const char *table_name);
uint cached_open_tables(void)
{
@@ -477,8 +475,10 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
To be able perform any operation on table we should own
some kind of metadata lock on it.
*/
- DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table_list->db,
- table_list->table_name));
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ table_list->db,
+ table_list->table_name,
+ MDL_SHARED));
/* Read table definition from cache */
if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
@@ -1003,7 +1003,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
{
/* A check that the table was locked for write is done by the caller. */
TABLE *table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db,
- table_list->table_name);
+ table_list->table_name, TRUE);
/* May return NULL if this table has already been closed via an alias. */
if (! table)
@@ -1084,7 +1084,7 @@ err_with_reopen:
than picking only those tables that were flushed.
*/
for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
- tab->mdl_ticket->downgrade_exclusive_lock();
+ tab->mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
}
DBUG_RETURN(result);
}
@@ -2145,7 +2145,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table,
table->s->table_name.str, (ulong) table->s,
table->db_stat, table->s->version));
- if (table->mdl_ticket->upgrade_shared_lock_to_exclusive())
+ if (thd->mdl_context.upgrade_shared_lock_to_exclusive(table->mdl_ticket))
DBUG_RETURN(TRUE);
pthread_mutex_lock(&LOCK_open);
@@ -2336,6 +2336,7 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list,
{
if (table_list->lock_strategy)
{
+ MDL_request_list mdl_requests;
MDL_request *global_request;
/*
In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table
@@ -2350,67 +2351,51 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list,
mdl_request->set_type(MDL_EXCLUSIVE);
DBUG_ASSERT(! thd->mdl_context.has_locks() ||
thd->handler_tables_hash.records ||
- thd->global_read_lock);
+ thd->global_read_lock.is_acquired());
if (!(global_request= ot_ctx->get_global_mdl_request(thd)))
return 1;
- if (! global_request->ticket)
- {
- ot_ctx->add_request(global_request);
- if (thd->mdl_context.acquire_global_intention_exclusive_lock(
- global_request))
- return 1;
- }
+ mdl_requests.push_front(mdl_request);
+ mdl_requests.push_front(global_request);
- ot_ctx->add_request(mdl_request);
- if (thd->mdl_context.acquire_exclusive_lock(mdl_request))
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
return 1;
}
else
{
- /*
- There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we
- want to be sure that caller doesn't pass us both flags simultaneously.
- */
- DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) ||
- !(flags & MYSQL_LOCK_IGNORE_FLUSH));
-
- if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL &&
- table_list->lock_type >= TL_WRITE_ALLOW_WRITE)
- mdl_request->set_type(MDL_SHARED_UPGRADABLE);
- if (flags & MYSQL_LOCK_IGNORE_FLUSH)
- mdl_request->set_type(MDL_SHARED_HIGH_PRIO);
-
- if (mdl_request->type == MDL_SHARED_UPGRADABLE)
+ if (flags & MYSQL_OPEN_FORCE_SHARED_MDL)
{
- MDL_request *global_request;
+ /*
+ While executing PREPARE for prepared statement we override
+ type-of-operation aware type of shared metadata lock which
+ was set in the parser with simple shared metadata lock.
+ This is necessary to allow concurrent execution of PREPARE
+ and LOCK TABLES WRITE statement which locks one of the tables
+ used in the statement being prepared.
+ */
+ DBUG_ASSERT(!(flags & (MYSQL_OPEN_TAKE_UPGRADABLE_MDL |
+ MYSQL_LOCK_IGNORE_FLUSH)));
- if (!(global_request= ot_ctx->get_global_mdl_request(thd)))
- return 1;
- if (! global_request->ticket)
- {
- ot_ctx->add_request(global_request);
- if (thd->mdl_context.try_acquire_global_intention_exclusive_lock(
- global_request))
- return 1;
- if (! global_request->ticket)
- goto failure;
- }
+ mdl_request->set_type(MDL_SHARED);
+ }
+ else if (flags & MYSQL_LOCK_IGNORE_FLUSH)
+ {
+ DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL));
+ mdl_request->set_type(MDL_SHARED_HIGH_PRIO);
}
ot_ctx->add_request(mdl_request);
- if (thd->mdl_context.try_acquire_shared_lock(mdl_request))
+ if (thd->mdl_context.try_acquire_lock(mdl_request))
return 1;
-failure:
if (mdl_request->ticket == NULL)
{
if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT)
my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), table_list->db, table_list->table_name);
else
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
+ ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK);
return 1;
}
}
@@ -2563,6 +2548,17 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{
int distance= ((int) table->reginfo.lock_type -
(int) table_list->lock_type);
+
+ /*
+ If we are performing DDL operation we also should ensure
+ that we will find TABLE instance with upgradable metadata
+ lock,
+ */
+ if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) &&
+ table_list->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ ! table->mdl_ticket->is_upgradable_or_exclusive())
+ distance= -1;
+
/*
Find a table that either has the exact lock type requested,
or has the best suitable lock. In case there is no locked
@@ -2596,6 +2592,13 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
if (best_table)
{
+ if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) &&
+ table_list->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ ! best_table->mdl_ticket->is_upgradable_or_exclusive())
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), alias);
+ DBUG_RETURN(TRUE);
+ }
table= best_table;
table->query_id= thd->query_id;
DBUG_PRINT("info",("Using locked table"));
@@ -2610,8 +2613,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
TABLES breaks metadata locking protocol (potentially can lead
to deadlocks) it should be disallowed.
*/
- if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, table_list->db,
- table_list->table_name))
+ if (thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+ table_list->db,
+ table_list->table_name,
+ MDL_SHARED))
{
char path[FN_REFLEN + 1];
enum legacy_db_type not_used;
@@ -2688,7 +2693,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{
/* Someone did a refresh while thread was opening tables */
pthread_mutex_unlock(&LOCK_open);
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
+ (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC);
DBUG_RETURN(TRUE);
}
@@ -2828,7 +2833,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
*/
release_table_share(share);
pthread_mutex_unlock(&LOCK_open);
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
+ (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC);
DBUG_RETURN(TRUE);
}
/* Force close at once after usage */
@@ -2895,11 +2900,11 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
/*
In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that
table exists now we should downgrade our exclusive metadata
- lock on this table to shared metadata lock.
+ lock on this table to SW metadata lock.
*/
if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL &&
!(flags & MYSQL_OPEN_HAS_MDL_LOCK))
- mdl_ticket->downgrade_exclusive_lock();
+ mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_WRITE);
table->mdl_ticket= mdl_ticket;
@@ -2982,33 +2987,43 @@ TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
/**
- Find write locked instance of table in the list of open tables,
- emit error if no such instance found.
+ Find instance of TABLE with upgradable or exclusive metadata
+ lock from the list of open tables, emit error if no such table
+ found.
- @param thd List of TABLE objects to be searched
+ @param list List of TABLE objects to be searched
@param db Database name.
@param table_name Name of table.
+ @param no_error Don't emit error if no suitable TABLE
+ instance were found.
- @return Pointer to write-locked TABLE instance, 0 - otherwise.
+ @return Pointer to TABLE instance with MDL_SHARED_NO_WRITE,
+ MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata
+ lock, NULL otherwise.
*/
-TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name)
+TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
+ const char *table_name,
+ bool no_error)
{
TABLE *tab= find_locked_table(list, db, table_name);
if (!tab)
{
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
- return 0;
+ if (!no_error)
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+ return NULL;
}
else
{
- while (tab->reginfo.lock_type < TL_WRITE_LOW_PRIORITY &&
+ while (tab->mdl_ticket != NULL &&
+ !tab->mdl_ticket->is_upgradable_or_exclusive() &&
(tab= find_locked_table(tab->next, db, table_name)))
continue;
if (!tab)
{
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+ if (!no_error)
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
return 0;
}
}
@@ -3016,34 +3031,6 @@ TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_na
}
-/**
- Find instance of TABLE with MDL_SHARED_UPGRADABLE or
- MDL_EXCLUSIVE lock from the list of open tables.
-
- @param list List of TABLE objects to be searched
- @param db Database name.
- @param table_name Name of table.
-
- @return Pointer to MDL_SHARED_UPGRADABLE or MDL_EXCLUSIVE
- TABLE instance, NULL otherwise.
-*/
-
-TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
- const char *table_name)
-{
- TABLE *tab= find_locked_table(list, db, table_name);
-
- while (tab != NULL)
- {
- if (tab->mdl_ticket != NULL &&
- tab->mdl_ticket->is_upgradable_or_exclusive())
- return tab;
- tab= find_locked_table(tab->next, db, table_name);
- }
- return NULL;
-}
-
-
/***********************************************************************
class Locked_tables_list implementation. Declared in sql_class.h
************************************************************************/
@@ -3741,9 +3728,9 @@ end_with_lock_open:
Open_table_context::Open_table_context(THD *thd)
:m_action(OT_NO_ACTION),
m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()),
- m_has_locks((thd->in_multi_stmt_transaction() ||
- thd->mdl_context.lt_or_ha_sentinel()) &&
- thd->mdl_context.has_locks()),
+ m_has_locks((thd->in_multi_stmt_transaction() &&
+ thd->mdl_context.has_locks()) ||
+ thd->mdl_context.trans_sentinel()),
m_global_mdl_request(NULL)
{}
@@ -3798,7 +3785,7 @@ request_backoff_action(enum_open_table_action action_arg)
Waiting will be done after releasing metadata locks acquired
by this statement.
*/
- if (m_has_locks && action_arg != OT_WAIT)
+ if (m_has_locks && action_arg != OT_WAIT_MDL_LOCK)
{
my_error(ER_LOCK_DEADLOCK, MYF(0));
return TRUE;
@@ -3838,33 +3825,28 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request,
/* Execute the action. */
switch (m_action)
{
- case OT_WAIT:
- result= (thd->mdl_context.wait_for_locks(&m_mdl_requests) ||
- tdc_wait_for_old_versions(thd, &m_mdl_requests));
+ case OT_WAIT_MDL_LOCK:
+ result= thd->mdl_context.wait_for_lock(mdl_request);
+ break;
+ case OT_WAIT_TDC:
+ result= tdc_wait_for_old_versions(thd, &m_mdl_requests);
DBUG_ASSERT(thd->mysys_var->current_mutex == NULL);
break;
case OT_DISCOVER:
{
MDL_request mdl_global_request;
MDL_request mdl_xlock_request(mdl_request);
+ MDL_request_list mdl_requests;
mdl_global_request.init(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
+ mdl_requests.push_front(&mdl_xlock_request);
+ mdl_requests.push_front(&mdl_global_request);
- if ((result= thd->mdl_context.acquire_global_intention_exclusive_lock(
- &mdl_global_request)))
- break;
-
- if ((result=
- thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
- {
- /*
- We rely on close_thread_tables() to release global lock eventually.
- */
+ if ((result= thd->mdl_context.acquire_locks(&mdl_requests)))
break;
- }
DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
@@ -3885,23 +3867,17 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request,
{
MDL_request mdl_global_request;
MDL_request mdl_xlock_request(mdl_request);
+ MDL_request_list mdl_requests;
mdl_global_request.init(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
- if ((result= thd->mdl_context.acquire_global_intention_exclusive_lock(
- &mdl_global_request)))
- break;
+ mdl_requests.push_front(&mdl_xlock_request);
+ mdl_requests.push_front(&mdl_global_request);
- if ((result=
- thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
- {
- /*
- We rely on close_thread_tables() to release global lock eventually.
- */
+ if ((result= thd->mdl_context.acquire_locks(&mdl_requests)))
break;
- }
DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
@@ -4015,13 +3991,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
*/
DBUG_ASSERT(rt->mdl_request.type == MDL_SHARED);
- if (thd->mdl_context.try_acquire_shared_lock(&rt->mdl_request))
+ if (thd->mdl_context.try_acquire_lock(&rt->mdl_request))
DBUG_RETURN(TRUE);
if (rt->mdl_request.ticket == NULL)
{
/* A lock conflict. Someone's trying to modify SP metadata. */
- ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
+ ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK);
DBUG_RETURN(TRUE);
}
DEBUG_SYNC(thd, "after_shared_lock_pname");
@@ -4345,6 +4321,66 @@ end:
/**
+ Acquire upgradable (SNW, SNRW) metadata locks on tables to be opened
+ for LOCK TABLES or a DDL statement.
+
+ @param thd Thread context.
+ @param tables_start Start of list of tables on which upgradable locks
+ should be acquired.
+ @param tables_end End of list of tables.
+ @param ot_ctx Context of open_tables() operation.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. connection was killed)
+*/
+
+static bool
+open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
+ TABLE_LIST *tables_end,
+ Open_table_context *ot_ctx)
+{
+ MDL_request_list mdl_requests;
+ TABLE_LIST *table;
+
+ for (table= tables_start; table && table != tables_end;
+ table= table->next_global)
+ {
+ if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ table->mdl_request.set_type(table->lock_type > TL_WRITE_ALLOW_READ ?
+ MDL_SHARED_NO_READ_WRITE :
+ MDL_SHARED_NO_WRITE);
+ mdl_requests.push_front(&table->mdl_request);
+ }
+ }
+
+ if (! mdl_requests.is_empty())
+ {
+ MDL_request *global_request= ot_ctx->get_global_mdl_request(thd);
+
+ if (global_request == NULL)
+ return TRUE;
+ mdl_requests.push_front(global_request);
+ }
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
+ return TRUE;
+
+ for (table= tables_start; table && table != tables_end;
+ table= table->next_global)
+ {
+ if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
+ {
+ table->mdl_request.ticket= NULL;
+ table->mdl_request.set_type(MDL_SHARED_WRITE);
+ }
+ }
+
+ return FALSE;
+}
+
+
+/**
Open all tables in list
@param[in] thd Thread context.
@@ -4419,6 +4455,29 @@ restart:
thd_proc_info(thd, "Opening tables");
/*
+ If we are executing LOCK TABLES statement or a DDL statement
+ (in non-LOCK TABLES mode) we might have to acquire upgradable
+ semi-exclusive metadata locks (SNW or SNRW) on some of the
+ tables to be opened.
+ So we acquire all such locks at once here as doing this in one
+ by one fashion may lead to deadlocks or starvation. Later when
+ we will be opening corresponding table pre-acquired metadata
+ lock will be reused (thanks to the fact that in recursive case
+ metadata locks are acquired without waiting).
+ */
+ if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) &&
+ ! thd->locked_tables_mode)
+ {
+ if (open_tables_acquire_upgradable_mdl(thd, *start,
+ thd->lex->first_not_own_table(),
+ &ot_ctx))
+ {
+ error= TRUE;
+ goto err;
+ }
+ }
+
+ /*
Perform steps of prelocking algorithm until there are unprocessed
elements in prelocking list/set.
*/
@@ -4922,8 +4981,8 @@ retry:
while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) &&
ot_ctx.can_recover_from_failed_open())
{
- /* We never have an open HANDLER or LOCK TABLES here. */
- DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL);
+ /* We never have an open HANDLER, LOCK TABLES or GRL here. */
+ DBUG_ASSERT(thd->mdl_context.trans_sentinel() == NULL);
/*
Even though we have failed to open table we still need to
call release_transactional_locks() to release metadata locks which
@@ -4974,8 +5033,8 @@ retry:
close_thread_tables(thd);
table_list->table= NULL;
table_list->mdl_request.ticket= NULL;
- /* We never have an open HANDLER or LOCK TABLES here. */
- DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL);
+ /* We never have an open HANDLER, LOCK TABLES or GRL here. */
+ DBUG_ASSERT(thd->mdl_context.trans_sentinel() == NULL);
thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp());
goto retry;
}
@@ -8459,15 +8518,19 @@ void flush_tables()
@param thd Current thread context
@param in_use The thread to wake up
+ @param needs_thr_lock_abort Indicates that to wake up thread
+ this call needs to abort its waiting
+ on table-level lock.
@retval TRUE if the thread was woken up
- @retval FALSE otherwise (e.g. it was not waiting for a table-level lock).
+ @retval FALSE otherwise.
@note It is one of two places where border between MDL and the
rest of the server is broken.
*/
-bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use)
+bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+ bool needs_thr_lock_abort)
{
bool signalled= FALSE;
if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
@@ -8481,19 +8544,23 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use)
signalled= TRUE;
}
pthread_mutex_lock(&LOCK_open);
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
+
+ if (needs_thr_lock_abort)
{
- /*
- Check for TABLE::needs_reopen() is needed since in some places we call
- handler::close() for table instance (and set TABLE::db_stat to 0)
- and do not remove such instances from the THD::open_tables
- for some time, during which other thread can see those instances
- (e.g. see partitioning code).
- */
- if (!thd_table->needs_reopen())
- signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /*
+ Check for TABLE::needs_reopen() is needed since in some places we call
+ handler::close() for table instance (and set TABLE::db_stat to 0)
+ and do not remove such instances from the THD::open_tables
+ for some time, during which other thread can see those instances
+ (e.g. see partitioning code).
+ */
+ if (!thd_table->needs_reopen())
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
}
/*
Wake up threads waiting in tdc_wait_for_old_versions().
@@ -8512,28 +8579,6 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use)
/**
- Force transactions holding shared metadata lock on the table to call
- MDL_context::can_wait_lead_to_deadlock() even if they don't need any
- new metadata locks so they can detect potential deadlocks between
- metadata locking subsystem and table-level locks.
-
- @param mdl_key MDL key for the table on which we are upgrading
- metadata lock.
-*/
-
-void mysql_abort_transactions_with_shared_lock(const MDL_key *mdl_key)
-{
- if (mdl_key->mdl_namespace() == MDL_key::TABLE)
- {
- pthread_mutex_lock(&LOCK_open);
- tdc_remove_table(NULL, TDC_RT_REMOVE_UNUSED, mdl_key->db_name(),
- mdl_key->name());
- pthread_mutex_unlock(&LOCK_open);
- }
-}
-
-
-/**
Remove all or some (depending on parameter) instances of TABLE and
TABLE_SHARE from the table definition cache.
@@ -8574,8 +8619,8 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
safe_mutex_assert_owner(&LOCK_open);
DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
- thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE,
- db, table_name));
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
+ MDL_EXCLUSIVE));
key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
@@ -8639,24 +8684,6 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests)
*/
mysql_ha_flush(thd);
- /*
- Check if there is someone waiting for one of metadata locks
- held by this connection and return an error if that's the
- case, since this situation may lead to a deadlock.
- This can happen, when, for example, this connection is
- waiting for an old version of some table to go away and
- another connection is trying to upgrade its shared
- metadata lock to exclusive, and thus is waiting
- for this to release its lock. We must check for
- the condition on each iteration of the loop to remove
- any window for a race.
- */
- if (thd->mdl_context.can_wait_lead_to_deadlock())
- {
- my_error(ER_LOCK_DEADLOCK, MYF(0));
- return TRUE;
- }
-
pthread_mutex_lock(&LOCK_open);
MDL_request_list::Iterator it(*mdl_requests);
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 62de06d382c..8affe88d01d 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -452,8 +452,6 @@ THD::THD()
examined_row_count(0),
warning_info(&main_warning_info),
stmt_da(&main_da),
- global_read_lock_protection(0),
- global_read_lock(0),
is_fatal_error(0),
transaction_rollback_request(0),
is_fatal_sub_stmt_error(0),
@@ -1000,22 +998,22 @@ void THD::cleanup(void)
locked_tables_list.unlock_locked_tables(this);
mysql_ha_cleanup(this);
+ DBUG_ASSERT(open_tables == NULL);
/*
If the thread was in the middle of an ongoing transaction (rolled
back a few lines above) or under LOCK TABLES (unlocked the tables
and left the mode a few lines above), there will be outstanding
metadata locks. Release them.
*/
- DBUG_ASSERT(open_tables == NULL);
- /* All HANDLERs must have been closed by now. */
- DBUG_ASSERT(mdl_context.lt_or_ha_sentinel() == NULL ||
- global_read_lock);
- /*
- Due to the above assert, this is guaranteed to release *all* in
- this session.
- */
mdl_context.release_transactional_locks();
+ /* Release the global read lock, if acquired. */
+ if (global_read_lock.is_acquired())
+ global_read_lock.unlock_global_read_lock(this);
+
+ /* All metadata locks must have been released by now. */
+ DBUG_ASSERT(!mdl_context.has_locks());
+
#if defined(ENABLED_DEBUG_SYNC)
/* End the Debug Sync Facility. See debug_sync.cc. */
debug_sync_end_thread(this);
@@ -1031,8 +1029,6 @@ void THD::cleanup(void)
sp_cache_clear(&sp_proc_cache);
sp_cache_clear(&sp_func_cache);
- if (global_read_lock)
- unlock_global_read_lock(this);
if (ull)
{
pthread_mutex_lock(&LOCK_user_locks);
diff --git a/sql/sql_class.h b/sql/sql_class.h
index dce06f7e0c5..2ca67133fe5 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1287,7 +1287,8 @@ public:
enum enum_open_table_action
{
OT_NO_ACTION= 0,
- OT_WAIT,
+ OT_WAIT_MDL_LOCK,
+ OT_WAIT_TDC,
OT_DISCOVER,
OT_REPAIR
};
@@ -1427,6 +1428,45 @@ struct Ha_data
Ha_data() :ha_ptr(NULL) {}
};
+/**
+ An instance of the global read lock in a connection.
+ Implemented in lock.cc.
+*/
+
+class Global_read_lock
+{
+public:
+ enum enum_grl_state
+ {
+ GRL_NONE,
+ GRL_ACQUIRED,
+ GRL_ACQUIRED_AND_BLOCKS_COMMIT
+ };
+
+ Global_read_lock()
+ :m_protection_count(0), m_state(GRL_NONE), m_mdl_global_shared_lock(NULL)
+ {}
+
+ bool lock_global_read_lock(THD *thd);
+ void unlock_global_read_lock(THD *thd);
+ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
+ bool is_not_commit);
+ void start_waiting_global_read_lock(THD *thd);
+ bool make_global_read_lock_block_commit(THD *thd);
+ bool is_acquired() const { return m_state != GRL_NONE; }
+ bool has_protection() const { return m_protection_count > 0; }
+ MDL_ticket *global_shared_lock() const { return m_mdl_global_shared_lock; }
+private:
+ uint m_protection_count; // GRL protection count
+ /**
+ In order to acquire the global read lock, the connection must
+ acquire a global shared metadata lock, to prohibit all DDL.
+ */
+ enum_grl_state m_state;
+ MDL_ticket *m_mdl_global_shared_lock;
+};
+
+
extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
/**
@@ -1685,6 +1725,7 @@ public:
init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
}
} transaction;
+ Global_read_lock global_read_lock;
Field *dup_field;
#ifndef __WIN__
sigset_t signals;
@@ -1905,8 +1946,7 @@ public:
ulong rand_saved_seed1, rand_saved_seed2;
pthread_t real_id; /* For debugging */
my_thread_id thread_id;
- uint global_read_lock_protection;// GRL protection count
- uint tmp_table, global_read_lock;
+ uint tmp_table;
uint server_status,open_options;
enum enum_thread_type system_thread;
uint select_number; //number of select (used for EXPLAIN)
@@ -2585,15 +2625,16 @@ public:
void enter_locked_tables_mode(enum_locked_tables_mode mode_arg)
{
DBUG_ASSERT(locked_tables_mode == LTM_NONE);
- DBUG_ASSERT(! mdl_context.lt_or_ha_sentinel() ||
- mdl_context.is_global_lock_owner(MDL_SHARED));
- mdl_context.set_lt_or_ha_sentinel();
+ DBUG_ASSERT(handler_tables_hash.records == 0);
+
+ mdl_context.set_trans_sentinel();
locked_tables_mode= mode_arg;
}
void leave_locked_tables_mode()
{
locked_tables_mode= LTM_NONE;
- mdl_context.clear_lt_or_ha_sentinel();
+ /* Make sure we don't release the global read lock when leaving LTM. */
+ mdl_context.reset_trans_sentinel(global_read_lock.global_shared_lock());
}
private:
/** The current internal error handler for this thread, or NULL. */
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
index 1b61a96d7ff..e7a36f664dd 100644
--- a/sql/sql_db.cc
+++ b/sql/sql_db.cc
@@ -637,7 +637,7 @@ int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info,
has the global read lock and refuses the operation with
ER_CANT_UPDATE_WITH_READLOCK if applicable.
*/
- if (wait_if_global_read_lock(thd, 0, 1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
{
error= -1;
goto exit2;
@@ -754,7 +754,7 @@ not_silent:
exit:
pthread_mutex_unlock(&LOCK_mysql_create_db);
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
exit2:
DBUG_RETURN(error);
}
@@ -781,7 +781,7 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info)
has the global read lock and refuses the operation with
ER_CANT_UPDATE_WITH_READLOCK if applicable.
*/
- if ((error=wait_if_global_read_lock(thd,0,1)))
+ if ((error= thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)))
goto exit2;
pthread_mutex_lock(&LOCK_mysql_create_db);
@@ -831,7 +831,7 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info)
exit:
pthread_mutex_unlock(&LOCK_mysql_create_db);
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
exit2:
DBUG_RETURN(error);
}
@@ -876,7 +876,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
has the global read lock and refuses the operation with
ER_CANT_UPDATE_WITH_READLOCK if applicable.
*/
- if (wait_if_global_read_lock(thd, 0, 1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
{
error= -1;
goto exit2;
@@ -1027,7 +1027,7 @@ exit:
if (thd->db && !strcmp(thd->db, db) && error == 0)
mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
pthread_mutex_unlock(&LOCK_mysql_create_db);
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
exit2:
DBUG_RETURN(error);
}
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index 228c001f71b..87e59c0ac65 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -1100,7 +1100,6 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
TABLE *table;
bool error= TRUE;
uint path_length;
- MDL_request mdl_global_request, mdl_request;
/*
Is set if we're under LOCK TABLES, and used
to downgrade the exclusive lock after the
@@ -1186,8 +1185,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
if (thd->locked_tables_mode)
{
- if (!(table= find_write_locked_table(thd->open_tables, table_list->db,
- table_list->table_name)))
+ if (!(table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db,
+ table_list->table_name, FALSE)))
DBUG_RETURN(TRUE);
mdl_ticket= table->mdl_ticket;
if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
@@ -1196,6 +1195,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
}
else
{
+ MDL_request mdl_global_request, mdl_request;
+ MDL_request_list mdl_requests;
/*
Even though we could use the previous execution branch
here just as well, we must not try to open the table:
@@ -1211,17 +1212,12 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name,
MDL_EXCLUSIVE);
- if (thd->mdl_context.acquire_global_intention_exclusive_lock(
- &mdl_global_request))
- DBUG_RETURN(TRUE);
- if (thd->mdl_context.acquire_exclusive_lock(&mdl_request))
- {
- /*
- We rely on that close_thread_tables() to release global lock
- in this case.
- */
+ mdl_requests.push_front(&mdl_request);
+ mdl_requests.push_front(&mdl_global_request);
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
DBUG_RETURN(TRUE);
- }
+
has_mdl_lock= TRUE;
pthread_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db,
@@ -1263,7 +1259,7 @@ end:
if (has_mdl_lock)
thd->mdl_context.release_transactional_locks();
if (mdl_ticket)
- mdl_ticket->downgrade_exclusive_lock();
+ mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
}
DBUG_PRINT("exit", ("error: %d", error));
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index ccfe21d1af5..5f800ea8642 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -315,8 +315,11 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
}
thd->open_tables= backup_open_tables;
if (hash_tables->mdl_request.ticket)
+ {
thd->mdl_context.
- move_ticket_after_lt_or_ha_sentinel(hash_tables->mdl_request.ticket);
+ move_ticket_after_trans_sentinel(hash_tables->mdl_request.ticket);
+ thd->mdl_context.set_needs_thr_lock_abort(TRUE);
+ }
/* Assert that the above check prevent opening of views and merge tables. */
DBUG_ASSERT(hash_tables->table->next == NULL);
@@ -376,6 +379,13 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
DBUG_RETURN(TRUE);
}
+ /*
+ Mark MDL_context as no longer breaking protocol if we have
+ closed last HANDLER.
+ */
+ if (! thd->handler_tables_hash.records)
+ thd->mdl_context.set_needs_thr_lock_abort(FALSE);
+
my_ok(thd);
DBUG_PRINT("exit", ("OK"));
DBUG_RETURN(FALSE);
@@ -739,6 +749,13 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables)
hash_tables= next;
}
+ /*
+ Mark MDL_context as no longer breaking protocol if we have
+ closed last HANDLER.
+ */
+ if (! thd->handler_tables_hash.records)
+ thd->mdl_context.set_needs_thr_lock_abort(FALSE);
+
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 397674471c5..0c3ccf0af85 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -1972,7 +1972,7 @@ mysql_execute_command(THD *thd)
*/
if (((sql_command_flags[lex->sql_command] & CF_PROTECT_AGAINST_GRL) != 0) &&
!thd->locked_tables_mode)
- if (wait_if_global_read_lock(thd, FALSE, TRUE))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
goto error;
switch (lex->sql_command) {
@@ -2045,7 +2045,7 @@ mysql_execute_command(THD *thd)
break;
if (!thd->locked_tables_mode && lex->protect_against_global_read_lock &&
- wait_if_global_read_lock(thd, 0, 1))
+ thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
break;
res= execute_sqlcom_select(thd, all_tables);
@@ -2397,8 +2397,7 @@ case SQLCOM_PREPARE:
goto end_with_restore_list;
}
- if (!(res= open_and_lock_tables_derived(thd, lex->query_tables, TRUE,
- MYSQL_OPEN_TAKE_UPGRADABLE_MDL)))
+ if (!(res= open_and_lock_tables_derived(thd, lex->query_tables, TRUE, 0)))
{
/*
Is table which we are changing used somewhere in other parts
@@ -2548,7 +2547,7 @@ end_with_restore_list:
client thread has locked tables
*/
if (thd->locked_tables_mode ||
- thd->active_transaction() || thd->global_read_lock)
+ thd->active_transaction() || thd->global_read_lock.is_acquired())
{
my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
@@ -3078,7 +3077,7 @@ end_with_restore_list:
ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
goto error;
}
- if (wait_if_global_read_lock(thd, 0, 1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
goto error;
res= mysql_truncate(thd, first_table, 0);
break;
@@ -3299,8 +3298,8 @@ end_with_restore_list:
thd->mdl_context.release_transactional_locks();
thd->options&= ~(OPTION_TABLE_LOCK);
}
- if (thd->global_read_lock)
- unlock_global_read_lock(thd);
+ if (thd->global_read_lock.is_acquired())
+ thd->global_read_lock.unlock_global_read_lock(thd);
my_ok(thd);
break;
case SQLCOM_LOCK_TABLES:
@@ -3322,7 +3321,7 @@ end_with_restore_list:
FALSE, UINT_MAX, FALSE))
goto error;
if (lex->protect_against_global_read_lock &&
- wait_if_global_read_lock(thd, 0, 1))
+ thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
goto error;
init_mdl_requests(all_tables);
@@ -4503,13 +4502,13 @@ error:
res= TRUE;
finish:
- if (thd->global_read_lock_protection > 0)
+ if (thd->global_read_lock.has_protection())
{
/*
Release the protection against the global read lock and wake
everyone, who might want to set a global read lock.
*/
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
}
if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
@@ -5971,7 +5970,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->next_name_resolution_table= NULL;
/* Link table in global list (all used tables) */
lex->add_to_query_tables(ptr);
- ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, MDL_SHARED);
+ ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name,
+ (ptr->lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
DBUG_RETURN(ptr);
}
@@ -6207,6 +6208,8 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type)
{
tables->lock_type= lock_type;
tables->updating= for_update;
+ tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
DBUG_VOID_RETURN;
}
@@ -6503,7 +6506,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
DBUG_ASSERT(!thd || thd->locked_tables_mode ||
!thd->mdl_context.has_locks() ||
thd->handler_tables_hash.records ||
- thd->global_read_lock);
+ thd->global_read_lock.is_acquired());
/*
Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too
@@ -6529,16 +6532,16 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
UNLOCK TABLES
*/
tmp_write_to_binlog= 0;
- if (lock_global_read_lock(thd))
+ if (thd->global_read_lock.lock_global_read_lock(thd))
return 1; // Killed
if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
FALSE : TRUE))
result= 1;
- if (make_global_read_lock_block_commit(thd)) // Killed
+ if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed
{
/* Don't leave things in a half-locked state */
- unlock_global_read_lock(thd);
+ thd->global_read_lock.unlock_global_read_lock(thd);
return 1;
}
}
@@ -6553,15 +6556,15 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
if (tables)
{
for (TABLE_LIST *t= tables; t; t= t->next_local)
- if (!find_write_locked_table(thd->open_tables, t->db,
- t->table_name))
+ if (!find_table_for_mdl_upgrade(thd->open_tables, t->db,
+ t->table_name, FALSE))
return 1;
}
else
{
for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
{
- if (tab->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
+ if (! tab->mdl_ticket->is_upgradable_or_exclusive())
{
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
tab->s->table_name.str);
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 70f69c75de3..031cb846be4 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1204,7 +1204,8 @@ static bool mysql_test_insert(Prepared_statement *stmt,
If we would use locks, then we have to ensure we are not using
TL_WRITE_DELAYED as having two such locks can cause table corruption.
*/
- if (open_normal_and_derived_tables(thd, table_list, 0))
+ if (open_normal_and_derived_tables(thd, table_list,
+ MYSQL_OPEN_FORCE_SHARED_MDL))
goto error;
if ((values= its++))
@@ -1285,7 +1286,7 @@ static int mysql_test_update(Prepared_statement *stmt,
DBUG_ENTER("mysql_test_update");
if (update_precheck(thd, table_list) ||
- open_tables(thd, &table_list, &table_count, 0))
+ open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL))
goto error;
if (table_list->multitable_view)
@@ -1362,7 +1363,8 @@ static bool mysql_test_delete(Prepared_statement *stmt,
DBUG_ENTER("mysql_test_delete");
if (delete_precheck(thd, table_list) ||
- open_normal_and_derived_tables(thd, table_list, 0))
+ open_normal_and_derived_tables(thd, table_list,
+ MYSQL_OPEN_FORCE_SHARED_MDL))
goto error;
if (!table_list->table)
@@ -1420,7 +1422,7 @@ static int mysql_test_select(Prepared_statement *stmt,
goto error;
}
- if (open_normal_and_derived_tables(thd, tables, 0))
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL))
goto error;
thd->used_tables= 0; // Updated by setup_fields
@@ -1481,7 +1483,7 @@ static bool mysql_test_do_fields(Prepared_statement *stmt,
UINT_MAX, FALSE))
DBUG_RETURN(TRUE);
- if (open_normal_and_derived_tables(thd, tables, 0))
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL))
DBUG_RETURN(TRUE);
DBUG_RETURN(setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0));
}
@@ -1511,7 +1513,7 @@ static bool mysql_test_set_fields(Prepared_statement *stmt,
if ((tables && check_table_access(thd, SELECT_ACL, tables, FALSE,
UINT_MAX, FALSE)) ||
- open_normal_and_derived_tables(thd, tables, 0))
+ open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL))
goto error;
while ((var= it++))
@@ -1548,7 +1550,7 @@ static bool mysql_test_call_fields(Prepared_statement *stmt,
if ((tables && check_table_access(thd, SELECT_ACL, tables, FALSE,
UINT_MAX, FALSE)) ||
- open_normal_and_derived_tables(thd, tables, 0))
+ open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL))
goto err;
while ((item= it++))
@@ -1631,7 +1633,8 @@ select_like_stmt_test_with_open(Prepared_statement *stmt,
prepared EXPLAIN yet so derived tables will clean up after
themself.
*/
- if (open_normal_and_derived_tables(stmt->thd, tables, 0))
+ if (open_normal_and_derived_tables(stmt->thd, tables,
+ MYSQL_OPEN_FORCE_SHARED_MDL))
DBUG_RETURN(TRUE);
DBUG_RETURN(select_like_stmt_test(stmt, specific_prepare,
@@ -1675,7 +1678,8 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (select_lex->item_list.elements)
{
- if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables,
+ MYSQL_OPEN_FORCE_SHARED_MDL))
DBUG_RETURN(TRUE);
select_lex->context.resolve_in_select_list= TRUE;
@@ -1694,7 +1698,8 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
we validate metadata of all CREATE TABLE statements,
which keeps metadata validation code simple.
*/
- if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables,
+ MYSQL_OPEN_FORCE_SHARED_MDL))
DBUG_RETURN(TRUE);
}
@@ -1727,7 +1732,7 @@ static bool mysql_test_create_view(Prepared_statement *stmt)
if (create_view_precheck(thd, tables, view, lex->create_view_mode))
goto err;
- if (open_normal_and_derived_tables(thd, tables, 0))
+ if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL))
goto err;
lex->view_prepare_mode= 1;
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc
index ca1543b32d8..50cd361b174 100644
--- a/sql/sql_rename.cc
+++ b/sql/sql_rename.cc
@@ -53,7 +53,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
mysql_ha_rm_tables(thd, table_list);
- if (wait_if_global_read_lock(thd,0,1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
DBUG_RETURN(1);
if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) ||
@@ -189,7 +189,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
unlock_table_names(thd);
err:
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
DBUG_RETURN(error);
}
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 278e0c1445f..9a4ce112ffe 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -3085,12 +3085,10 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table,
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
MDL_SHARED_HIGH_PRIO);
while (!(error=
- thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) &&
+ thd->mdl_context.try_acquire_lock(&table->mdl_request)) &&
!table->mdl_request.ticket && !can_deadlock)
{
- MDL_request_list mdl_requests;
- mdl_requests.push_front(&table->mdl_request);
- if ((error= thd->mdl_context.wait_for_locks(&mdl_requests)))
+ if ((error= thd->mdl_context.wait_for_lock(&table->mdl_request)))
break;
}
return error;
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index e8c2af4c87b..971a1d79363 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -1790,7 +1790,8 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
if (!drop_temporary)
{
- if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1))
+ if (!thd->locked_tables_mode &&
+ thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
DBUG_RETURN(TRUE);
}
@@ -1798,8 +1799,8 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
thd->pop_internal_handler();
- if (thd->global_read_lock_protection > 0)
- start_waiting_global_read_lock(thd);
+ if (thd->global_read_lock.has_protection())
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
if (error)
DBUG_RETURN(TRUE);
@@ -1931,8 +1932,8 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
by parser) it is safe to cache pointer to the TABLE instances
in its elements.
*/
- table->table= find_write_locked_table(thd->open_tables, table->db,
- table->table_name);
+ table->table= find_table_for_mdl_upgrade(thd->open_tables, table->db,
+ table->table_name, FALSE);
if (!table->table)
DBUG_RETURN(1);
table->mdl_request.ticket= table->table->mdl_ticket;
@@ -2200,7 +2201,7 @@ err:
Under LOCK TABLES we should release meta-data locks on the tables
which were dropped. Otherwise we can rely on close_thread_tables()
doing this. Unfortunately in this case we are likely to get more
- false positives in try_acquire_exclusive_lock() function. So
+ false positives in try_acquire_lock() function. So
it makes sense to remove exclusive meta-data locks in all cases.
Leave LOCK TABLES mode if we managed to drop all tables which were
@@ -4142,7 +4143,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
Open or obtain an exclusive metadata lock on table being created.
*/
if (open_and_lock_tables_derived(thd, thd->lex->query_tables, FALSE,
- MYSQL_OPEN_TAKE_UPGRADABLE_MDL))
+ 0))
{
result= TRUE;
goto unlock;
@@ -4353,6 +4354,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
if (!(table= table_list->table))
{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ MDL_request mdl_global_request;
+ MDL_request_list mdl_requests;
/*
If the table didn't exist, we have a shared metadata lock
on it that is left from mysql_admin_table()'s attempt to
@@ -4365,22 +4370,17 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
Attempt to do full-blown table open in mysql_admin_table() has failed.
Let us try to open at least a .FRM for this table.
*/
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
key_length= create_table_def_key(thd, key, table_list, 0);
table_list->mdl_request.init(MDL_key::TABLE,
table_list->db, table_list->table_name,
MDL_EXCLUSIVE);
- MDL_request mdl_global_request;
mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
+ mdl_requests.push_front(&table_list->mdl_request);
+ mdl_requests.push_front(&mdl_global_request);
- if (thd->mdl_context.acquire_global_intention_exclusive_lock(
- &mdl_global_request))
- DBUG_RETURN(0);
-
- if (thd->mdl_context.acquire_exclusive_lock(&table_list->mdl_request))
+ if (thd->mdl_context.acquire_locks(&mdl_requests))
DBUG_RETURN(0);
has_mdl_lock= TRUE;
@@ -5274,8 +5274,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
non-temporary table.
*/
DBUG_ASSERT((create_info->options & HA_LEX_CREATE_TMP_TABLE) ||
- thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, table->db,
- table->table_name));
+ thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
+ table->table_name,
+ MDL_EXCLUSIVE));
/*
We have to write the query before we unlock the tables.
@@ -6458,7 +6459,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
DBUG_RETURN(TRUE);
}
- if (wait_if_global_read_lock(thd,0,1))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
DBUG_RETURN(TRUE);
if (lock_table_names(thd, table_list))
{
@@ -6484,7 +6485,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
unlock_table_names(thd);
view_err:
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
DBUG_RETURN(error);
}
@@ -6568,10 +6569,11 @@ view_err:
Global intention exclusive lock must have been already acquired when
table to be altered was open, so there is no need to do it here.
*/
- DBUG_ASSERT(thd->
- mdl_context.is_global_lock_owner(MDL_INTENTION_EXCLUSIVE));
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL,
+ "", "",
+ MDL_INTENTION_EXCLUSIVE));
- if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request))
+ if (thd->mdl_context.try_acquire_lock(&target_mdl_request))
DBUG_RETURN(TRUE);
if (target_mdl_request.ticket == NULL)
{
@@ -6767,9 +6769,6 @@ view_err:
Under LOCK TABLES we should adjust meta-data locks before finishing
statement. Otherwise we can rely on close_thread_tables() releasing
them.
-
- TODO: Investigate what should be done with upgraded table-level
- lock here...
*/
if (new_name != table_name || new_db != db)
{
@@ -6777,7 +6776,7 @@ view_err:
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
}
else
- mdl_ticket->downgrade_exclusive_lock();
+ mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
}
DBUG_RETURN(error);
}
@@ -7462,7 +7461,7 @@ view_err:
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
}
else
- mdl_ticket->downgrade_exclusive_lock();
+ mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
}
end_temporary:
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index 8719938d85d..12180d9447c 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -385,7 +385,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
LOCK_open is not enough because global read lock is held without holding
LOCK_open).
*/
- if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1))
+ if (!thd->locked_tables_mode &&
+ thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
DBUG_RETURN(TRUE);
if (!create)
@@ -453,8 +454,10 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (thd->locked_tables_mode)
{
/* Under LOCK TABLES we must only accept write locked tables. */
- if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db,
- tables->table_name)))
+ if (!(tables->table= find_table_for_mdl_upgrade(thd->open_tables,
+ tables->db,
+ tables->table_name,
+ FALSE)))
goto end;
}
else
@@ -517,10 +520,10 @@ end:
TABLE instance created by open_n_lock_single_table() and metadata lock.
*/
if (thd->locked_tables_mode && tables && lock_upgrade_done)
- mdl_ticket->downgrade_exclusive_lock();
+ mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
- if (thd->global_read_lock_protection > 0)
- start_waiting_global_read_lock(thd);
+ if (thd->global_read_lock.has_protection())
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
if (!result)
my_ok(thd);
@@ -1886,7 +1889,8 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db,
In the future, only an exclusive metadata lock will be enough.
*/
#ifndef DBUG_OFF
- if (thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, db, old_table))
+ if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table,
+ MDL_EXCLUSIVE))
safe_mutex_assert_owner(&LOCK_open);
#endif
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 51f80c589b2..c9b6a926649 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -614,7 +614,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
#endif
- if (wait_if_global_read_lock(thd, 0, 0))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
{
res= TRUE;
goto err;
@@ -669,7 +669,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
pthread_mutex_unlock(&LOCK_open);
if (mode != VIEW_CREATE_NEW)
query_cache_invalidate3(thd, view, 0);
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
if (res)
goto err;
diff --git a/sql/table.cc b/sql/table.cc
index 74add641544..2bd7a8cc7ec 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -4583,11 +4583,12 @@ void TABLE_LIST::reinit_before_use(THD *thd)
mdl_request.ticket= NULL;
/*
- Not strictly necessary, but we manipulate with the type in open_table(),
- so it's "safe" to reset the lock request type to the parser default, to
- restore things back to first-execution state.
+ Since we manipulate with the metadata lock type in open_table(),
+ we need to reset it to the parser default, to restore things back
+ to first-execution state.
*/
- mdl_request.set_type(MDL_SHARED);
+ mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
/*
@@ -4821,7 +4822,8 @@ void init_mdl_requests(TABLE_LIST *table_list)
for ( ; table_list ; table_list= table_list->next_global)
table_list->mdl_request.init(MDL_key::TABLE,
table_list->db, table_list->table_name,
- MDL_SHARED);
+ table_list->lock_type >= TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
diff --git a/sql/table.h b/sql/table.h
index fb9c335d877..6f75e693021 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -1103,7 +1103,9 @@ struct TABLE_LIST
table_name_length= table_name_length_arg;
alias= (char*) alias_arg;
lock_type= lock_type_arg;
- mdl_request.init(MDL_key::TABLE, db, table_name, MDL_SHARED);
+ mdl_request.init(MDL_key::TABLE, db, table_name,
+ (lock_type >= TL_WRITE_ALLOW_WRITE) ?
+ MDL_SHARED_WRITE : MDL_SHARED_READ);
}
/*
diff --git a/sql/transaction.cc b/sql/transaction.cc
index 1ca455028f0..7b34826d154 100644
--- a/sql/transaction.cc
+++ b/sql/transaction.cc
@@ -586,7 +586,7 @@ bool trans_xa_commit(THD *thd)
}
else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE)
{
- if (wait_if_global_read_lock(thd, 0, 0))
+ if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE))
{
ha_rollback_trans(thd, TRUE);
my_error(ER_XAER_RMERR, MYF(0));
@@ -596,7 +596,7 @@ bool trans_xa_commit(THD *thd)
res= test(ha_commit_one_phase(thd, 1));
if (res)
my_error(ER_XAER_RMERR, MYF(0));
- start_waiting_global_read_lock(thd);
+ thd->global_read_lock.start_waiting_global_read_lock(thd);
}
}
else