#At file:///opt/local/work/6.0-46224-1/ based on revid:tor.didriksen@stripped
3717 Konstantin Osipov 2009-11-18
A prerequisite patch for the fix for Bug#46224
"HANDLER statements within a transaction might lead to deadlocks".
Introduce a notion of a sentinel to MDL_context. A sentinel
is a ticket that separates all tickets in the context into two
groups: before and after it. Currently we can have (and need) only
one designated sentinel -- it separates all locks taken by LOCK
TABLE or HANDLER statement, which must survive COMMIT and ROLLBACK
and all other locks, which must be released at COMMIT and ROLLBACK.
The tricky part is maintaining the sentinel up to date when
someone release its corresponding ticket. This can happen, e.g.
if someone issues DROP TABLE under LOCK TABLES (generally,
see all calls to release_all_locks_for_name()).
MDL_context::release_ticket() is modified to take care of it.
******
A fix and a test case for Bug#46224 "HANDLER statements within a
transaction might lead to deadlocks".
An attempt to mix HANDLER SQL and transactions could lead to a deadlock.
Incompatible change: entering LOCK TABLES mode automatically
closes all open HANDLERs in the current connection.
Incompatible change: previously an attempt to wait on a lock
in a connection that has an open HANDLER statement could wait
indefinitely/deadlock. After this patch, an error ER_LOCK_DEADLOCK
is produced.
The idea of the fix is to merge thd->handler_mdl_context
with the main mdl_context of the connection, used for transactional
locks. This makes deadlock detection possible, since all waits
with locks are "visible" and available to analysis in a single
MDL context of a connection.
Since HANDLER locks and transactional locks have a different life
cycle -- HANDLERs are explicitly open and closed, and so
are HANDLER locks, explicitly acquired and released, whereas
transactional locks "accumulate" till the end of a transaction
and are released only with COMMIT, ROLLBACK and ROLLBACK TO SAVEPOINT,
a concept of "sentinel" was introduced to MDL_context.
All locks, HANDLER and others, reside in the same linked list.
However, a selected element of the list separates locks with
different life cycle. HANDLER locks always reside at the
end of the list, after the sentinel. Transactional locks are
prepended to the beginning of the list, before the sentinel.
Thus, ROLLBACK, COMMIT or ROLLBACK TO SAVEPOINT, only
release those locks that reside before the sentinel. HANDLER locks
must be released explicitly as part of HANDLER CLOSE statement,
or an implicit close.
@ mysql-test/include/handler.inc
Add test coverage for Bug#46224 "HANDLER statements within a
transaction might lead to deadlocks"
@ mysql-test/r/alter_table-big.result
Make the test pass, it wasn't updated in a while (no
semantical change).
@ mysql-test/r/create-big.result
Make the test pass, it wasn't updated in a while (no
semantical change).
@ mysql-test/r/handler_innodb.result
Update results (Bug#46224).
@ mysql-test/r/handler_myisam.result
Update results (Bug#46224).
@ mysql-test/t/alter_table-big.test
Make the test pass, it wasn't updated in a while (no
semantical change).
@ mysql-test/t/create-big.test
Make the test pass, it wasn't updated in a while (no
semantical change).
@ sql/lock.cc
Remove thd->some_tables_deleted, it's never used.
@ sql/mdl.cc
Implement the concept of HANDLER and LOCK TABLES "sentinel".
Implement a method to clone an acquired ticket.
Do not return tickets beyond the sentinel when acquiring
locks, create a copy.
Remove methods to merge and backup MDL_context, they are now
not used (Hurra!). This opens a path to a proper constructor
and destructor of class MDL_context (to be done in a separate
patch).
Modify find_ticket() to provide information about where
the ticket position is with regard to the sentinel.
@ sql/mdl.h
Add declarations necessary for the implementation of the concept of "sentinel", a dedicated ticket separating transactional
and non-transactional locks.
@ sql/sql_base.cc
Remove thd->some_tables_deleted.
Modify deadlock-prevention asserts and deadlock detection
heuristics to take into account that from now on HANDLER locks
reside in the same locking context.
broadcast_refresh() in mysql_notify_thread_having_shared_lock():
this is necessary if the thread having the shared lock
is asleep in tdc_wait_for_old_versions(). It is only possible
with HANDLER t1 OPEN; FLUSH TABLE (since all over code paths
that lead to tdc_wait_for_old_versions() always have an
empty MDL_context), and previously would deadlock. Now
it's possible to not deadlock in this situation.
@ sql/sql_class.cc
Remove now unused methods. Move mysql_ha_cleanup() a few
lines above in THD::cleanup() to make sure that
all handlers are closed when it's time to destroy the MDL_context
of this connection.
Remove handler_mdl_context and handler_tables.
@ sql/sql_class.h
Remove THD::handler_tables, THD::handler_mdl_context, THD::some_tables_deleted.
@ sql/sql_delete.cc
Remove a duplicate mysql_ha_rm_tables() (bad merge).
@ sql/sql_handler.cc
Remove thd->handler_tables.
Remove thd->handler_mdl_context.
Rewrite mysql_ha_open() to have no special provision for MERGE
tables, now that we don't have to manipulate with thd->handler_tables
it's easy to do.
Remove dead code.
Fix a bug in mysql_ha_flush() when we would always flush
a temporary HANDLER when mysql_ha_flush() is called (actually
mysql_ha_flush() never needs to flush temporary tables).
@ sql/sql_insert.cc
Update a comment, no more some_tables_deleted.
@ sql/sql_parse.cc
Implement an incompatible change: entering LOCK TABLES closes
active HANDLERs, if any.
Now that we have a sentinel, we don't need to check
for thd->locked_tables_mode when releasing metadata locks in
COMMIT/ROLLBACK.
@ sql/sql_plist.h
Add new (now necessary) methods to the list class.
@ sql/sql_prepare.cc
Make sure we don't release HANDLER locks when rollback to a
savepoint, set to not keep locks taken at PREPARE.
@ sql/sql_table.cc
Remove thd->some_tables_deleted.
modified:
mysql-test/include/handler.inc
mysql-test/r/alter_table-big.result
mysql-test/r/create-big.result
mysql-test/r/handler_innodb.result
mysql-test/r/handler_myisam.result
mysql-test/t/alter_table-big.test
mysql-test/t/create-big.test
sql/lock.cc
sql/log_event.cc
sql/mdl.cc
sql/mdl.h
sql/rpl_injector.cc
sql/rpl_rli.cc
sql/set_var.cc
sql/slave.cc
sql/sql_acl.cc
sql/sql_base.cc
sql/sql_class.cc
sql/sql_class.h
sql/sql_delete.cc
sql/sql_handler.cc
sql/sql_insert.cc
sql/sql_parse.cc
sql/sql_plist.h
sql/sql_prepare.cc
sql/sql_servers.cc
sql/sql_table.cc
sql/transaction.cc
=== modified file 'mysql-test/include/handler.inc'
--- a/mysql-test/include/handler.inc 2009-10-12 09:08:34 +0000
+++ b/mysql-test/include/handler.inc 2009-11-18 18:47:43 +0000
@@ -561,14 +561,22 @@ let $wait_condition=
--source include/wait_condition.inc
connection default;
--echo connection: default
+--echo # RENAME placed two pending locks.
+--echo # An attempt to open t2 will fail due to one of them.
+--echo # At the same time, open_tables has mysql_ha_flush which will
+--echo # close the open HANDLER for t1. Thus RENAME will be able to
+--echo # go through.
+--error 0, ER_LOCK_DEADLOCK
handler t2 open;
-handler t2 read first;
---error ER_NO_SUCH_TABLE
-handler t1 read next;
-handler t1 close;
+--error 0, ER_UNKNOWN_TABLE
handler t2 close;
+--echo connection: flush
connection flush;
reap;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--error ER_UNKNOWN_TABLE
+handler t1 close;
connection default;
drop table t2;
connection flush;
@@ -748,3 +756,408 @@ USE information_schema;
--error ER_WRONG_USAGE
HANDLER COLUMNS OPEN;
USE test;
+
+--echo #
+--echo # Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL.
+--echo #
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a)) select * from t1;
+create temporary table t3 (a int, key a (a)) select * from t2;
+handler t1 open;
+handler t2 open;
+--echo #
+--echo # LOCK TABLES implicitly closes all handlers.
+--echo #
+lock table t3 read;
+--echo #
+--echo # No HANDLER sql is available under lock tables anyway.
+--echo #
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+handler t1 read next;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+handler t2 close;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+handler t3 open;
+--echo # After UNLOCK TABLES no handlers are around, they were
+--echo # implicitly closed.
+unlock tables;
+drop temporary table t3;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--error ER_UNKNOWN_TABLE
+handler t2 close;
+--echo #
+--echo # Other operations also implicitly close handler:
+--echo #
+--echo # TRUNCATE
+--echo #
+handler t1 open;
+truncate table t1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+handler t1 open;
+--echo #
+--echo # CREATE TRIGGER
+--echo #
+create trigger t1_ai after insert on t1 for each row set @a=1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # DROP TRIGGER
+--echo #
+handler t1 open;
+drop trigger t1_ai;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # ALTER TABLE
+--echo #
+handler t1 open;
+alter table t1 add column b int;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # ANALYZE TABLE
+--echo #
+handler t1 open;
+analyze table t1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # OPTIMIZE TABLE
+--echo #
+handler t1 open;
+optimize table t1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # REPAIR TABLE
+--echo #
+handler t1 open;
+repair table t1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # DROP TABLE, naturally.
+--echo #
+handler t1 open;
+drop table t1;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+create table t1 (a int, b int, key a (a)) select a from t2;
+--echo #
+--echo # RENAME TABLE, naturally
+--echo #
+handler t1 open;
+rename table t1 to t3;
+--error ER_UNKNOWN_TABLE
+handler t1 read next;
+--echo #
+--echo # CREATE TABLE (even with IF NOT EXISTS clause,
+--echo # and the table exists).
+--echo #
+handler t2 open;
+create table if not exists t2 (a int);
+--error ER_UNKNOWN_TABLE
+handler t2 read next;
+rename table t3 to t1;
+drop table t2;
+--echo #
+--echo # FLUSH TABLE doesn't close the table but loses the position
+--echo #
+handler t1 open;
+handler t1 read a prev;
+flush table t1;
+handler t1 read a prev;
+handler t1 close;
+--echo #
+--echo # FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE.
+--echo #
+handler t1 open;
+handler t1 read a prev;
+flush tables with read lock;
+handler t1 read a prev;
+handler t1 close;
+unlock tables;
+--echo #
+--echo # Explore the effect of HANDLER locks on concurrent DDL
+--echo #
+handler t1 open;
+connect(con1, localhost, root,,);
+connect(con2, localhost, root,,);
+connect(con3, localhost, root,,);
+--echo # --> Connection con1;
+connection con1;
+--send drop table t1
+--echo # We can't use connection 'default' as wait_condition will
+--echo # autoclose handlers.
+--echo # --> Connection con2
+connection con2;
+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 # --> Connection default
+connection default;
+handler t1 read a prev;
+handler t1 read a prev;
+handler t1 close;
+--echo # --> connection con1
+connection con1;
+--reap
+--echo # --> connection default
+connection default;
+--echo #
+--echo # Explore the effect of HANDLER locks in parallel with SELECT
+--echo #
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+handler t1 open;
+handler t1 read a prev;
+handler t1 read a prev;
+handler t1 close;
+--echo # --> Connection con1;
+connection con1;
+--send drop table t1
+--echo # --> Connection con2
+connection con2;
+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 # --> Connection default
+connection default;
+--echo # We can still use the table, it's part of the transaction
+select * from t1;
+--echo # Such are the circumstances that t1 is a part of transaction,
+--echo # thus we can reopen it in the handler
+handler t1 open;
+--echo # We can commit the transaction, it doesn't close the handler
+--echo # and doesn't let DROP to proceed.
+commit;
+handler t1 read a prev;
+handler t1 read a prev;
+handler t1 read a prev;
+handler t1 close;
+--echo # --> connection con1
+connection con1;
+--echo # Now drop can proceed
+--reap
+--echo # --> connection default
+connection default;
+--echo #
+--echo # Demonstrate that HANDLER locks and transaction locks
+--echo # reside in the same context, and we don't back-off
+--echo # when have transaction or handler locks.
+--echo #
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a));
+insert into t2 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+--echo # --> connection con1
+connection con1;
+lock table t2 read;
+--echo # --> connection con2
+connection con2;
+--send drop table t2
+--echo # --> connection con1
+connection con1;
+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 default
+connection default;
+--error ER_LOCK_DEADLOCK
+handler t2 open;
+--error ER_LOCK_DEADLOCK
+select * from t2;
+handler t1 open;
+commit;
+--error ER_LOCK_DEADLOCK
+handler t2 open;
+handler t1 close;
+--echo # --> connection con1
+connection con1;
+unlock tables;
+--echo # --> connection con2
+connection con2;
+--reap
+--echo # --> connection default
+connection default;
+handler t1 open;
+handler t1 read a prev;
+handler t1 close;
+
+--echo #
+--echo # ROLLBACK TO SAVEPOINT releases transactional locks,
+--echo # but has no effect on open HANDLERs
+--echo #
+create table t2 like t1;
+create table t3 like t1;
+begin;
+--echo # Have something before the savepoint
+select * from t3;
+savepoint sv;
+handler t1 open;
+handler t1 read a first;
+handler t1 read a next;
+select * from t2;
+--echo # --> connection con1
+connection con1;
+--send drop table t1
+--echo # --> connection con2
+connection con2;
+--send drop table t2
+--echo # --> connection default
+connection default;
+--echo # Let DROP TABLE statements sync in. We must use
+--echo # a separate connection for that, because otherwise SELECT
+--echo # will auto-close the HANDLERs, becaues there are pending
+--echo # exclusive locks against them.
+--echo # --> connection con3
+connection con3;
+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
+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 # Demonstrate that t2 lock was released and t2 was dropped
+--echo # after ROLLBACK TO SAVEPOINT
+--echo # --> connection default
+connection default;
+rollback to savepoint sv;
+--echo # --> connection con2
+connection con2;
+--reap
+--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
+--echo # lock.
+--echo # --> connection default
+connection default;
+handler t1 read a next;
+handler t1 read a next;
+--echo # Demonstrate that the drop will go through as soon as we close the
+--echo # HANDLER
+handler t1 close;
+--echo # connection con1
+connection con1;
+--reap
+--echo # --> connection default
+connection default;
+commit;
+drop table t3;
+--echo #
+--echo # If we have to wait on an exclusive locks while having
+--echo # an open HANDLER, ER_LOCK_DEADLOCK is reported.
+--echo #
+create table t1 (a int, key a(a));
+create table t2 like t1;
+handler t1 open;
+--echo # --> connection con1
+connection con1;
+lock table t2 read;
+--echo # --> connection default
+connection default;
+--error ER_LOCK_DEADLOCK
+drop table t2;
+--error ER_LOCK_DEADLOCK
+rename table t2 to t3;
+--echo # Demonstrate that there is no deadlock with FLUSH TABLE,
+--echo # even though it is waiting for the other table to go away
+--send flush table t2
+--echo # --> connection con2
+connection con2;
+drop table t1;
+--echo # --> connection con1
+connection con1;
+unlock tables;
+--echo # --> connection default
+connection default;
+--reap
+drop table t2;
+
+--echo #
+--echo # Bug #46224 HANDLER statements within a transaction might
+--echo # lead to deadlocks
+--echo #
+create table t1 (a int, key a(a));
+
+
+--echo # --> Connection default
+connection default;
+begin;
+select * from t1;
+handler t1 open;
+
+--echo # --> Connection con1
+connection con1;
+lock tables t1 write;
+
+--echo # --> Connection default
+connection default;
+--send handler t1 read a next
+
+--echo # --> Connection con1
+connection con1;
+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
+--send drop table t1
+
+--echo # --> Connection con2
+connection con2;
+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 # --> Connection con1
+connection default;
+--error ER_LOCK_DEADLOCK
+--reap
+handler t1 close;
+commit;
+
+--echo # Connection 2
+connection con1;
+--reap
+
+--echo # --> connection con1
+connection con1;
+disconnect con1;
+--source include/wait_until_disconnected.inc
+--echo # --> connection con2
+connection con2;
+disconnect con2;
+--source include/wait_until_disconnected.inc
+--echo # --> connection con3
+connection con3;
+disconnect con3;
+--source include/wait_until_disconnected.inc
+connection default;
+
+--echo #
+--echo # A temporary table test.
+--echo # Check that we don't loose positions of HANDLER opened
+--echo # against a temporary table.
+--echo #
+create table t1 (a int, b int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create temporary table t2 (a int, b int, key a (a));
+insert into t2 (a) select a from t1;
+handler t1 open;
+handler t1 read a next;
+handler t2 open;
+handler t2 read a next;
+flush table t1;
+handler t2 read a next;
+--echo # Sic: the position is lost
+handler t1 read a next;
+select * from t1;
+--echo # Sic: the position is not lost
+handler t2 read a next;
+--error ER_CANT_REOPEN_TABLE
+select * from t2;
+handler t2 read a next;
+drop table t1;
+drop temporary table t2;
+
=== modified file 'mysql-test/r/alter_table-big.result'
--- a/mysql-test/r/alter_table-big.result 2007-05-19 06:49:56 +0000
+++ b/mysql-test/r/alter_table-big.result 2009-11-18 18:47:43 +0000
@@ -12,7 +12,7 @@ alter table t1 enable keys;;
insert into t2 values (1);
insert into t1 values (1, 1, 1);
set session debug="-d,sleep_alter_enable_indexes";
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; insert into t2 values (1)
master-bin.000001 # Query 1 # use `test`; alter table t1 enable keys
@@ -41,7 +41,7 @@ alter table t2 change c vc varchar(100)
rename table t1 to t3;
drop table t3;
set session debug="-d,sleep_alter_before_main_binlog";
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; alter table t1 change i c char(10) default 'Test1'
master-bin.000001 # Query 1 # use `test`; insert into t1 values ()
=== modified file 'mysql-test/r/create-big.result'
--- a/mysql-test/r/create-big.result 2007-05-23 11:26:16 +0000
+++ b/mysql-test/r/create-big.result 2009-11-18 18:47:43 +0000
@@ -175,10 +175,10 @@ t2 CREATE TABLE `t2` (
`i` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
drop table t2;
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
-master-bin.000001 # Query 1 # use `test`; insert into t1 values (1)
master-bin.000001 # Query 1 # use `test`; create table t2 like t1
+master-bin.000001 # Query 1 # use `test`; insert into t1 values (1)
master-bin.000001 # Query 1 # use `test`; drop table t1
master-bin.000001 # Query 1 # use `test`; drop table t2
create table t1 (i int);
@@ -197,7 +197,7 @@ reset master;
create table t2 like t1;;
drop table t1;
drop table t2;
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; create table t2 like t1
master-bin.000001 # Query 1 # use `test`; drop table t1
@@ -213,7 +213,7 @@ drop table t2;
create table t2 like t1;;
drop table t1;
drop table t2;
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; create table t2 like t1
master-bin.000001 # Query 1 # use `test`; insert into t2 values (1)
@@ -234,7 +234,7 @@ drop table t2;
create table t2 like t1;;
drop table t1;
drop table t2;
-show binlog events in 'master-bin.000001' from 106;
+show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; create table t2 like t1
master-bin.000001 # Query 1 # use `test`; insert into t2 values (1)
=== modified file 'mysql-test/r/handler_innodb.result'
--- a/mysql-test/r/handler_innodb.result 2009-10-12 09:08:34 +0000
+++ b/mysql-test/r/handler_innodb.result 2009-11-18 18:47:43 +0000
@@ -570,13 +570,18 @@ connection: flush
rename table t1 to t2;;
connection: waiter
connection: default
+# RENAME placed two pending locks.
+# An attempt to open t2 will fail due to one of them.
+# At the same time, open_tables has mysql_ha_flush which will
+# close the open HANDLER for t1. Thus RENAME will be able to
+# go through.
handler t2 open;
-handler t2 read first;
-c1
+handler t2 close;
+connection: flush
handler t1 read next;
-ERROR 42S02: Table 'test.t1' doesn't exist
+ERROR 42S02: Unknown table 't1' in HANDLER
handler t1 close;
-handler t2 close;
+ERROR 42S02: Unknown table 't1' in HANDLER
drop table t2;
drop table if exists t1;
create temporary table t1 (a int, b char(1), key a(a), key b(a,b));
@@ -745,3 +750,397 @@ USE information_schema;
HANDLER COLUMNS OPEN;
ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema
USE test;
+#
+# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL.
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a)) select * from t1;
+create temporary table t3 (a int, key a (a)) select * from t2;
+handler t1 open;
+handler t2 open;
+#
+# LOCK TABLES implicitly closes all handlers.
+#
+lock table t3 read;
+#
+# No HANDLER sql is available under lock tables anyway.
+#
+handler t1 read next;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+handler t2 close;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+handler t3 open;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# After UNLOCK TABLES no handlers are around, they were
+# implicitly closed.
+unlock tables;
+drop temporary table t3;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+handler t2 close;
+ERROR 42S02: Unknown table 't2' in HANDLER
+#
+# Other operations also implicitly close handler:
+#
+# TRUNCATE
+#
+handler t1 open;
+truncate table t1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+handler t1 open;
+#
+# CREATE TRIGGER
+#
+create trigger t1_ai after insert on t1 for each row set @a=1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# DROP TRIGGER
+#
+handler t1 open;
+drop trigger t1_ai;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# ALTER TABLE
+#
+handler t1 open;
+alter table t1 add column b int;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# ANALYZE TABLE
+#
+handler t1 open;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status OK
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# OPTIMIZE TABLE
+#
+handler t1 open;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize note Table does not support optimize, doing recreate + analyze instead
+test.t1 optimize status OK
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# REPAIR TABLE
+#
+handler t1 open;
+repair table t1;
+Table Op Msg_type Msg_text
+test.t1 repair note The storage engine for the table doesn't support repair
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# DROP TABLE, naturally.
+#
+handler t1 open;
+drop table t1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+create table t1 (a int, b int, key a (a)) select a from t2;
+#
+# RENAME TABLE, naturally
+#
+handler t1 open;
+rename table t1 to t3;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# CREATE TABLE (even with IF NOT EXISTS clause,
+# and the table exists).
+#
+handler t2 open;
+create table if not exists t2 (a int);
+Warnings:
+Note 1050 Table 't2' already exists
+handler t2 read next;
+ERROR 42S02: Unknown table 't2' in HANDLER
+rename table t3 to t1;
+drop table t2;
+#
+# FLUSH TABLE doesn't close the table but loses the position
+#
+handler t1 open;
+handler t1 read a prev;
+b a
+NULL 5
+flush table t1;
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 close;
+#
+# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE.
+#
+handler t1 open;
+handler t1 read a prev;
+b a
+NULL 5
+flush tables with read lock;
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 close;
+unlock tables;
+#
+# Explore the effect of HANDLER locks on concurrent DDL
+#
+handler t1 open;
+# --> Connection con1;
+drop table t1 ;
+# We can't use connection 'default' as wait_condition will
+# autoclose handlers.
+# --> Connection con2
+# --> Connection default
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 read a prev;
+b a
+NULL 4
+handler t1 close;
+# --> connection con1
+# --> connection default
+#
+# Explore the effect of HANDLER locks in parallel with SELECT
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+a
+1
+2
+3
+4
+5
+handler t1 open;
+handler t1 read a prev;
+a
+5
+handler t1 read a prev;
+a
+4
+handler t1 close;
+# --> Connection con1;
+drop table t1 ;
+# --> Connection con2
+# --> Connection default
+# We can still use the table, it's part of the transaction
+select * from t1;
+a
+1
+2
+3
+4
+5
+# Such are the circumstances that t1 is a part of transaction,
+# thus we can reopen it in the handler
+handler t1 open;
+# We can commit the transaction, it doesn't close the handler
+# and doesn't let DROP to proceed.
+commit;
+handler t1 read a prev;
+a
+5
+handler t1 read a prev;
+a
+4
+handler t1 read a prev;
+a
+3
+handler t1 close;
+# --> connection con1
+# Now drop can proceed
+# --> connection default
+#
+# Demonstrate that HANDLER locks and transaction locks
+# reside in the same context, and we don't back-off
+# when have transaction or handler locks.
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a));
+insert into t2 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+a
+1
+2
+3
+4
+5
+# --> connection con1
+lock table t2 read;
+# --> connection con2
+drop table t2;
+# --> connection con1
+# --> connection default
+handler t2 open;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+select * from t2;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 open;
+commit;
+handler t2 open;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 close;
+# --> connection con1
+unlock tables;
+# --> connection con2
+# --> connection default
+handler t1 open;
+handler t1 read a prev;
+a
+5
+handler t1 close;
+#
+# ROLLBACK TO SAVEPOINT releases transactional locks,
+# but has no effect on open HANDLERs
+#
+create table t2 like t1;
+create table t3 like t1;
+begin;
+# Have something before the savepoint
+select * from t3;
+a
+savepoint sv;
+handler t1 open;
+handler t1 read a first;
+a
+1
+handler t1 read a next;
+a
+2
+select * from t2;
+a
+# --> connection con1
+drop table t1;
+# --> connection con2
+drop table t2;
+# --> connection default
+# Let DROP TABLE statements sync in. We must use
+# a separate connection for that, because otherwise SELECT
+# will auto-close the HANDLERs, becaues there are pending
+# exclusive locks against them.
+# --> connection con3
+# Demonstrate that t2 lock was released and t2 was dropped
+# after ROLLBACK TO SAVEPOINT
+# --> connection default
+rollback to savepoint sv;
+# --> connection con2
+# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
+# lock.
+# --> connection default
+handler t1 read a next;
+a
+3
+handler t1 read a next;
+a
+4
+# Demonstrate that the drop will go through as soon as we close the
+# HANDLER
+handler t1 close;
+# connection con1
+# --> connection default
+commit;
+drop table t3;
+#
+# If we have to wait on an exclusive locks while having
+# an open HANDLER, ER_LOCK_DEADLOCK is reported.
+#
+create table t1 (a int, key a(a));
+create table t2 like t1;
+handler t1 open;
+# --> connection con1
+lock table t2 read;
+# --> connection default
+drop table t2;
+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
+# Demonstrate that there is no deadlock with FLUSH TABLE,
+# even though it is waiting for the other table to go away
+flush table t2;
+# --> connection con2
+drop table t1;
+# --> connection con1
+unlock tables;
+# --> connection default
+drop table t2;
+#
+# Bug #46224 HANDLER statements within a transaction might
+# lead to deadlocks
+#
+create table t1 (a int, key a(a));
+# --> Connection default
+begin;
+select * from t1;
+a
+handler t1 open;
+# --> Connection con1
+lock tables t1 write;
+# --> Connection default
+handler t1 read a next;
+# --> Connection con1
+drop table t1;
+# --> Connection con2
+# --> Connection con1
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 close;
+commit;
+# Connection 2
+# --> connection con1
+# --> connection con2
+# --> connection con3
+#
+# A temporary table test.
+# Check that we don't loose positions of HANDLER opened
+# against a temporary table.
+#
+create table t1 (a int, b int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create temporary table t2 (a int, b int, key a (a));
+insert into t2 (a) select a from t1;
+handler t1 open;
+handler t1 read a next;
+a b
+1 NULL
+handler t2 open;
+handler t2 read a next;
+a b
+1 NULL
+flush table t1;
+handler t2 read a next;
+a b
+2 NULL
+# Sic: the position is lost
+handler t1 read a next;
+a b
+1 NULL
+select * from t1;
+a b
+1 NULL
+2 NULL
+3 NULL
+4 NULL
+5 NULL
+# Sic: the position is not lost
+handler t2 read a next;
+a b
+3 NULL
+select * from t2;
+ERROR HY000: Can't reopen table: 't2'
+handler t2 read a next;
+a b
+4 NULL
+drop table t1;
+drop temporary table t2;
=== modified file 'mysql-test/r/handler_myisam.result'
--- a/mysql-test/r/handler_myisam.result 2009-10-23 06:24:37 +0000
+++ b/mysql-test/r/handler_myisam.result 2009-11-18 18:47:43 +0000
@@ -569,13 +569,18 @@ connection: flush
rename table t1 to t2;;
connection: waiter
connection: default
+# RENAME placed two pending locks.
+# An attempt to open t2 will fail due to one of them.
+# At the same time, open_tables has mysql_ha_flush which will
+# close the open HANDLER for t1. Thus RENAME will be able to
+# go through.
handler t2 open;
-handler t2 read first;
-c1
+handler t2 close;
+connection: flush
handler t1 read next;
-ERROR 42S02: Table 'test.t1' doesn't exist
+ERROR 42S02: Unknown table 't1' in HANDLER
handler t1 close;
-handler t2 close;
+ERROR 42S02: Unknown table 't1' in HANDLER
drop table t2;
drop table if exists t1;
create temporary table t1 (a int, b char(1), key a(a), key b(a,b));
@@ -744,6 +749,399 @@ HANDLER COLUMNS OPEN;
ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema
USE test;
#
+# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL.
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a)) select * from t1;
+create temporary table t3 (a int, key a (a)) select * from t2;
+handler t1 open;
+handler t2 open;
+#
+# LOCK TABLES implicitly closes all handlers.
+#
+lock table t3 read;
+#
+# No HANDLER sql is available under lock tables anyway.
+#
+handler t1 read next;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+handler t2 close;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+handler t3 open;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# After UNLOCK TABLES no handlers are around, they were
+# implicitly closed.
+unlock tables;
+drop temporary table t3;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+handler t2 close;
+ERROR 42S02: Unknown table 't2' in HANDLER
+#
+# Other operations also implicitly close handler:
+#
+# TRUNCATE
+#
+handler t1 open;
+truncate table t1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+handler t1 open;
+#
+# CREATE TRIGGER
+#
+create trigger t1_ai after insert on t1 for each row set @a=1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# DROP TRIGGER
+#
+handler t1 open;
+drop trigger t1_ai;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# ALTER TABLE
+#
+handler t1 open;
+alter table t1 add column b int;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# ANALYZE TABLE
+#
+handler t1 open;
+analyze table t1;
+Table Op Msg_type Msg_text
+test.t1 analyze status Table is already up to date
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# OPTIMIZE TABLE
+#
+handler t1 open;
+optimize table t1;
+Table Op Msg_type Msg_text
+test.t1 optimize status OK
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# REPAIR TABLE
+#
+handler t1 open;
+repair table t1;
+Table Op Msg_type Msg_text
+test.t1 repair status OK
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# DROP TABLE, naturally.
+#
+handler t1 open;
+drop table t1;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+create table t1 (a int, b int, key a (a)) select a from t2;
+#
+# RENAME TABLE, naturally
+#
+handler t1 open;
+rename table t1 to t3;
+handler t1 read next;
+ERROR 42S02: Unknown table 't1' in HANDLER
+#
+# CREATE TABLE (even with IF NOT EXISTS clause,
+# and the table exists).
+#
+handler t2 open;
+create table if not exists t2 (a int);
+Warnings:
+Note 1050 Table 't2' already exists
+handler t2 read next;
+ERROR 42S02: Unknown table 't2' in HANDLER
+rename table t3 to t1;
+drop table t2;
+#
+# FLUSH TABLE doesn't close the table but loses the position
+#
+handler t1 open;
+handler t1 read a prev;
+b a
+NULL 5
+flush table t1;
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 close;
+#
+# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE.
+#
+handler t1 open;
+handler t1 read a prev;
+b a
+NULL 5
+flush tables with read lock;
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 close;
+unlock tables;
+#
+# Explore the effect of HANDLER locks on concurrent DDL
+#
+handler t1 open;
+# --> Connection con1;
+drop table t1 ;
+# We can't use connection 'default' as wait_condition will
+# autoclose handlers.
+# --> Connection con2
+# --> Connection default
+handler t1 read a prev;
+b a
+NULL 5
+handler t1 read a prev;
+b a
+NULL 4
+handler t1 close;
+# --> connection con1
+# --> connection default
+#
+# Explore the effect of HANDLER locks in parallel with SELECT
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+a
+1
+2
+3
+4
+5
+handler t1 open;
+handler t1 read a prev;
+a
+5
+handler t1 read a prev;
+a
+4
+handler t1 close;
+# --> Connection con1;
+drop table t1 ;
+# --> Connection con2
+# --> Connection default
+# We can still use the table, it's part of the transaction
+select * from t1;
+a
+1
+2
+3
+4
+5
+# Such are the circumstances that t1 is a part of transaction,
+# thus we can reopen it in the handler
+handler t1 open;
+# We can commit the transaction, it doesn't close the handler
+# and doesn't let DROP to proceed.
+commit;
+handler t1 read a prev;
+a
+5
+handler t1 read a prev;
+a
+4
+handler t1 read a prev;
+a
+3
+handler t1 close;
+# --> connection con1
+# Now drop can proceed
+# --> connection default
+#
+# Demonstrate that HANDLER locks and transaction locks
+# reside in the same context, and we don't back-off
+# when have transaction or handler locks.
+#
+create table t1 (a int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create table t2 (a int, key a (a));
+insert into t2 (a) values (1), (2), (3), (4), (5);
+begin;
+select * from t1;
+a
+1
+2
+3
+4
+5
+# --> connection con1
+lock table t2 read;
+# --> connection con2
+drop table t2;
+# --> connection con1
+# --> connection default
+handler t2 open;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+select * from t2;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 open;
+commit;
+handler t2 open;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 close;
+# --> connection con1
+unlock tables;
+# --> connection con2
+# --> connection default
+handler t1 open;
+handler t1 read a prev;
+a
+5
+handler t1 close;
+#
+# ROLLBACK TO SAVEPOINT releases transactional locks,
+# but has no effect on open HANDLERs
+#
+create table t2 like t1;
+create table t3 like t1;
+begin;
+# Have something before the savepoint
+select * from t3;
+a
+savepoint sv;
+handler t1 open;
+handler t1 read a first;
+a
+1
+handler t1 read a next;
+a
+2
+select * from t2;
+a
+# --> connection con1
+drop table t1;
+# --> connection con2
+drop table t2;
+# --> connection default
+# Let DROP TABLE statements sync in. We must use
+# a separate connection for that, because otherwise SELECT
+# will auto-close the HANDLERs, becaues there are pending
+# exclusive locks against them.
+# --> connection con3
+# Demonstrate that t2 lock was released and t2 was dropped
+# after ROLLBACK TO SAVEPOINT
+# --> connection default
+rollback to savepoint sv;
+# --> connection con2
+# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
+# lock.
+# --> connection default
+handler t1 read a next;
+a
+3
+handler t1 read a next;
+a
+4
+# Demonstrate that the drop will go through as soon as we close the
+# HANDLER
+handler t1 close;
+# connection con1
+# --> connection default
+commit;
+drop table t3;
+#
+# If we have to wait on an exclusive locks while having
+# an open HANDLER, ER_LOCK_DEADLOCK is reported.
+#
+create table t1 (a int, key a(a));
+create table t2 like t1;
+handler t1 open;
+# --> connection con1
+lock table t2 read;
+# --> connection default
+drop table t2;
+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
+# Demonstrate that there is no deadlock with FLUSH TABLE,
+# even though it is waiting for the other table to go away
+flush table t2;
+# --> connection con2
+drop table t1;
+# --> connection con1
+unlock tables;
+# --> connection default
+drop table t2;
+#
+# Bug #46224 HANDLER statements within a transaction might
+# lead to deadlocks
+#
+create table t1 (a int, key a(a));
+# --> Connection default
+begin;
+select * from t1;
+a
+handler t1 open;
+# --> Connection con1
+lock tables t1 write;
+# --> Connection default
+handler t1 read a next;
+# --> Connection con1
+drop table t1;
+# --> Connection con2
+# --> Connection con1
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+handler t1 close;
+commit;
+# Connection 2
+# --> connection con1
+# --> connection con2
+# --> connection con3
+#
+# A temporary table test.
+# Check that we don't loose positions of HANDLER opened
+# against a temporary table.
+#
+create table t1 (a int, b int, key a (a));
+insert into t1 (a) values (1), (2), (3), (4), (5);
+create temporary table t2 (a int, b int, key a (a));
+insert into t2 (a) select a from t1;
+handler t1 open;
+handler t1 read a next;
+a b
+1 NULL
+handler t2 open;
+handler t2 read a next;
+a b
+1 NULL
+flush table t1;
+handler t2 read a next;
+a b
+2 NULL
+# Sic: the position is lost
+handler t1 read a next;
+a b
+1 NULL
+select * from t1;
+a b
+1 NULL
+2 NULL
+3 NULL
+4 NULL
+5 NULL
+# Sic: the position is not lost
+handler t2 read a next;
+a b
+3 NULL
+select * from t2;
+ERROR HY000: Can't reopen table: 't2'
+handler t2 read a next;
+a b
+4 NULL
+drop table t1;
+drop temporary table t2;
+#
# BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash
#
CREATE TABLE t1 AS SELECT 1 AS f1;
=== modified file 'mysql-test/t/alter_table-big.test'
--- a/mysql-test/t/alter_table-big.test 2009-03-06 14:56:17 +0000
+++ b/mysql-test/t/alter_table-big.test 2009-11-18 18:47:43 +0000
@@ -50,8 +50,7 @@ connection default;
--reap
set session debug="-d,sleep_alter_enable_indexes";
# Check that statements were executed/binlogged in correct order.
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
# Clean up
drop tables t1, t2;
@@ -111,8 +110,7 @@ drop table t3;
set session debug="-d,sleep_alter_before_main_binlog";
# Check that all statements were logged in correct order
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
--echo End of 5.1 tests
=== modified file 'mysql-test/t/create-big.test'
--- a/mysql-test/t/create-big.test 2007-05-23 11:26:16 +0000
+++ b/mysql-test/t/create-big.test 2009-11-18 18:47:43 +0000
@@ -305,8 +305,7 @@ connection default;
show create table t2;
drop table t2;
# Let us check that statements were executed/binlogged in correct order
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
# Now let us check the gap between check for target table
# existance and copying of .frm file.
@@ -330,8 +329,7 @@ drop table t1;
connection default;
--reap
drop table t2;
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
# And now he gap between copying of .frm file and ha_create_table() call.
create table t1 (i int);
@@ -359,8 +357,7 @@ drop table t1;
connection default;
--reap
drop table t2;
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
# Finally we check the gap between ha_create_table() and binlogging
create table t1 (i int);
@@ -386,7 +383,6 @@ drop table t1;
connection default;
--reap
drop table t2;
---replace_column 2 # 5 #
-show binlog events in 'master-bin.000001' from 106;
+--source include/show_binlog_events2.inc
set session debug="-d,sleep_create_like_before_binlogging";
=== modified file 'sql/lock.cc'
--- a/sql/lock.cc 2009-11-02 15:11:43 +0000
+++ b/sql/lock.cc 2009-11-18 18:47:43 +0000
@@ -336,23 +336,12 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd,
preserved.
*/
reset_lock_data(sql_lock);
- thd->some_tables_deleted=1; // Try again
sql_lock->lock_count= 0; // Locks are already freed
// Fall through: unlock, reset lock data, free and retry
}
- else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH))
- {
- /*
- Success and nobody set thd->some_tables_deleted to force reopen
- or we were called with MYSQL_LOCK_IGNORE_FLUSH so such attempts
- should be ignored.
- */
- break;
- }
- else if (!thd->open_tables)
+ else
{
- // Only using temporary tables, no need to unlock
- thd->some_tables_deleted=0;
+ /* Success */
break;
}
thd_proc_info(thd, 0);
=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc 2009-11-10 07:47:59 +0000
+++ b/sql/log_event.cc 2009-11-18 18:47:43 +0000
@@ -5319,8 +5319,7 @@ int Xid_log_event::do_apply_event(Relay_
if (!(res= trans_commit(thd)))
{
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
}
return res;
}
=== modified file 'sql/mdl.cc'
--- a/sql/mdl.cc 2009-10-25 13:41:27 +0000
+++ b/sql/mdl.cc 2009-11-18 18:47:43 +0000
@@ -187,6 +187,7 @@ void MDL_context::init(THD *thd_arg)
{
m_has_global_shared_lock= FALSE;
m_thd= thd_arg;
+ m_lt_or_ha_sentinel= NULL;
/*
FIXME: In reset_n_backup_open_tables_state,
we abuse "init" as a reset, i.e. call it on an already
@@ -218,76 +219,6 @@ void MDL_context::destroy()
/**
- Backup and reset state of meta-data locking context.
-
- mdl_context_backup_and_reset(), mdl_context_restore() and
- mdl_context_merge() are used by HANDLER implementation which
- needs to open table for new HANDLER independently of already
- open HANDLERs and add this table/metadata lock to the set of
- tables open/metadata locks for HANDLERs afterwards.
-*/
-
-void MDL_context::backup_and_reset(MDL_context *backup)
-{
- DBUG_ASSERT(backup->m_tickets.is_empty());
-
- m_tickets.swap(backup->m_tickets);
-
- backup->m_has_global_shared_lock= m_has_global_shared_lock;
- /*
- When the main context is swapped out, one can not take
- the global shared lock, and one can not rely on it:
- the functionality in this mode is reduced, since it exists as
- a temporary hack to support ad-hoc opening of system tables.
- */
- m_has_global_shared_lock= FALSE;
-}
-
-
-/**
- Restore state of meta-data locking context from backup.
-*/
-
-void MDL_context::restore_from_backup(MDL_context *backup)
-{
- DBUG_ASSERT(m_tickets.is_empty());
- DBUG_ASSERT(m_has_global_shared_lock == FALSE);
-
- m_tickets.swap(backup->m_tickets);
- m_has_global_shared_lock= backup->m_has_global_shared_lock;
-}
-
-
-/**
- Merge meta-data locks from one context into another.
-*/
-
-void MDL_context::merge(MDL_context *src)
-{
- MDL_ticket *ticket;
-
- DBUG_ASSERT(m_thd == src->m_thd);
-
- if (!src->m_tickets.is_empty())
- {
- Ticket_iterator it(src->m_tickets);
- while ((ticket= it++))
- {
- DBUG_ASSERT(ticket->m_ctx);
- ticket->m_ctx= this;
- m_tickets.push_front(ticket);
- }
- src->m_tickets.empty();
- }
- /*
- MDL_context::merge() is a hack used in one place only: to open
- an SQL handler. We never acquire the global shared lock there.
- */
- DBUG_ASSERT(! src->m_has_global_shared_lock);
-}
-
-
-/**
Initialize a lock request.
This is to be used for every lock request.
@@ -606,7 +537,7 @@ MDL_lock::can_grant_lock(const MDL_conte
if (waiting.is_empty() || type_arg == MDL_SHARED_HIGH_PRIO)
can_grant= TRUE;
}
- else if (granted.head()->get_ctx() == requestor_ctx)
+ else if (granted.front()->get_ctx() == requestor_ctx)
{
/*
When exclusive lock comes from the same context we can satisfy our
@@ -659,20 +590,31 @@ MDL_lock::can_grant_lock(const MDL_conte
/**
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.
@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?
@return A pointer to the lock ticket for the object or NULL otherwise.
*/
MDL_ticket *
-MDL_context::find_ticket(MDL_request *mdl_request)
+MDL_context::find_ticket(MDL_request *mdl_request,
+ bool *is_lt_or_ha)
{
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;
+
if (mdl_request->type == ticket->m_type &&
mdl_request->key.is_equal(&ticket->m_lock->key))
break;
@@ -709,6 +651,8 @@ MDL_context::try_acquire_shared_lock(MDL
MDL_lock *lock;
MDL_key *key= &mdl_request->key;
MDL_ticket *ticket;
+ bool is_lt_or_ha;
+ bool has_ticket;
DBUG_ASSERT(mdl_request->is_shared() && mdl_request->ticket == NULL);
@@ -724,10 +668,13 @@ MDL_context::try_acquire_shared_lock(MDL
}
/*
- Check whether the context already holds a shared lock on the object,
- and if so, grant the request.
+ Check whether the context already holds a shared lock on the
+ object, and if so, grant the request.
+ If the existing ticket is a HANDLER or LOCK TABLES ticket,
+ or if we're acquiring a HANDLER ticket, we will create a
+ separate ticket for it, see below.
*/
- if ((ticket= find_ticket(mdl_request)))
+ if ((ticket= find_ticket(mdl_request, &is_lt_or_ha)) && !is_lt_or_ha)
{
DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED);
/* Only shared locks can be recursive. */
@@ -735,6 +682,7 @@ MDL_context::try_acquire_shared_lock(MDL
mdl_request->ticket= ticket;
return FALSE;
}
+ has_ticket= test(ticket);
pthread_mutex_lock(&LOCK_mdl);
@@ -764,7 +712,12 @@ MDL_context::try_acquire_shared_lock(MDL
}
}
- if (lock->can_grant_lock(this, mdl_request->type, FALSE))
+ /*
+ If we already have such ticket, just
+ create another MDL_ticket object and grant it.
+ Otherwise check if we can grant the lock.
+ */
+ if (has_ticket || lock->can_grant_lock(this, mdl_request->type, FALSE))
{
mdl_request->ticket= ticket;
lock->granted.push_front(ticket);
@@ -787,6 +740,43 @@ MDL_context::try_acquire_shared_lock(MDL
/**
+ 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,
+ we don't release a transactional ticket, and
+ vice versa -- when we COMMIT, we don't mistakenly
+ release a ticket for an open HANDLER.
+*/
+
+bool
+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());
+
+ if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
+ return TRUE;
+
+ ticket->m_state= MDL_ACQUIRED;
+ ticket->m_lock= mdl_request->ticket->m_lock;
+ mdl_request->ticket= ticket;
+
+ pthread_mutex_lock(&LOCK_mdl);
+ ticket->m_lock->granted.push_front(ticket);
+ if (mdl_request->type == MDL_SHARED_UPGRADABLE)
+ global_lock.active_intention_exclusive++;
+ pthread_mutex_unlock(&LOCK_mdl);
+
+ m_tickets.push_front(ticket);
+
+ return FALSE;
+}
+
+/**
Notify a thread holding a shared metadata lock which
conflicts with a pending exclusive lock.
@@ -850,7 +840,9 @@ bool MDL_context::acquire_exclusive_lock
safe_mutex_assert_not_owner(&LOCK_open);
/* Exclusive locks must always be acquired first, all at once. */
- DBUG_ASSERT(! has_locks());
+ DBUG_ASSERT(! has_locks() ||
+ (m_lt_or_ha_sentinel &&
+ m_tickets.front() == m_lt_or_ha_sentinel));
if (m_has_global_shared_lock)
{
@@ -924,6 +916,17 @@ bool MDL_context::acquire_exclusive_lock
if (!mdl_request)
break;
+ 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.
+ */
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ goto err;
+ }
+
/* There is a shared or exclusive lock on the object. */
DEBUG_SYNC(m_thd, "mdl_acquire_exclusive_locks_wait");
@@ -1041,6 +1044,23 @@ MDL_ticket::upgrade_shared_lock_to_exclu
if (m_lock->can_grant_lock(m_ctx, MDL_EXCLUSIVE, TRUE))
break;
+ /*
+ 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 the only case of lock upgrade *not* under
+ LOCK TABLES, is ALTER TABLE. Which leaves us with the following
+ scenario for deadlock only:
+
+ connection 1 connection 2
+ handler t1 open; handler t2 open;
+ alter table t2 ... alter table t1 ...
+
+ But even that scenario is quite remote, since ALTER
+ will perform mysql_ha_flush() in the beginning, and thus
+ if there is a pending lock upgrade, close the HANDLER.
+ Since the possibility of ALTER interleaving in the above
+ manner is quite remove, we do nothing here to address it.
+ */
bool signalled= FALSE;
MDL_ticket *conflicting_ticket;
MDL_lock::Ticket_iterator it(m_lock->granted);
@@ -1280,6 +1300,9 @@ void MDL_context::release_ticket(MDL_tic
safe_mutex_assert_owner(&LOCK_mdl);
+ if (ticket == m_lt_or_ha_sentinel)
+ m_lt_or_ha_sentinel= Ticket_list::Iterator(m_tickets, ticket).next();
+
m_tickets.remove(ticket);
switch (ticket->m_type)
@@ -1317,14 +1340,21 @@ void MDL_context::release_ticket(MDL_tic
/**
- Release all locks associated with the context.
+ Release all locks associated with the context. If the sentinel
+ is not NULL, do not release locks before and including the
+ sentinel.
- This function is used to back off in case of a lock conflict.
- It is also used to release shared locks in the end of an SQL
- statement.
+ This function is used to:
+ - back off in case of a lock conflict.
+ - release all locks in the end of a transaction
+ - rollback to a savepoint
+
+ The sentinel semantics is used to support LOCK TABLES
+ mode, and HANDLER statements: locks taken by these statement
+ survive COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT.
*/
-void MDL_context::release_all_locks()
+void MDL_context::release_all_locks_after(MDL_ticket *sentinel)
{
MDL_ticket *ticket;
Ticket_iterator it(m_tickets);
@@ -1336,7 +1366,7 @@ void MDL_context::release_all_locks()
DBUG_VOID_RETURN;
pthread_mutex_lock(&LOCK_mdl);
- while ((ticket= it++))
+ while ((ticket= it++) && ticket != sentinel)
{
DBUG_PRINT("info", ("found lock to release ticket=%p", ticket));
release_ticket(ticket);
@@ -1345,8 +1375,6 @@ void MDL_context::release_all_locks()
pthread_cond_broadcast(&COND_mdl);
pthread_mutex_unlock(&LOCK_mdl);
- m_tickets.empty();
-
DBUG_VOID_RETURN;
}
@@ -1452,8 +1480,9 @@ MDL_context::is_exclusive_lock_owner(MDL
const char *db, const char *name)
{
MDL_request mdl_request;
+ bool unused;
mdl_request.init(mdl_namespace, db, name, MDL_EXCLUSIVE);
- MDL_ticket *ticket= find_ticket(&mdl_request);
+ MDL_ticket *ticket= find_ticket(&mdl_request, &unused);
DBUG_ASSERT(ticket == NULL || ticket->m_state == MDL_ACQUIRED);
@@ -1593,19 +1622,83 @@ void *MDL_ticket::get_cached_object()
void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint)
{
- MDL_ticket *ticket;
- Ticket_iterator it(m_tickets);
DBUG_ENTER("MDL_context::rollback_to_savepoint");
- while ((ticket= it++))
+ release_all_locks_after(mdl_savepoint);
+
+ DBUG_VOID_RETURN;
+}
+
+
+void MDL_context::commit_transaction()
+{
+ DBUG_ENTER("MDL_context::commit_or_rollback_transaction");
+ release_all_locks_after(m_lt_or_ha_sentinel);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Should be used only when we know for sure
+ we're not under LOCK TABLES.
+*/
+
+void MDL_context::release_all_locks()
+{
+ DBUG_ENTER("MDL_context::release_all_locks");
+ DBUG_ASSERT(m_lt_or_ha_sentinel == NULL);
+
+ release_all_locks_after(m_lt_or_ha_sentinel);
+ m_lt_or_ha_sentinel= NULL;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Does this savepoint has this lock?
+
+ @retval TRUE if the ticket belongs to a savepoint taken.
+ @retval FALSE The ticket is either LT/HA ticket,
+ or is taken after the savepoint.
+*/
+
+bool MDL_context::has_lock(MDL_ticket *mdl_savepoint,
+ MDL_ticket *mdl_ticket)
+{
+ MDL_ticket *ticket;
+ MDL_context::Ticket_iterator it(m_tickets);
+
+ while ((ticket= it++) && ticket != m_lt_or_ha_sentinel)
{
- /* Stop when lock was acquired before this savepoint. */
+ /* First met the savepoint. Then ticket must be sometimes after it. */
if (ticket == mdl_savepoint)
- break;
- release_lock(ticket);
+ return TRUE;
+ /* First met the ticket. */
+ if (ticket == mdl_ticket)
+ return FALSE;
}
-
- DBUG_VOID_RETURN;
+ return FALSE;
}
+/**
+ Rearrange the ticket to reside in the part of the list that's
+ beyong m_lt_or_ha_sentinel. This effectively changes the ticket
+ life cycle, from automatic to manual: i.e. the ticket is no
+ longer released by MDL_context::commit_transaction() or
+ MDL_context::rollback_to_savepoint(), it must be released manually.
+*/
+
+void MDL_context::move_lt_or_ha_ticket(MDL_ticket *mdl_ticket)
+{
+ m_tickets.remove(mdl_ticket);
+ if (m_lt_or_ha_sentinel == NULL)
+ {
+ m_lt_or_ha_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);
+}
=== modified file 'sql/mdl.h'
--- a/sql/mdl.h 2009-10-12 09:08:34 +0000
+++ b/sql/mdl.h 2009-11-18 18:47:43 +0000
@@ -327,19 +327,15 @@ public:
void init(THD *thd);
void destroy();
- void backup_and_reset(MDL_context *backup);
- void restore_from_backup(MDL_context *backup);
- void merge(MDL_context *source);
-
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 acquire_global_shared_lock();
+ bool clone_ticket(MDL_request *mdl_request);
bool wait_for_locks(MDL_request_list *requests);
- void release_all_locks();
void release_all_locks_for_name(MDL_ticket *ticket);
void release_lock(MDL_ticket *ticket);
void release_global_shared_lock();
@@ -350,6 +346,9 @@ public:
bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
const char *db, const char *name);
+
+ bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket);
+
inline bool has_locks() const
{
return !m_tickets.is_empty();
@@ -357,19 +356,43 @@ public:
inline MDL_ticket *mdl_savepoint()
{
- return m_tickets.head();
+ return m_tickets.front();
+ }
+
+ void set_lt_or_ha_sentinel()
+ {
+ DBUG_ASSERT(m_lt_or_ha_sentinel == NULL);
+ m_lt_or_ha_sentinel= mdl_savepoint();
+ }
+ MDL_ticket *lt_or_ha_sentinel() const { return m_lt_or_ha_sentinel; }
+
+ void clear_lt_or_ha_sentinel()
+ {
+ m_lt_or_ha_sentinel= NULL;
}
+ void move_lt_or_ha_ticket(MDL_ticket *mdl_ticket);
+
+ void commit_transaction();
+ void release_all_locks();
void rollback_to_savepoint(MDL_ticket *mdl_savepoint);
inline THD *get_thd() const { return m_thd; }
private:
Ticket_list m_tickets;
bool m_has_global_shared_lock;
+ /**
+ When entering LOCK TABLES mode, remember the last taken
+ metadata lock. COMMIT/ROLLBACK must preserve these metadata
+ locks.
+ */
+ MDL_ticket *m_lt_or_ha_sentinel;
THD *m_thd;
private:
void release_ticket(MDL_ticket *ticket);
- MDL_ticket *find_ticket(MDL_request *mdl_req);
+ MDL_ticket *find_ticket(MDL_request *mdl_req,
+ bool *is_lt_or_ha);
+ void release_all_locks_after(MDL_ticket *sentinel);
};
=== modified file 'sql/rpl_injector.cc'
--- a/sql/rpl_injector.cc 2009-03-06 22:17:00 +0000
+++ b/sql/rpl_injector.cc 2009-11-18 18:47:43 +0000
@@ -86,8 +86,7 @@ int injector::transaction::commit()
if (!trans_commit(m_thd))
{
close_thread_tables(m_thd);
- if (!m_thd->locked_tables_mode)
- m_thd->mdl_context.release_all_locks();
+ m_thd->mdl_context.commit_transaction();
}
DBUG_RETURN(0);
}
@@ -99,8 +98,7 @@ int injector::transaction::rollback()
if (!trans_rollback(m_thd))
{
close_thread_tables(m_thd);
- if (!m_thd->locked_tables_mode)
- m_thd->mdl_context.release_all_locks();
+ m_thd->mdl_context.commit_transaction();
}
DBUG_RETURN(0);
}
=== modified file 'sql/rpl_rli.cc'
--- a/sql/rpl_rli.cc 2009-10-26 14:02:26 +0000
+++ b/sql/rpl_rli.cc 2009-11-18 18:47:43 +0000
@@ -922,8 +922,8 @@ void Relay_log_info::cleanup_context(THD
}
m_table_map.clear_tables();
slave_close_thread_tables(thd);
- if (error && !thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ if (error)
+ thd->mdl_context.commit_transaction();
clear_flag(IN_STMT);
/*
Cleanup for the flags that have been set at do_apply_event.
=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc 2009-11-02 15:16:58 +0000
+++ b/sql/set_var.cc 2009-11-18 18:47:43 +0000
@@ -3758,8 +3758,7 @@ static bool set_option_autocommit(THD *t
return TRUE;
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
}
if (var->save_result.ulong_value != 0)
=== modified file 'sql/slave.cc'
--- a/sql/slave.cc 2009-11-10 07:47:59 +0000
+++ b/sql/slave.cc 2009-11-18 18:47:43 +0000
@@ -2570,8 +2570,7 @@ static int exec_relay_log_event(THD* thd
exec_res= 0;
trans_rollback(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
/* chance for concurrent connection to get more locks */
safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE),
(CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli);
=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc 2009-11-09 10:27:46 +0000
+++ b/sql/sql_acl.cc 2009-11-18 18:47:43 +0000
@@ -741,8 +741,7 @@ my_bool acl_reload(THD *thd)
end:
trans_commit_implicit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
DBUG_RETURN(return_val);
}
@@ -3916,8 +3915,7 @@ my_bool grant_reload(THD *thd)
rw_unlock(&LOCK_grant);
trans_commit_implicit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
/*
It is OK failing to load procs_priv table because we may be
=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc 2009-11-09 10:27:46 +0000
+++ b/sql/sql_base.cc 2009-11-18 18:47:43 +0000
@@ -1259,7 +1259,6 @@ static void close_open_tables(THD *thd)
while (thd->open_tables)
found_old_table|= close_thread_table(thd, &thd->open_tables);
- thd->some_tables_deleted= 0;
/* Free tables to hold down open files */
while (table_cache_count > table_cache_size && unused_tables)
@@ -1473,7 +1472,7 @@ void close_thread_tables(THD *thd)
if (thd->locked_tables_mode == LTM_LOCK_TABLES)
DBUG_VOID_RETURN;
- thd->locked_tables_mode= LTM_NONE;
+ thd->leave_locked_tables_mode();
/* Fallthrough */
}
@@ -1503,16 +1502,21 @@ void close_thread_tables(THD *thd)
if (thd->open_tables)
close_open_tables(thd);
- /*
- Defer the release of metadata locks until the current transaction
- is either committed or rolled back. This prevents other statements
- from modifying the table for the entire duration of this transaction.
- This provides commitment ordering for guaranteeing serializability
- across multiple transactions.
- */
- if (!thd->in_multi_stmt_transaction() ||
- (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
+ if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)
+ {
thd->mdl_context.release_all_locks();
+ }
+ else if (! thd->in_multi_stmt_transaction())
+ {
+ /*
+ Defer the release of metadata locks until the current transaction
+ is either committed or rolled back. This prevents other statements
+ from modifying the table for the entire duration of this transaction.
+ This provides commitment ordering for guaranteeing serializability
+ across multiple transactions.
+ */
+ thd->mdl_context.commit_transaction();
+ }
DBUG_VOID_RETURN;
}
@@ -2340,7 +2344,8 @@ open_table_get_mdl_lock(THD *thd, TABLE_
enforced by asserts in metadata locking subsystem.
*/
mdl_request->set_type(MDL_EXCLUSIVE);
- DBUG_ASSERT(! thd->mdl_context.has_locks());
+ DBUG_ASSERT(! thd->mdl_context.has_locks() ||
+ thd->handler_tables_hash.records);
if (thd->mdl_context.acquire_exclusive_lock(mdl_request))
return 1;
@@ -2798,7 +2803,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
if (!share->free_tables.is_empty())
{
- table= share->free_tables.head();
+ table= share->free_tables.front();
table_def_use_table(thd, table);
/* We need to release share as we have EXTRA reference to it in our hands. */
release_table_share(share);
@@ -3089,7 +3094,7 @@ Locked_tables_list::init_locked_tables(T
return TRUE;
}
}
- thd->locked_tables_mode= LTM_LOCK_TABLES;
+ thd->enter_locked_tables_mode(LTM_LOCK_TABLES);
return FALSE;
}
@@ -3128,7 +3133,7 @@ Locked_tables_list::unlock_locked_tables
*/
table_list->table->pos_in_locked_tables= NULL;
}
- thd->locked_tables_mode= LTM_NONE;
+ thd->leave_locked_tables_mode();
close_thread_tables(thd);
}
@@ -3643,7 +3648,8 @@ end_with_lock_open:
Open_table_context::Open_table_context(THD *thd)
:m_action(OT_NO_ACTION),
- m_can_deadlock(thd->in_multi_stmt_transaction() &&
+ m_can_deadlock((thd->in_multi_stmt_transaction() ||
+ thd->mdl_context.lt_or_ha_sentinel())&&
thd->mdl_context.has_locks())
{}
@@ -4142,7 +4148,7 @@ bool open_tables(THD *thd, TABLE_LIST **
even if they don't create problems for current thread (i.e. to avoid
having DDL blocked by HANDLERs opened for long time).
*/
- if (thd->handler_tables)
+ if (thd->handler_tables_hash.records)
mysql_ha_flush(thd);
/*
@@ -4650,11 +4656,10 @@ retry:
{
/*
Even though we have failed to open table we still need to
- call release_all_locks() to release metadata locks which
+ call release_all_locks_after() to release metadata locks which
might have been acquired successfully.
*/
- if (! thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.release_all_locks();
table_list->mdl_request.ticket= 0;
if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list))
break;
@@ -4705,8 +4710,7 @@ retry:
close_thread_tables(thd);
table_list->table= NULL;
table_list->mdl_request.ticket= NULL;
- if (! thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.release_all_locks();
goto retry;
}
}
@@ -4775,7 +4779,8 @@ bool open_and_lock_tables_derived(THD *t
break;
if (!need_reopen)
DBUG_RETURN(TRUE);
- if (thd->in_multi_stmt_transaction() && has_locks)
+ if ((thd->in_multi_stmt_transaction() ||
+ thd->mdl_context.lt_or_ha_sentinel()) && has_locks)
{
my_error(ER_LOCK_DEADLOCK, MYF(0));
DBUG_RETURN(TRUE);
@@ -5132,7 +5137,7 @@ bool lock_tables(THD *thd, TABLE_LIST *t
*/
mark_real_tables_as_free_for_reuse(first_not_own);
DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED"));
- thd->locked_tables_mode= LTM_PRELOCKED;
+ thd->enter_locked_tables_mode(LTM_PRELOCKED);
}
}
else
@@ -5234,8 +5239,7 @@ void close_tables_for_reopen(THD *thd, T
for (tmp= first_not_own_table; tmp; tmp= tmp->next_global)
tmp->mdl_request.ticket= NULL;
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
}
@@ -8235,6 +8239,7 @@ bool mysql_notify_thread_having_shared_l
if (!thd_table->needs_reopen())
signalled|= mysql_lock_abort_for_thread(thd, thd_table);
}
+ broadcast_refresh();
pthread_mutex_unlock(&LOCK_open);
return signalled;
}
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2009-11-10 07:47:59 +0000
+++ b/sql/sql_class.cc 2009-11-18 18:47:43 +0000
@@ -491,7 +491,7 @@ THD::THD()
catalog= (char*)"std"; // the only catalog we have for now
main_security_ctx.init();
security_ctx= &main_security_ctx;
- some_tables_deleted=no_errors=password= 0;
+ no_errors=password= 0;
query_start_used= 0;
count_cuted_fields= CHECK_FIELD_IGNORE;
killed= NOT_KILLED;
@@ -1005,6 +1005,7 @@ void THD::cleanup(void)
}
locked_tables_list.unlock_locked_tables(this);
+ mysql_ha_cleanup(this);
/*
If the thread was in the middle of an ongoing transaction (rolled
@@ -1021,7 +1022,6 @@ void THD::cleanup(void)
#endif /* defined(ENABLED_DEBUG_SYNC) */
wt_thd_destroy(&transaction.wt);
- mysql_ha_cleanup(this);
delete_dynamic(&user_var_events);
my_hash_free(&user_vars);
close_temporary_tables(this);
@@ -1098,7 +1098,6 @@ THD::~THD()
cleanup();
mdl_context.destroy();
- handler_mdl_context.destroy();
ha_close_connection(this);
mysql_audit_release(this);
plugin_thdvar_cleanup(this);
@@ -3135,12 +3134,11 @@ void THD::restore_backup_open_tables_sta
to be sure that it was properly cleaned up.
*/
DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 &&
- handler_tables == 0 && derived_tables == 0 &&
+ derived_tables == 0 &&
lock == 0 &&
locked_tables_mode == LTM_NONE &&
m_reprepare_observer == NULL);
mdl_context.destroy();
- handler_mdl_context.destroy();
set_open_tables_state(backup);
DBUG_VOID_RETURN;
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2009-11-10 07:47:59 +0000
+++ b/sql/sql_class.h 2009-11-18 18:47:43 +0000
@@ -936,11 +936,6 @@ public:
XXX Why are internal temporary tables added to this list?
*/
TABLE *temporary_tables;
- /**
- List of tables that were opened with HANDLER OPEN and are
- still in use by this thread.
- */
- TABLE *handler_tables;
TABLE *derived_tables;
/*
During a MySQL session, one can lock tables in two modes: automatic
@@ -964,7 +959,6 @@ public:
lower level routines, which would otherwise miss that lock.
*/
MYSQL_LOCK *extra_lock;
-
/*
Enum enum_locked_tables_mode and locked_tables_mode member are
used to indicate whether the so-called "locked tables mode" is on,
@@ -1007,8 +1001,6 @@ public:
uint state_flags;
MDL_context mdl_context;
- MDL_context handler_mdl_context;
-
/**
This constructor initializes Open_tables_state instance which can only
be used as backup storage. To prepare Open_tables_state instance for
@@ -1033,13 +1025,23 @@ public:
void reset_open_tables_state(THD *thd)
{
- open_tables= temporary_tables= handler_tables= derived_tables= 0;
+ open_tables= temporary_tables= derived_tables= 0;
extra_lock= lock= 0;
locked_tables_mode= LTM_NONE;
state_flags= 0U;
m_reprepare_observer= NULL;
mdl_context.init(thd);
- handler_mdl_context.init(thd);
+ }
+ void enter_locked_tables_mode(enum_locked_tables_mode mode_arg)
+ {
+ DBUG_ASSERT(locked_tables_mode == LTM_NONE);
+ mdl_context.set_lt_or_ha_sentinel();
+ locked_tables_mode= mode_arg;
+ }
+ void leave_locked_tables_mode()
+ {
+ locked_tables_mode= LTM_NONE;
+ mdl_context.clear_lt_or_ha_sentinel();
}
};
@@ -1946,7 +1948,6 @@ public:
bool slave_thread, one_shot_set;
/* tells if current statement should binlog row-based(1) or stmt-based(0) */
bool current_stmt_binlog_row_based;
- bool some_tables_deleted;
bool last_cuted_field;
bool no_errors, password;
/**
=== modified file 'sql/sql_delete.cc'
--- a/sql/sql_delete.cc 2009-11-09 10:27:46 +0000
+++ b/sql/sql_delete.cc 2009-11-18 18:47:43 +0000
@@ -1129,8 +1129,6 @@ bool mysql_truncate(THD *thd, TABLE_LIST
Ha_global_schema_lock_guard global_schema_lock_guard(thd);
DBUG_ENTER("mysql_truncate");
- mysql_ha_rm_tables(thd, table_list);
-
bzero((char*) &create_info,sizeof(create_info));
/* Remove tables from the HANDLER's hash. */
=== modified file 'sql/sql_handler.cc'
--- a/sql/sql_handler.cc 2009-11-02 15:16:58 +0000
+++ b/sql/sql_handler.cc 2009-11-18 18:47:43 +0000
@@ -124,32 +124,19 @@ static void mysql_ha_hash_free(TABLE_LIS
static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
{
- TABLE **table_ptr;
- MDL_ticket *mdl_ticket;
- /*
- Though we could take the table pointer from hash_tables->table,
- we must follow the thd->handler_tables chain anyway, as we need the
- address of the 'next' pointer referencing this table
- for close_thread_table().
- */
- for (table_ptr= &(thd->handler_tables);
- *table_ptr && (*table_ptr != tables->table);
- table_ptr= &(*table_ptr)->next)
- ;
-
- if (*table_ptr)
+ if (tables->table && !tables->table->s->tmp_table)
{
- (*table_ptr)->file->ha_index_or_rnd_end();
- mdl_ticket= (*table_ptr)->mdl_ticket;
+ /* Non temporary table. */
+ tables->table->file->ha_index_or_rnd_end();
pthread_mutex_lock(&LOCK_open);
- if (close_thread_table(thd, table_ptr))
+ if (close_thread_table(thd, &tables->table))
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
pthread_mutex_unlock(&LOCK_open);
- thd->handler_mdl_context.release_lock(mdl_ticket);
+ thd->mdl_context.release_lock(tables->mdl_request.ticket);
}
else if (tables->table)
{
@@ -195,7 +182,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST
uint dblen, namelen, aliaslen, counter;
bool error;
TABLE *backup_open_tables;
- MDL_context backup_mdl_context;
+ MDL_ticket *mdl_savepoint;
DBUG_ENTER("mysql_ha_open");
DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
tables->db, tables->table_name, tables->alias,
@@ -265,6 +252,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST
memcpy(hash_tables->table_name, tables->table_name, namelen);
memcpy(hash_tables->alias, tables->alias, aliaslen);
hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED);
+ /* for now HANDLER can be used only for real TABLES */
+ hash_tables->required_type= FRMTYPE_TABLE;
/* add to hash */
if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
@@ -292,7 +281,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST
*/
backup_open_tables= thd->open_tables;
thd->open_tables= NULL;
- thd->mdl_context.backup_and_reset(&backup_mdl_context);
+ mdl_savepoint= thd->mdl_context.mdl_savepoint();
/*
open_tables() will set 'hash_tables->table' if successful.
@@ -300,53 +289,44 @@ bool mysql_ha_open(THD *thd, TABLE_LIST
*/
DBUG_ASSERT(! hash_tables->table);
- /* for now HANDLER can be used only for real TABLES */
- hash_tables->required_type= FRMTYPE_TABLE;
/*
We use open_tables() here, rather than, say,
open_ltable() or open_table() because we would like to be able
to open a temporary table.
*/
error= open_tables(thd, &hash_tables, &counter, 0);
- if (thd->open_tables)
+
+ if (! error &&
+ hash_tables->table &&
+ ! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
{
- if (thd->open_tables->next)
- {
- /*
- We opened something that is more than a single table.
- This happens with MERGE engine. Don't try to link
- this mess into thd->handler_tables list, close it
- and report an error. We must do it right away
- because mysql_ha_close_table(), called down the road,
- can close a single table only.
- */
- close_thread_tables(thd);
- thd->mdl_context.release_all_locks();
- my_error(ER_ILLEGAL_HA, MYF(0), hash_tables->alias);
- error= TRUE;
- }
- else
- {
- /* Merge the opened table into handler_tables list. */
- thd->open_tables->next= thd->handler_tables;
- thd->handler_tables= thd->open_tables;
- }
+ my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
+ error= TRUE;
+ }
+ if (!error &&
+ hash_tables->mdl_request.ticket &&
+ thd->mdl_context.has_lock(mdl_savepoint,
+ hash_tables->mdl_request.ticket))
+ {
+ /* The ticket returned is within a savepoint. Make a copy. */
+ error= thd->mdl_context.clone_ticket(&hash_tables->mdl_request);
+ hash_tables->table->mdl_ticket= hash_tables->mdl_request.ticket;
}
- thd->handler_mdl_context.merge(&thd->mdl_context);
-
- /* Restore the state. */
- thd->open_tables= backup_open_tables;
- thd->mdl_context.restore_from_backup(&backup_mdl_context);
-
if (error)
- goto err;
-
- /* There can be only one table in '*tables'. */
- if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
{
- my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
- goto err;
+ close_thread_tables(thd);
+ thd->open_tables= backup_open_tables;
+ thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+ if (!reopen)
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
+ else
+ hash_tables->table= NULL;
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
}
+ thd->open_tables= backup_open_tables;
+ if (hash_tables->mdl_request.ticket)
+ thd->mdl_context.move_lt_or_ha_ticket(hash_tables->mdl_request.ticket);
/*
If it's a temp table, don't reset table->query_id as the table is
@@ -358,14 +338,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST
my_ok(thd);
DBUG_PRINT("exit",("OK"));
DBUG_RETURN(FALSE);
-
-err:
- if (hash_tables->table)
- mysql_ha_close_table(thd, hash_tables);
- if (!reopen)
- my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
- DBUG_PRINT("exit",("ERROR"));
- DBUG_RETURN(TRUE);
}
@@ -497,46 +469,25 @@ retry:
hash_tables->db, hash_tables->table_name,
hash_tables->alias, table));
}
-
-#if MYSQL_VERSION_ID < 40100
- if (*tables->db && strcmp(table->table_cache_key, tables->db))
- {
- DBUG_PRINT("info",("wrong db"));
- table= NULL;
- }
-#endif
}
else
table= NULL;
if (!table)
{
-#if MYSQL_VERSION_ID < 40100
- char buff[MAX_DBKEY_LENGTH];
- if (*tables->db)
- strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name,
- NullS);
- else
- strncpy(buff, tables->alias, sizeof(buff));
- my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER");
-#else
my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
-#endif
goto err0;
}
- tables->table=table;
/* save open_tables state */
backup_open_tables= thd->open_tables;
/*
mysql_lock_tables() needs thd->open_tables to be set correctly to
- be able to handle aborts properly. When the abort happens, it's
- safe to not protect thd->handler_tables because it won't close any
- tables.
+ be able to handle aborts properly.
*/
- thd->open_tables= thd->handler_tables;
+ thd->open_tables= NULL;
- lock= mysql_lock_tables(thd, &tables->table, 1, 0, &need_reopen);
+ lock= mysql_lock_tables(thd, &hash_tables->table, 1, 0, &need_reopen);
/* restore previous context */
thd->open_tables= backup_open_tables;
@@ -544,12 +495,6 @@ retry:
if (need_reopen)
{
mysql_ha_close_table(thd, hash_tables);
- /*
- The lock might have been aborted, we need to manually reset
- thd->some_tables_deleted because handler's tables are closed
- in a non-standard way. Otherwise we might loop indefinitely.
- */
- thd->some_tables_deleted= 0;
goto retry;
}
@@ -557,7 +502,8 @@ retry:
goto err0; // mysql_lock_tables() printed error message already
// Always read all columns
- tables->table->read_set= &tables->table->s->all_set;
+ hash_tables->table->read_set= &hash_tables->table->s->all_set;
+ tables->table= hash_tables->table;
if (cond)
{
@@ -821,7 +767,8 @@ void mysql_ha_flush(THD *thd)
if (hash_tables->table &&
((hash_tables->table->mdl_ticket &&
hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) ||
- hash_tables->table->s->needs_reopen()))
+ (!hash_tables->table->s->tmp_table &&
+ hash_tables->table->s->needs_reopen())))
mysql_ha_close_table(thd, hash_tables);
}
=== modified file 'sql/sql_insert.cc'
--- a/sql/sql_insert.cc 2009-11-09 10:27:46 +0000
+++ b/sql/sql_insert.cc 2009-11-18 18:47:43 +0000
@@ -3658,8 +3658,7 @@ static TABLE *create_table_from_items(TH
/*
mysql_lock_tables() below should never fail with request to reopen table
since it won't wait for the table lock (we have exclusive metadata lock on
- the table) and thus can't get aborted and since it ignores other threads
- setting THD::some_tables_deleted thanks to MYSQL_LOCK_IGNORE_FLUSH.
+ the table) and thus can't get aborted.
*/
if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
MYSQL_LOCK_IGNORE_FLUSH, ¬_used)) ||
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2009-11-12 12:22:31 +0000
+++ b/sql/sql_parse.cc 2009-11-18 18:47:43 +0000
@@ -1240,8 +1240,7 @@ bool dispatch_command(enum enum_server_c
if (trans_commit_implicit(thd))
break;
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
if (check_global_access(thd,RELOAD_ACL))
break;
general_log_print(thd, command, NullS);
@@ -1271,8 +1270,7 @@ bool dispatch_command(enum enum_server_c
if (trans_commit_implicit(thd))
break;
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
my_ok(thd);
break;
}
@@ -2042,8 +2040,7 @@ mysql_execute_command(THD *thd)
goto error;
/* Close tables and release metadata locks. */
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
}
/*
@@ -3582,7 +3579,7 @@ end_with_restore_list:
if (thd->options & OPTION_TABLE_LOCK)
{
trans_commit_implicit(thd);
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
thd->options&= ~(OPTION_TABLE_LOCK);
}
if (thd->global_read_lock)
@@ -3639,7 +3636,13 @@ end_with_restore_list:
if (trans_commit_implicit(thd))
goto error;
/* release transactional metadata locks. */
- thd->mdl_context.release_all_locks();
+ /*
+ As of 5.6, entering LOCK TABLES mode
+ implicitly closes all open HANDLERs. This is
+ to avoid deadlocks.
+ */
+ mysql_ha_cleanup(thd);
+ thd->mdl_context.commit_transaction();
if (lex->protect_against_global_read_lock &&
wait_if_global_read_lock(thd, 0, 1))
goto error;
@@ -3675,7 +3678,7 @@ end_with_restore_list:
*/
close_thread_tables(thd);
DBUG_ASSERT(!thd->locked_tables_mode);
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
thd->options&= ~(OPTION_TABLE_LOCK);
}
else
@@ -4168,8 +4171,7 @@ end_with_restore_list:
thd->locked_tables_mode == LTM_LOCK_TABLES);
if (trans_commit(thd))
goto error;
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
/* Begin transaction with the same isolation level. */
if (lex->tx_chain && trans_begin(thd))
goto error;
@@ -4184,8 +4186,7 @@ end_with_restore_list:
thd->locked_tables_mode == LTM_LOCK_TABLES);
if (trans_rollback(thd))
goto error;
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
/* Begin transaction with the same isolation level. */
if (lex->tx_chain && trans_begin(thd))
goto error;
@@ -4582,8 +4583,7 @@ create_sp_error:
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (sp_automatic_privileges && !opt_noacl &&
@@ -4731,15 +4731,13 @@ create_sp_error:
case SQLCOM_XA_COMMIT:
if (trans_xa_commit(thd))
goto error;
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
my_ok(thd);
break;
case SQLCOM_XA_ROLLBACK:
if (trans_xa_rollback(thd))
goto error;
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
my_ok(thd);
break;
case SQLCOM_XA_RECOVER:
@@ -4896,8 +4894,7 @@ finish:
trans_commit_implicit(thd);
/* Close tables and release metadata locks. */
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
thd->stmt_da->can_overwrite_status= FALSE;
}
@@ -6891,7 +6888,9 @@ bool reload_acl_and_cache(THD *thd, ulon
}
#endif /*HAVE_QUERY_CACHE*/
- DBUG_ASSERT(!thd || thd->locked_tables_mode || !thd->mdl_context.has_locks());
+ DBUG_ASSERT(!thd || thd->locked_tables_mode ||
+ !thd->mdl_context.has_locks() ||
+ thd->handler_tables_hash.records);
/*
Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too
=== modified file 'sql/sql_plist.h'
--- a/sql/sql_plist.h 2009-03-04 13:31:31 +0000
+++ b/sql/sql_plist.h 2009-11-18 18:47:43 +0000
@@ -71,6 +71,36 @@ public:
first= a;
*B::prev_ptr(a)= &first;
}
+ inline void push_back(T *a)
+ {
+ insert_after(back(), a);
+ }
+ inline T *back()
+ {
+ T *t= front();
+ if (t)
+ {
+ while (*B::next_ptr(t))
+ t= *B::next_ptr(t);
+ }
+ return t;
+ }
+ inline void insert_after(T *pos, T *a)
+ {
+ if (pos == NULL)
+ push_front(a);
+ else
+ {
+ *B::next_ptr(a)= *B::next_ptr(pos);
+ *B::prev_ptr(a)= B::next_ptr(pos);
+ *B::next_ptr(pos)= a;
+ if (*B::next_ptr(a))
+ {
+ T *old_next= *B::next_ptr(a);
+ *B::prev_ptr(old_next)= B::next_ptr(a);
+ }
+ }
+ }
inline void remove(T *a)
{
T *next= *B::next_ptr(a);
@@ -78,8 +108,8 @@ public:
*B::prev_ptr(next)= *B::prev_ptr(a);
**B::prev_ptr(a)= next;
}
- inline T* head() { return first; }
- inline const T *head() const { return first; }
+ inline T* front() { return first; }
+ inline const T *front() const { return first; }
void swap(I_P_List<T,B> &rhs)
{
swap_variables(T *, first, rhs.first);
@@ -106,6 +136,7 @@ class I_P_List_iterator
T *current;
public:
I_P_List_iterator(I_P_List<T, B> &a) : list(&a), current(a.first) {}
+ I_P_List_iterator(I_P_List<T, B> &a, T* current_arg) : list(&a), current(current_arg) {}
inline void init(I_P_List<T, B> &a)
{
list= &a;
@@ -118,6 +149,10 @@ public:
current= *B::next_ptr(current);
return result;
}
+ inline T* next()
+ {
+ return *B::next_ptr(current);
+ }
inline void rewind()
{
current= list->first;
=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc 2009-11-09 10:27:46 +0000
+++ b/sql/sql_prepare.cc 2009-11-18 18:47:43 +0000
@@ -3194,8 +3194,7 @@ bool Prepared_statement::prepare(const c
Marker used to release metadata locks acquired while the prepared
statement is being checked.
*/
- if (thd->in_multi_stmt_transaction())
- mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ mdl_savepoint= thd->mdl_context.mdl_savepoint();
/*
The only case where we should have items in the thd->free_list is
@@ -3221,13 +3220,11 @@ bool Prepared_statement::prepare(const c
lex_end(lex);
cleanup_stmt();
/*
- If not inside a multi-statement transaction, the metadata locks have
- already been released and the rollback_to_savepoint is a nop.
- Otherwise, release acquired locks -- a NULL mdl_savepoint means that
- all locks are going to be released or that the transaction didn't
- own any locks.
+ If not inside a multi-statement transaction, the metadata
+ locks have already been released and our savepoint pointer
+ is dead beef.
*/
- if (!thd->locked_tables_mode)
+ if (thd->in_multi_stmt_transaction())
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
thd->restore_backup_statement(this, &stmt_backup);
thd->stmt_arena= old_stmt_arena;
=== modified file 'sql/sql_servers.cc'
--- a/sql/sql_servers.cc 2009-11-02 15:11:43 +0000
+++ b/sql/sql_servers.cc 2009-11-18 18:47:43 +0000
@@ -247,8 +247,7 @@ bool servers_reload(THD *thd)
end:
trans_commit_implicit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
DBUG_PRINT("info", ("unlocking servers_cache"));
rw_unlock(&THR_LOCK_servers);
DBUG_RETURN(return_val);
=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc 2009-11-12 12:22:31 +0000
+++ b/sql/sql_table.cc 2009-11-18 18:47:43 +0000
@@ -4719,8 +4719,7 @@ static bool mysql_admin_table(THD* thd,
trans_commit_stmt(thd);
trans_commit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
lex->reset_query_tables_list(FALSE);
table->table=0; // For query cache
if (protocol->write())
@@ -4772,8 +4771,7 @@ static bool mysql_admin_table(THD* thd,
trans_rollback_stmt(thd);
trans_rollback(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
tmp_disable_binlog(thd); // binlogging is done by caller if wanted
result_code= mysql_recreate_table(thd, table);
reenable_binlog(thd);
@@ -4889,8 +4887,7 @@ send_result_message:
trans_commit_stmt(thd);
trans_commit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
DEBUG_SYNC(thd, "ha_admin_try_alter");
protocol->store(STRING_WITH_LEN("note"), system_charset_info);
protocol->store(STRING_WITH_LEN(
@@ -4916,8 +4913,7 @@ send_result_message:
trans_commit_stmt(thd);
trans_commit(thd);
close_thread_tables(thd);
- if (!thd->locked_tables_mode)
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
if (!result_code) // recreation went ok
{
/* Clear the ticket released in close_thread_tables(). */
@@ -7675,7 +7671,6 @@ end_temporary:
(ulong) (copied + deleted), (ulong) deleted,
(ulong) thd->warning_info->statement_warn_count());
my_ok(thd, copied + deleted, 0L, tmp_name);
- thd->some_tables_deleted=0;
DBUG_RETURN(FALSE);
err_new_table_cleanup:
=== modified file 'sql/transaction.cc'
--- a/sql/transaction.cc 2009-11-01 08:53:43 +0000
+++ b/sql/transaction.cc 2009-11-18 18:47:43 +0000
@@ -103,7 +103,7 @@ bool trans_begin(THD *thd, uint flags)
Release transactional metadata locks only after the
transaction has been committed.
*/
- thd->mdl_context.release_all_locks();
+ thd->mdl_context.commit_transaction();
thd->options|= OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
@@ -341,6 +341,10 @@ bool trans_savepoint(THD *thd, LEX_STRIN
Remember the last acquired lock before the savepoint was set.
This is used as a marker to only release locks acquired after
the setting of this savepoint.
+ Note: this works just fine if we're under LOCK TABLES,
+ since mdl_savepoint() is guaranteed to be beyond
+ the last locked table. This allows to release some
+ locks acquired during LOCK TABLES.
*/
newsv->mdl_savepoint = thd->mdl_context.mdl_savepoint();
@@ -388,8 +392,10 @@ bool trans_rollback_to_savepoint(THD *th
thd->transaction.savepoints= sv;
- /* Release metadata locks that were acquired during this savepoint unit. */
- if (!res && !thd->locked_tables_mode)
+ /*
+ Release metadata locks that were acquired during this savepoint unit.
+ */
+ if (!res)
thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
DBUG_RETURN(test(res));
Attachment: [text/bzr-bundle] bzr/kostja@sun.com-20091118184743-pu3vi5o2moyqj5pt.bundle