List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:November 11 2009 10:40pm
Subject:bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3700)
Bug#46224
View as plain text  
#At file:///opt/local/work/6.0-46224-1/ based on revid:kostja@stripped

 3700 Konstantin Osipov	2009-11-12
      A fix for Bug#46224 "HANDLER statements within 
      a transaction might lead to deadlocks"

    modified:
      sql/lock.cc
      sql/mdl.cc
      sql/mdl.h
      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_table.cc
=== modified file 'sql/lock.cc'
--- a/sql/lock.cc	2009-10-12 09:08:34 +0000
+++ b/sql/lock.cc	2009-11-11 22:40:17 +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/mdl.cc'
--- a/sql/mdl.cc	2009-11-09 22:43:35 +0000
+++ b/sql/mdl.cc	2009-11-11 22:40:17 +0000
@@ -219,80 +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;
-  backup->m_lt_or_ha_sentinel= m_lt_or_ha_sentinel;
-  /*
-    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;
-  m_lt_or_ha_sentinel= NULL;
-}
-
-
-/**
-  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;
-  m_lt_or_ha_sentinel= backup->m_lt_or_ha_sentinel;
-}
-
-
-/**
-  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);
-  DBUG_ASSERT(! src->m_lt_or_ha_sentinel);
-}
-
-
-/**
   Initialize a lock request.
 
   This is to be used for every lock request.
@@ -611,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
@@ -664,6 +590,9 @@ 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
 
@@ -671,13 +600,19 @@ MDL_lock::can_grant_lock(const MDL_conte
 */
 
 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;
@@ -714,6 +649,7 @@ MDL_context::try_acquire_shared_lock(MDL
   MDL_lock *lock;
   MDL_key *key= &mdl_request->key;
   MDL_ticket *ticket;
+  bool is_lt_or_ha;
 
   DBUG_ASSERT(mdl_request->is_shared() && mdl_request->ticket == NULL);
 
@@ -729,10 +665,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. */
@@ -769,7 +708,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 (is_lt_or_ha || lock->can_grant_lock(this, mdl_request->type, FALSE))
   {
     mdl_request->ticket= ticket;
     lock->granted.push_front(ticket);
@@ -792,6 +736,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.
 
@@ -855,7 +836,10 @@ 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 &&
+              ! thd_in_lock_tables(m_thd)));
 
   if (m_has_global_shared_lock)
   {
@@ -1465,8 +1449,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);
 
@@ -1637,3 +1622,52 @@ void MDL_context::release_all_locks()
 
   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)
+  {
+    /* First met the savepoint. Then ticket must be sometimes after it. */
+    if (ticket == mdl_savepoint)
+      return TRUE;
+    /* First met the ticket. */
+    if (ticket == mdl_ticket)
+      return FALSE;
+  }
+  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-11-09 22:43:35 +0000
+++ b/sql/mdl.h	2009-11-11 22:40:17 +0000
@@ -327,15 +327,12 @@ 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);
 
@@ -349,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();
@@ -356,7 +356,7 @@ public:
 
   inline MDL_ticket *mdl_savepoint()
   {
-    return m_tickets.head();
+    return m_tickets.front();
   }
 
   void set_lt_or_ha_sentinel()
@@ -369,6 +369,8 @@ public:
   {
     m_lt_or_ha_sentinel= NULL;
   }
+  void move_lt_or_ha_ticket(MDL_ticket *mdl_ticket);
+
 
   void commit_transaction();
   void release_all_locks();
@@ -387,7 +389,8 @@ private:
   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/sql_base.cc'
--- a/sql/sql_base.cc	2009-11-09 22:43:35 +0000
+++ b/sql/sql_base.cc	2009-11-11 22:40:17 +0000
@@ -1258,7 +1258,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)
@@ -2343,7 +2342,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;
@@ -2801,7 +2801,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);
@@ -4145,8 +4145,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)
-    mysql_ha_flush(thd);
+  mysql_ha_flush(thd);
 
   /*
     temporary mem_root for new .frm parsing.

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2009-10-26 14:02:26 +0000
+++ b/sql/sql_class.cc	2009-11-11 22:40:17 +0000
@@ -492,7 +492,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;
@@ -1006,6 +1006,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
@@ -1022,7 +1023,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);
@@ -1099,7 +1099,6 @@ THD::~THD()
     cleanup();
 
   mdl_context.destroy();
-  handler_mdl_context.destroy();
   ha_close_connection(this);
   mysql_audit_release(this);
   plugin_thdvar_cleanup(this);
@@ -3134,12 +3133,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-09 22:43:35 +0000
+++ b/sql/sql_class.h	2009-11-11 22:40:17 +0000
@@ -935,11 +935,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
@@ -1005,8 +1000,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
@@ -1031,13 +1024,12 @@ 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)
   {
@@ -1955,7 +1947,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-10-23 06:24:37 +0000
+++ b/sql/sql_delete.cc	2009-11-11 22:40:17 +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-10-25 13:41:27 +0000
+++ b/sql/sql_handler.cc	2009-11-11 22:40:17 +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,31 +469,13 @@ 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;
@@ -530,11 +484,9 @@ retry:
   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);
 
@@ -544,12 +496,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;
   }
 
@@ -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-10-25 13:41:27 +0000
+++ b/sql/sql_insert.cc	2009-11-11 22:40:17 +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-09 22:43:35 +0000
+++ b/sql/sql_parse.cc	2009-11-11 22:40:17 +0000
@@ -3636,6 +3636,12 @@ end_with_restore_list:
     if (trans_commit_implicit(thd))
       goto error;
     /* release transactional metadata 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))
@@ -6886,7 +6892,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-11 22:40:17 +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);

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2009-11-09 22:43:35 +0000
+++ b/sql/sql_table.cc	2009-11-11 22:40:17 +0000
@@ -7630,7 +7630,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:


Attachment: [text/bzr-bundle] bzr/kostja@sun.com-20091111224017-9xkjtftddjq1g4eb.bundle
Thread
bzr commit into mysql-6.0-codebase-bugfixing branch (kostja:3700)Bug#46224Konstantin Osipov11 Nov