List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:November 18 2009 6:47pm
Subject:bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3717)
Bug#46224
View as plain text  
#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, &not_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
Thread
bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3717)Bug#46224Konstantin Osipov18 Nov
  • Re: bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3717)Bug#46224Dmitry Lenev25 Nov
    • Re: bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3717)Bug#46224Konstantin Osipov22 Dec