List:Commits« Previous MessageNext Message »
From:dlenev Date:March 6 2008 1:27pm
Subject:bk commit into 6.0 tree (dlenev:1.2561) WL#3726
View as plain text  
Below is the list of changes that have just been committed into a local
6.0 repository of dlenev.  When dlenev does a push these changes
will be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2008-03-06 15:27:03+03:00, dlenev@stripped +6 -0
  WL#3726 "DDL locking for all metadata objects"
  
  Work in progress.
  
  Changed signatures and descriptions of functions and objects to
  follow LLD, moved some functions out of MDL subsystem to increase
  modularity, minor optimization in MDL implementation.

  sql/meta_lock.cc@stripped, 2008-03-06 15:26:55+03:00, dlenev@stripped +360 -296
    As promised moved pointer to THD from MDL_EL object to MDL_CONTEXT.
    Changed signatures and descriptions of functions and objects to
    follow LLD.
    tdc_wait_for_old_versions() and notify_thread_having_shared_lock()
    functions were moved to sql_base.cc to increase modularity.

  sql/meta_lock.h@stripped, 2008-03-06 15:26:55+03:00, dlenev@stripped +73 -13
    As promised moved pointer to THD from MDL_EL object to MDL_CONTEXT.
    Changed signatures and descriptions of functions and objects to
    follow LLD.
    tdc_wait_for_old_versions() and notify_thread_having_shared_lock()
    functions were moved to sql_base.cc to increase modularity.

  sql/mysql_priv.h@stripped, 2008-03-06 15:26:55+03:00, dlenev@stripped +1 -0
    notify_thread_having_shared_lock() function was moved from meta_lock.cc
    to sql_base.cc in order to increase modularity.

  sql/sql_base.cc@stripped, 2008-03-06 15:26:55+03:00, dlenev@stripped +108 -3
    Names of some MDL related functions were changed to follow LLD.
    tdc_wait_for_old_versions() and notify_thread_having_shared_lock()
    functions were moved from meta_lock.cc to sql_base.cc in order to
    increase modularity.

  sql/sql_class.cc@stripped, 2008-03-06 15:26:56+03:00, dlenev@stripped +4 -4
    Initialization of MDL_CONTEXT now requires pointer to THD for current
    connection.

  sql/sql_class.h@stripped, 2008-03-06 15:26:56+03:00, dlenev@stripped +4 -4
    Initialization of MDL_CONTEXT now requires pointer to THD for current
    connection.

diff -Nrup a/sql/meta_lock.cc b/sql/meta_lock.cc
--- a/sql/meta_lock.cc	2008-03-04 18:21:05 +03:00
+++ b/sql/meta_lock.cc	2008-03-06 15:26:55 +03:00
@@ -21,7 +21,12 @@
 struct MDL_CONTEXT;
 
 
-/* Class representing lock on the object. */
+/**
+   The lock context. Created internally for an acquired lock.
+   For a given name, there exists only one MDL_LOCK instance,
+   and it exists only when the lock has been granted.
+   Can be seen as an MDL subsystem's version of TABLE_SHARE.
+*/
 
 struct MDL_LOCK
 {
@@ -65,7 +70,21 @@ extern "C" uchar *mdl_locks_key(const uc
 
 
 /**
-   Init meta-data locking subsystem.
+   Initialize the metadata locking subsystem.
+
+   This function is called at server startup.
+
+   In particular, initializes the new global mutex and
+   the associated condition variable: LOCK_mdl and COND_mdl.
+   These locking primitives are implementation details of the MDL
+   subsystem and are private to it.
+
+   Note, that even though the new implementation adds acquisition
+   of a new global mutex to the execution flow of almost every SQL
+   statement, the design capitalizes on that to later save on
+   look ups in the table definition cache. This leads to reduced
+   contention overall and on LOCK_open in particular.
+   Please see the description of mdl_acquire_shared_lock() for details.
 */
 
 void mdl_init()
@@ -76,8 +95,12 @@ void mdl_init()
             mdl_locks_key, 0, 0);
 }
 
+
 /**
-   Release resources of meta-data locking subsystem.
+   Release resources of metadata locking subsystem.
+
+   Destroys the global mutex and the condition variable.
+   Called at server shutdown.
 */
 
 void mdl_destroy()
@@ -88,18 +111,33 @@ void mdl_destroy()
   hash_free(&mdl_locks);
 }
 
+
 /**
-   Initialize meta-data locking context.
+   Initialize a metadata locking context.
+
+   This is to be called when a new server connection is created.
+   This is a no-cost operation to just reset to NULL the list
+   of lock requests that this originator currently has and
+   links context to the THD object for this connection.
 */
 
-void mdl_context_init(MDL_CONTEXT *context)
+void mdl_context_init(MDL_CONTEXT *context, THD *thd)
 {
   context->locks= 0;
+  context->thd= thd;
 }
 
 
 /**
-   Destroy meta-data locking context.
+   Destroy metadata locking context.
+
+   Assumes and asserts that there are no active or pending locks
+   associated with this context at the time of the destruction.
+
+   Currently does nothing. Asserts that there are no pending
+   or satisfied lock requests. The pending locks must be released
+   prior to destruction. This is a new way to express the assertion
+   that all tables are closed before a connection is destroyed.
 */
 
 void mdl_context_destroy(MDL_CONTEXT *context)
@@ -110,6 +148,17 @@ void mdl_context_destroy(MDL_CONTEXT *co
 
 /**
    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.
+
+   QQ: Does it makes sense to hide the fact that we
+       simply copy MDL_CONTEXT contents given that
+       in Open_tables_state::set_open_tables_state()
+       copies context directly anyway.
 */
 
 void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
@@ -138,6 +187,8 @@ void mdl_context_merge(MDL_CONTEXT *dst,
 {
   MDL_EL *l;
 
+  DBUG_ASSERT(dst->thd == src->thd);
+
   if (src->locks)
   {
     for (l= src->locks; l; l= l->next_context)
@@ -153,15 +204,40 @@ void mdl_context_merge(MDL_CONTEXT *dst,
 
 
 /**
-   Initialize lock request object.
+   Initialize a lock request.
+
+   This is to be used for every lock request.
 
-   @param  mdl        Pointer to unitialized MDL_EL object
-                      (or rather memory for it).
-   @param  key        Pointer to the buffer for key for the lock request
-                      (should be at least MAX_DBNAME_LENGTH bytes)
+   Note that initialization and allocation are split
+   into two calls. This is to allow flexible memory management
+   of lock requests. Normally a lock request is stored
+   in statement memory (e.g. is a member of struct TABLE_LIST),
+   but we would also like to allow allocation of lock
+   requests in other memory roots, for example in the grant
+   subsystem, to lock privilege tables.
+
+   The MDL subsystem does not own or manage memory of lock
+   requests. Instead it assumes that the life time of every lock
+   request encloses calls to mdl_acquire_shared_lock() and
+   mdl_release_locks().
+
+   @param  mdl        Pointer to an MDL_EL object to initialize
+   @param  key_buff   Pointer to the buffer for key for the lock request
+                      (should be at least strlen(db) + strlen(name)
+                      + 2 bytes, or, if the lengths are not known,                       
                                                               MAX_DBNAME_LENGTH)
    @param  type       Id of type of object to be locked
-   @param  db         Name of database to which object belongs
-   @param  name       Name of of object
+   @param  db         Name of database to which the object belongs
+   @param  name       Name of of the object
+
+   Stores the database name, object name and the type in the key
+   buffer. Initializes mdl_el to point to the key.
+   We can't simply initialize mdl_el with type, db and name
+   by-pointer because of the underlying HASH implementation
+   requires the key to be a contiguous buffer.
+
+   Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2
+   Note that tables and views have the same lock type, since
+   they share the same name space in the SQL standard.
 */
 
 void mdl_init_lock(MDL_EL *mdl, char *key, int type, const char *db,
@@ -178,7 +254,11 @@ void mdl_init_lock(MDL_EL *mdl, char *ke
 
 
 /**
-   Allocate and initialize one request for lock for particular object.
+   Allocate and initialize one lock request.
+
+   Same as mdl_init_lock(), but allocates the lock and the key buffer
+   on a memory root. Necessary to lock ad-hoc tables, e.g.
+   mysql.* tables of grant and data dictionary subsystems.
 
    @param  type       Id of type of object to be locked
    @param  db         Name of database to which object belongs
@@ -186,7 +266,7 @@ void mdl_init_lock(MDL_EL *mdl, char *ke
    @param  root       MEM_ROOT on which object should be allocated
 
    @retval 0      Error
-   @retval non-0  Pointer to object representing lock request
+   @retval non-0  Pointer to an object representing a lock request
 */
 
 MDL_EL *mdl_alloc_lock(int type, const char *db, const char *name,
@@ -219,11 +299,20 @@ void mdl_alloc_locks(TABLE_LIST *table_l
 
 
 /**
-   Add lock request to the list of pending lock requests for current context.
+   Add a lock request to the list of lock requests of the context.
 
-   @param  context    Current MDL context.
-   @param  lock       Lock request to be added.
-   @param  exclusive  Type of lock requested
+   The procedure to acquire metadata locks is:
+     - allocate and initialize lock requests (mdl_alloc_lock())
+     - associate them with a context (mdl_add_lock())
+     - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly).
+
+   Associates a lock request with the given context.
+
+   @param  context    The MDL context to associate the lock with.
+                      There should be no more than one context per
+                      connection, to avoid deadlocks.
+   @param  lock       The lock request to be added.
+   @param  exclusive  Type of the lock requested
 */
 
 void mdl_add_lock(MDL_CONTEXT *context, MDL_EL *lock, bool exclusive)
@@ -235,7 +324,6 @@ void mdl_add_lock(MDL_CONTEXT *context, 
     lock->type= SHARED_PENDING;
   DBUG_ASSERT(!lock->ctx);
   lock->ctx= context;
-  lock->thd= current_thd;
 
   lock->next_context= context->locks;
   context->locks= lock;
@@ -244,6 +332,35 @@ void mdl_add_lock(MDL_CONTEXT *context, 
 
 
 /**
+   Clear all lock requests in the context (clear the context).
+
+   Disassociates lock requests from the context.
+   All granted locks must be released prior to calling this
+   function.
+
+   In other words, the expected procedure to release locks is:
+     - mdl_release_locks();
+     - mdl_remove_all_locks();
+
+   We could possibly merge mdl_remove_all_locks() and mdl_release_locks(),
+   but this function comes in handy when we need to back off: in that case
+   we release all the locks acquired so-far but do not free them, since
+   we know that the respective lock requests will be used again.
+
+   @param context Context to be cleared.
+*/
+
+void mdl_free_locks(MDL_CONTEXT *context)
+{
+#ifndef DBUG_OFF
+  for (MDL_EL *l= context->locks; l; l= l->next_context)
+    l->ctx= 0;
+#endif
+  context->locks= 0;
+}
+
+
+/**
    Auxiliary functions needed for creation/destruction of MDL_LOCK objects.
 
    @todo This naive implementation should be replaced with one that saves
@@ -263,18 +380,28 @@ static void release_lock_object(MDL_LOCK
 
 
 /**
-   Try to acquire shared lock on the object.
+   Try to acquire one shared lock.
+
+   Unlike exclusive locks, shared locks are acquired one by
+   one. This is interface is chosen to simplify introduction of
+   the new locking API to the system. mdl_acquire_shared_lock()
+   is currently used from open_table(), and there we have only one
+   table to work with.
+
+   In future we may consider allocating multiple shared locks at once.
+
+   This function must be called after the lock is added to a context.
 
    @param lock  Lock request object for lock to be acquired
 
-   @retval  FALSE - success
-   @retval  TRUE  - conflicting lock exists. Another attempt
+   @retval  FALSE   success
+   @retval  TRUE    a conflicting lock exists. Another attempt
                     should be made after releasing all current
                     locks and waiting for conflicting lock go
                     away (using mdl_wait_for_locks()).
 */
 
-bool mdl_acquire_lock(MDL_EL *l)
+bool mdl_acquire_shared_lock(MDL_EL *l)
 {
   MDL_LOCK *lock;
   bool result= FALSE;
@@ -324,52 +451,15 @@ bool mdl_acquire_lock(MDL_EL *l)
 
 
 /**
-   Auxiliary function that tries to wake up thread waiting on some table-level
-   lock (or insert delayed thread waiting for more inserts).
-
-   @param in_use Thread to wake up
+   Acquire exclusive locks. The context must contain the list of
+   locks to be acquired. There must be no granted locks in the
+   context.
 
-   @return TRUE  if thread was woken up
-           FALSE otherwise (e.g. it was not waiting for a table-level lock).
+   This is a replacement of lock_table_names(). It is used in
+   RENAME, DROP and other DDL SQL statements.
 
-   @note It is one of two places where border between MDL and the rest of
-         the server is broken.
-
-   TODO: Should be moved out of MDL sub-system.
-*/
-
-static bool zap_thread_having_shared_lock(THD *in_use)
-{
-  bool signalled= FALSE;
-  if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
-      !in_use->killed)
-  {
-    in_use->killed= THD::KILL_CONNECTION;
-    pthread_mutex_lock(&in_use->mysys_var->mutex);
-    if (in_use->mysys_var->current_cond)
-      pthread_cond_broadcast(in_use->mysys_var->current_cond);
-    pthread_mutex_unlock(&in_use->mysys_var->mutex);
-    signalled= TRUE;
-  }
-  pthread_mutex_lock(&LOCK_open);
-  for (TABLE *thd_table= in_use->open_tables;
-       thd_table ;
-       thd_table= thd_table->next)
-  {
-    /* TODO With new MDL check for db_stat is probably a legacy */
-    if (thd_table->db_stat)
-      signalled|= mysql_lock_abort_for_thread(current_thd, thd_table);
-  }
-  pthread_mutex_unlock(&LOCK_open);
-  return signalled;
-}
-
-
-/**
-   Acquire exclusive locks requests for which present in the context
-
-   @param context  Context containing requests for exclusive locks
-                   (and only them).
+   @param context  A context containing requests for exclusive locks
+                   The context may not have other lock requests.
 */
 
 void mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
@@ -378,16 +468,15 @@ void mdl_acquire_exclusive_locks(MDL_CON
   MDL_LOCK *lock;
   bool signalled= FALSE;
   const char *proc_info;
-  THD *thd= current_thd;
 
+  DBUG_ASSERT(context->thd == current_thd);
   /*
     FIXME: Probably we should re-use wait_for_condition() here
            and also properly handle situations in which waiting
            thread is killed.
-           We also should get current THD from MDL_CONTEXT.
   */
-  proc_info= thd->proc_info;
-  thd_proc_info(thd, "Waiting for table");
+  proc_info= context->thd->proc_info;
+  thd_proc_info(context->thd, "Waiting for table");
 
   safe_mutex_assert_not_owner(&LOCK_open);
 
@@ -421,7 +510,8 @@ void mdl_acquire_exclusive_locks(MDL_CON
 
       if ((lh= lock->active_readers))
       {
-        signalled= zap_thread_having_shared_lock(lh->thd);
+        signalled= notify_thread_having_shared_lock(context->thd,
+                                                    lh->ctx->thd);
         break;
       }
       else if (lock->active_writers || lock->active_readers_waiting_upgrade)
@@ -469,12 +559,15 @@ void mdl_acquire_exclusive_locks(MDL_CON
     lock->cached_object= NULL;
   }
   pthread_mutex_unlock(&LOCK_mdl);
-  thd_proc_info(thd, proc_info);
+  thd_proc_info(context->thd, proc_info);
 }
 
 
 /**
-   Upgrade shared meta-data lock for an object to exclusive lock.
+   Upgrade a shared metadata lock to exclusive.
+
+   Used in ALTER TABLE, when a copy of the table with the
+   new definition has been constructed.
 
    @param context Context to which shared long belongs
    @param type    Id of object type
@@ -494,6 +587,8 @@ void mdl_upgrade_shared_lock_to_exclusiv
   DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive");
   DBUG_PRINT("enter", ("db=%s name=%s", db, name));
 
+  DBUG_ASSERT(context->thd == current_thd);
+
   int4store(key, type);
   key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
 
@@ -532,7 +627,8 @@ void mdl_upgrade_shared_lock_to_exclusiv
         if ((lh= lock->active_readers))
         {
           DBUG_PRINT("info", ("found active readers"));
-          signalled= zap_thread_having_shared_lock(lh->thd);
+          signalled= notify_thread_having_shared_lock(context->thd,
+                                                      lh->ctx->thd);
           break;
         }
         else if (lock->active_writers)
@@ -586,91 +682,64 @@ void mdl_upgrade_shared_lock_to_exclusiv
 
 
 /**
-   Auxiliary function which allows to release particular lock
-   owbership of which is represented by lock request object.
-*/
-
-static void release_lock(MDL_EL *l)
-{
-  MDL_EL **lp;
-  MDL_LOCK *lock;
-
-  DBUG_ENTER("release_lock");
-  DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4,
-                        l->key + 4 + strlen(l->key + 4) + 1));
-
-  lock= l->lock;
-  if (lock->has_no_other_users())
-  {
-    hash_delete(&mdl_locks, (uchar *)lock);
-    DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
-                        lock->cached_object));
-    if (lock->cached_object)
-      (*lock->cached_object_release_hook)(l->lock->cached_object);
-    release_lock_object(lock);
-  }
-  else
-  {
-    switch (l->type)
-    {
-      case SHARED:
-        lp= &(lock->active_readers);
-        break;
-      case EXCLUSIVE_PENDING:
-        lp= &(lock->waiting_writers);
-        break;
-      case EXCLUSIVE:
-        lp= &(lock->active_writers);
-        break;
-      default:
-        /* TODO Really? How about problems during lock upgrade ? */
-        DBUG_ASSERT(0);
-    }
-    while (*lp != l)
-      lp= &((*lp)->next_lock);
-    *lp= (*lp)->next_lock;
-    lock->users--;
-  }
+   Try to acquire an exclusive lock on the object if there are
+   no conflicting locks.
 
-  DBUG_VOID_RETURN;
-}
+   Similar to the previous function, but returns
+   immediately without any side effect if encounters a lock
+   conflict. Otherwise takes the lock.
+
+   This function is used in CREATE TABLE ... LIKE to acquire a lock
+   on the table to be created. In this statement we don't want to
+   block and wait for the lock if the table already exists.
 
+   @param context The context containing the lock request
+   @param lock    The lock request
 
-/**
-   Release meta-data locks associated with context, but leave them in the
-   context as lock requests.
+   @retval FALSE the lock was granted
+   @retval TRUE  there were conflicting locks.
 
-   @param context Context with which locks to be released are associated.
+   FIXME: Compared to lock_table_name_if_not_cached()
+          it gives sligthly more false negatives.
 */
 
-void mdl_release_locks(MDL_CONTEXT *context)
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_EL *l)
 {
-  MDL_EL *l;
-  DBUG_ENTER("mdl_release_locks");
+  MDL_LOCK *lock;
+
+  DBUG_ASSERT(l->type == EXCLUSIVE_PENDING);
 
   safe_mutex_assert_not_owner(&LOCK_open);
 
   pthread_mutex_lock(&LOCK_mdl);
-  for (l= context->locks; l; l= l->next_context)
+  if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key,
l->key_length)))
   {
-    DBUG_PRINT("info", ("found lock to release l=%p", l));
-    /*
-      We should not release locks which we have not obtained. Allows us
-      to avoid problems in open_tables() in case of back-off
-    */
-    if (l->type != SHARED_PENDING)
-    {
-      release_lock(l);
-      l->type= SHARED_PENDING;
-#ifndef DBUG_OFF
-      l->lock= 0;
-#endif
-    }
+    lock= get_lock_object();
+    lock->active_writers= l;
+    lock->users= 1;
+    my_hash_insert(&mdl_locks, (uchar*)lock);
+    l->type= EXCLUSIVE;
+    l->next_lock= 0;
+    l->lock= lock;
+    lock= 0;
   }
-  /* Inefficient but will do for a while */
-  pthread_cond_broadcast(&COND_mdl);
   pthread_mutex_unlock(&LOCK_mdl);
-  DBUG_VOID_RETURN;
+
+  /*
+    FIXME: We can't leave EXCLUSIVE_PENDING locks in the list since for such
+           locks we assume that they have MDL_EL::lock properly set.
+           Long term we should clearly define relation between lock types,
+           presence in the context lists and MDL_EL::lock values.
+  */
+  if (lock)
+  {
+    MDL_EL **lp;
+    for (lp= &context->locks; *lp != l; lp= &((*lp)->next_context))
+      continue;
+    *lp= l->next_context;
+  }
+
+  return lock;
 }
 
 
@@ -678,23 +747,29 @@ void mdl_release_locks(MDL_CONTEXT *cont
    Wait until there will be no locks that conflict with lock requests
    in the context.
 
+   This is a part of the locking protocol and must be used by the
+   acquirer of shared locks after a back-off.
+
+   Does not acquire the locks!
+
    @param context Context with which lock requests are associated.
 */
 
-void mdl_wait_for_locks(THD *thd, MDL_CONTEXT *context)
+void mdl_wait_for_locks(MDL_CONTEXT *context)
 {
   MDL_EL *l;
   MDL_LOCK *lock;
   const char *proc_info;
 
   safe_mutex_assert_not_owner(&LOCK_open);
+  DBUG_ASSERT(context->thd == current_thd);
 
   /*
     FIXME: Reuse wait_for_condition() here and handle situations
            when thread is killed.
   */
-  proc_info= thd->proc_info;
-  thd_proc_info(thd, "Waiting for table");
+  proc_info= context->thd->proc_info;
+  thd_proc_info(context->thd, "Waiting for table");
 
   while (1)
   {
@@ -708,7 +783,7 @@ void mdl_wait_for_locks(THD *thd, MDL_CO
       TODO: investigate situations in which we need to broadcast on
             COND_mdl because of above scenario.
     */
-    mysql_ha_flush(thd);
+    mysql_ha_flush(context->thd);
     pthread_mutex_lock(&LOCK_mdl);
     for (l= context->locks; l; l= l->next_context)
     {
@@ -726,139 +801,107 @@ void mdl_wait_for_locks(THD *thd, MDL_CO
     pthread_cond_wait(&COND_mdl, &LOCK_mdl);
     pthread_mutex_unlock(&LOCK_mdl);
   }
-  thd_proc_info(thd, proc_info);
+  thd_proc_info(context->thd, proc_info);
 }
 
 
 /**
-   Wait until there will be old versions of tables which correspond
-   to the lock requests in the context will go away.
-
-   @param context Context containing lock request object for tables
-                  to be waited for.
-
-   @note It is one of two places where border between MDL and the rest
-         of the server is broken.
+   Auxiliary function which allows to release particular lock
+   ownership of which is represented by lock request object.
 */
 
-void mdl_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+static void release_lock(MDL_EL *l)
 {
-  MDL_EL *l;
-  TABLE_SHARE *share;
-  const char *proc_info;
+  MDL_EL **lp;
+  MDL_LOCK *lock;
 
-  /*
-    FIXME: Reuse wait_for_condition() here and handle situations
-           when thread is killed.
-  */
-  proc_info= thd->proc_info;
-  thd_proc_info(thd, "Waiting for table");
+  DBUG_ENTER("release_lock");
+  DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4,
+                        l->key + 4 + strlen(l->key + 4) + 1));
 
-  while (1)
+  lock= l->lock;
+  if (lock->has_no_other_users())
   {
-    /*
-      Here we have situation as in mdl_wait_for_locks() we need to
-      get rid of offending HANDLERs to avoid deadlock.
-      TODO: We should also investigate in which situations we have
-            to broadcast on COND_refresh because of this.
-    */
-    mysql_ha_flush(thd);
-    pthread_mutex_lock(&LOCK_open);
-    for (l= context->locks; l; l= l->next_context)
+    hash_delete(&mdl_locks, (uchar *)lock);
+    DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
+                        lock->cached_object));
+    if (lock->cached_object)
+      (*lock->cached_object_release_hook)(l->lock->cached_object);
+    release_lock_object(lock);
+  }
+  else
+  {
+    switch (l->type)
     {
-      char *key= l->key +4;
-      uint key_length= l->key_length - 4;
-      if ((share= (TABLE_SHARE*) hash_search(&table_def_cache, (uchar*) key,
-                                             key_length)) &&
-          share->version != refresh_version &&
-          share->used_tables)
+      case SHARED:
+        lp= &(lock->active_readers);
         break;
+      case EXCLUSIVE_PENDING:
+        lp= &(lock->waiting_writers);
+        break;
+      case EXCLUSIVE:
+        lp= &(lock->active_writers);
+        break;
+      default:
+        /* TODO Really? How about problems during lock upgrade ? */
+        DBUG_ASSERT(0);
     }
-    if (!l)
-    {
-      pthread_mutex_unlock(&LOCK_open);
-      break;
-    }
-    pthread_cond_wait(&COND_refresh, &LOCK_open);
-    pthread_mutex_unlock(&LOCK_open);
+    while (*lp != l)
+      lp= &((*lp)->next_lock);
+    *lp= (*lp)->next_lock;
+    lock->users--;
   }
-  thd_proc_info(thd, proc_info);
-}
-
-
-/**
-   Release all lock requests in the context (clear context).
-
-   @param context Context with lock requests to be released.
-*/
 
-void mdl_free_locks(MDL_CONTEXT *context)
-{
-#ifndef DBUG_OFF
-  for (MDL_EL *l= context->locks; l; l= l->next_context)
-    l->ctx= 0;
-#endif
-  context->locks= 0;
+  DBUG_VOID_RETURN;
 }
 
 
 /**
-   Try to acquire exclusive lock on the object if there are
-   no conflicting locks.
+   Release all locks associated with the context, but leave them
+   in the context as lock requests.
 
-   @param context Context containing lock request
-   @param lock    MDL_EL object representing lock
-                  request for lock to be taken
+   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.
 
-   FIXME: Compared to lock_table_name_if_not_cached() it gives
-          more false negatives.
-
-   @return FALSE - if lock was successfully taken, TRUE - if there
-           were conflicting locks.
+   @param context The context with which the locks to be released
+                  are associated.
 */
 
-bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_EL *l)
+void mdl_release_locks(MDL_CONTEXT *context)
 {
-  MDL_LOCK *lock;
-
-  DBUG_ASSERT(l->type == EXCLUSIVE_PENDING);
+  MDL_EL *l;
+  DBUG_ENTER("mdl_release_locks");
 
   safe_mutex_assert_not_owner(&LOCK_open);
 
   pthread_mutex_lock(&LOCK_mdl);
-  if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key,
l->key_length)))
+  for (l= context->locks; l; l= l->next_context)
   {
-    lock= get_lock_object();
-    lock->active_writers= l;
-    lock->users= 1;
-    my_hash_insert(&mdl_locks, (uchar*)lock);
-    l->type= EXCLUSIVE;
-    l->next_lock= 0;
-    l->lock= lock;
-    lock= 0;
+    DBUG_PRINT("info", ("found lock to release l=%p", l));
+    /*
+      We should not release locks which we have not obtained. Allows us
+      to avoid problems in open_tables() in case of back-off
+    */
+    if (l->type != SHARED_PENDING)
+    {
+      release_lock(l);
+      l->type= SHARED_PENDING;
+#ifndef DBUG_OFF
+      l->lock= 0;
+#endif
+    }
   }
+  /* Inefficient but will do for a while */
+  pthread_cond_broadcast(&COND_mdl);
   pthread_mutex_unlock(&LOCK_mdl);
-
-  /*
-    FIXME: We can't leave EXCLUSIVE_PENDING locks in the list since for such
-           locks we assume that they have MDL_EL::lock properly set.
-           Long term we should clearly define relation between lock types,
-           presence in the context lists and MDL_EL::lock values.
-  */
-  if (lock)
-  {
-    MDL_EL **lp;
-    for (lp= &context->locks; *lp != l; lp= &((*lp)->next_context))
-      continue;
-    *lp= l->next_context;
-  }
-
-  return lock;
+  DBUG_VOID_RETURN;
 }
 
 
 /**
    Release all exclusive locks associated with context.
+   Removes the locks from the context.
 
    @param context Context with exclusive locks.
 
@@ -892,9 +935,38 @@ void mdl_release_exclusive_locks(MDL_CON
 
 
 /**
-   Downgrade all exclusive lock in the context to shared
+   Release a lock.
+   Removes the lock from the context.
 
-   @param context Context with exclusive locks.
+   @param context Context containing lock in question
+   @param lock    Lock to be released
+*/
+
+void mdl_release_lock(MDL_CONTEXT *context, MDL_EL *lr)
+{
+  MDL_EL **l;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  for (l= &context->locks; *l != lr; l= &((*l)->next_context))
+    continue;
+  release_lock(*l);
+#ifndef DBUG_OFF
+  (*l)->ctx= 0;
+  (*l)->lock= 0;
+#endif
+  *l= (*l)->next_context;
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Downgrade all exclusive locks in the context to
+   shared.
+
+   @param context A context with exclusive locks.
 */
 
 void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context)
@@ -923,33 +995,6 @@ void mdl_downgrade_exclusive_locks(MDL_C
 
 
 /**
-   Release particular meta-data lock.
-
-   @param context Context containing lock in question
-   @param lock    Lock to be released
-*/
-
-void mdl_release_lock(MDL_CONTEXT *context, MDL_EL *lr)
-{
-  MDL_EL **l;
-
-  safe_mutex_assert_not_owner(&LOCK_open);
-
-  pthread_mutex_lock(&LOCK_mdl);
-  for (l= &context->locks; *l != lr; l= &((*l)->next_context))
-    continue;
-  release_lock(*l);
-#ifndef DBUG_OFF
-  (*l)->ctx= 0;
-  (*l)->lock= 0;
-#endif
-  *l= (*l)->next_context;
-  pthread_cond_broadcast(&COND_mdl);
-  pthread_mutex_unlock(&LOCK_mdl);
-}
-
-
-/**
    Auxiliary function which allows to check if we have exclusive lock
    on the object.
 
@@ -995,29 +1040,31 @@ bool mdl_has_pending_conflicting_lock(MD
 
 
 /**
-   @brief Get pointer to opaque object associated with lock.
-
-   @param  l Lock request for the lock with which object is associated.
+   Associate pointer to an opaque object with a lock.
 
-   @return Pointer to opaque object associated with lock.
-*/
-
-void* mdl_get_cached_object(MDL_EL *l)
-{
-  DBUG_ASSERT(l->type == SHARED || l->type == EXCLUSIVE ||
-              l->type == SHARED_PENDING_UPGRADE);
-  return l->lock->cached_object;
-}
-
-
-/**
-   @brief Associate pointer to opaque object with lock.
-
-   @param l             Lock request for the lock with which object should
-                        be associated.
+   @param l             Lock request for the lock with which the
+                        object should be associated.
    @param cached_object Pointer to the object
    @param release_hook  Cleanup function to be called when MDL subsystem
                         decides to remove lock or associate another object.
+
+   This is used to cache a pointer to TABLE_SHARE in the lock
+   structure. Such caching can save one acquisition of LOCK_open
+   and one table definition cache lookup for every table.
+
+   Since the pointer may be stored only inside an acquired lock,
+   the caching is only effective when there is more than one lock
+   granted on a given table.
+
+   This function has the following usage pattern:
+     - try to acquire an MDL lock
+     - when done, call for mdl_get_cached_object(). If it returns NULL, our
+       thread has the only lock on this table.
+     - look up TABLE_SHARE in the table definition cache
+     - call mdl_set_cache_object() to assign the share to the opaque pointer.
+
+  The release hook is invoked when the last shared metadata
+  lock on this name is released.
 */
 
 void mdl_set_cached_object(MDL_EL *l, void *cached_object,
@@ -1043,4 +1090,21 @@ void mdl_set_cached_object(MDL_EL *l, vo
   l->lock->cached_object_release_hook= release_hook;
 
   DBUG_VOID_RETURN;
+}
+
+
+/**
+   Get a pointer to an opaque object that associated with the lock.
+
+   @param  l Lock request for the lock with which the object is
+             associated.
+
+   @return Pointer to an opaque object associated with the lock.
+*/
+
+void* mdl_get_cached_object(MDL_EL *l)
+{
+  DBUG_ASSERT(l->type == SHARED || l->type == EXCLUSIVE ||
+              l->type == SHARED_PENDING_UPGRADE);
+  return l->lock->cached_object;
 }
diff -Nrup a/sql/meta_lock.h b/sql/meta_lock.h
--- a/sql/meta_lock.h	2008-03-04 18:21:05 +03:00
+++ b/sql/meta_lock.h	2008-03-06 15:26:55 +03:00
@@ -18,12 +18,15 @@
 struct MDL_EL;
 struct MDL_LOCK;
 
-
-/* Class representing owner/requestor of meta-data lock. */
+/**
+   Context of the owner of metadata locks. I.e. each server
+   connection has such a context.
+*/
 
 struct MDL_CONTEXT
 {
   MDL_EL   *locks;
+  THD      *thd;
 };
 
 
@@ -36,11 +39,16 @@ enum enum_mdl_type {SHARED_PENDING=0, SH
                     SHARED_PENDING_UPGRADE, EXCLUSIVE};
 
 
-/*
-  Class that represents request for a lock (pending or satisified).
 
-  TODO: By using double linked lists instead of single linked lists
-        we can make MDL code more simple and efficient.
+/**
+   A pending lock request or a granted metadata lock. A lock is requested
+   or granted based on a fully qualified name and type. E.g. for a table
+   the key consists of <0 (=table)>+<database name>+<table name>.
+   Later in this document this triple will be referred to simply as
+   "key" or "name".
+
+   TODO: By using double linked lists instead of single linked lists
+         we can make MDL code more simple and efficient.
 */
 
 struct MDL_EL
@@ -59,36 +67,39 @@ struct MDL_EL
   */
   MDL_LOCK    *lock;
   MDL_CONTEXT *ctx;
-  /* FIXME Belongs to MDL_CONTEXT */
-  THD         *thd;
 };
 
 
 void mdl_init();
 void mdl_destroy();
-void mdl_context_init(MDL_CONTEXT *context);
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd);
 void mdl_context_destroy(MDL_CONTEXT *context);
 void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
 void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
 void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source);
+
 void mdl_init_lock(MDL_EL *mdl, char *key, int type, const char *db,
                    const char *name);
 MDL_EL *mdl_alloc_lock(int type, const char *db, const char *name,
                        MEM_ROOT *root);
 void mdl_alloc_locks(TABLE_LIST *table_list, MEM_ROOT *root);
 void mdl_add_lock(MDL_CONTEXT *context, MDL_EL *lock, bool exclusive);
-bool mdl_acquire_lock(MDL_EL *lock);
+void mdl_free_locks(MDL_CONTEXT *context);
+
+bool mdl_acquire_shared_lock(MDL_EL *lock);
 void mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
 void mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
                                           const char *db, const char *name);
 bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_EL *lock);
+
+void mdl_wait_for_locks(MDL_CONTEXT *context);
+
 void mdl_release_locks(MDL_CONTEXT *context);
-void mdl_wait_for_locks(THD *thd, MDL_CONTEXT *context);
-void mdl_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
-void mdl_free_locks(MDL_CONTEXT *context);
 void mdl_release_exclusive_locks(MDL_CONTEXT *context);
 void mdl_release_lock(MDL_CONTEXT *context, MDL_EL *lock);
 void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context);
+
 bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db,
                                  const char *name);
 bool mdl_has_pending_conflicting_lock(MDL_EL *l);
@@ -97,6 +108,55 @@ inline bool mdl_has_non_freed_locks(MDL_
 {
   return context->locks;
 }
+
+
+/**
+   Get first lock request in the context (i.e. get initial
+   value for iterating over all lock requests in the context).
+
+   TODO: Try to get rid of mdl_get_first_lock()/mdl_get_next_lock()
+         by implementing new intrusive list and iterator templates
+         (current I_List implementation does not allow its elements
+         to participate in several intrusive lists).
+*/
+
+inline MDL_EL *mdl_get_first_lock(MDL_CONTEXT *ctx)
+{
+  return ctx->locks;
+}
+
+
+/**
+   Given the current lock request get next lock request in the context.
+*/
+
+inline MDL_EL *mdl_get_next_lock(MDL_CONTEXT *ctx, MDL_EL *current)
+{
+  return current->next_context;
+}
+
+
+/**
+   Give metadata lock request object for the table get table definition
+   cache key corresponding to it.
+
+   @param l   [in]  Lock request object for the table.
+   @param key [out] LEX_STRING object where table definition cache key
+                    should be put.
+
+   @note This key will have the same life-time as this lock request object.
+
+   @note This is yet another place where border between MDL subsystem and the
+         rest of the server is broken. OTOH it allows to save some CPU cycles
+         and memory by avoiding generating these TDC keys from table list.
+*/
+
+inline void mdl_get_tdc_key(MDL_EL *l, LEX_STRING *key)
+{
+  key->str= l->key + 4;
+  key->length= l->key_length - 4;
+}
+
 
 typedef void (* mdl_cached_object_release_hook)(void *);
 void* mdl_get_cached_object(MDL_EL *l);
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2008-03-04 18:21:05 +03:00
+++ b/sql/mysql_priv.h	2008-03-06 15:26:55 +03:00
@@ -1595,6 +1595,7 @@ uint prep_alter_part_table(THD *thd, TAB
 #define RTFC_CHECK_KILLED_FLAG      0x0004
 bool remove_table_from_cache(THD *thd, const char *db, const char *table,
                              uint flags);
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use);
 void expel_table_from_cache(THD *leave_thd, const char *db,
                             const char *table_name);
 
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2008-03-04 18:21:06 +03:00
+++ b/sql/sql_base.cc	2008-03-06 15:26:55 +03:00
@@ -109,6 +109,7 @@ static bool auto_repair_table(THD *thd, 
 static void free_cache_entry(TABLE *entry);
 static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
                                  bool send_refresh);
+static void tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
 static bool
 has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables);
 
@@ -2756,7 +2757,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   }
   else
   {
-    if (mdl_acquire_lock(mdl))
+    if (mdl_acquire_shared_lock(mdl))
     {
       if (action)
         *action= OT_BACK_OFF_AND_RETRY;
@@ -4111,8 +4112,8 @@ static bool handle_failed_open_table_att
   switch (action)
   {
     case OT_BACK_OFF_AND_RETRY:
-      mdl_wait_for_locks(thd, &thd->mdl_context);
-      mdl_wait_for_old_versions(thd, &thd->mdl_context);
+      mdl_wait_for_locks(&thd->mdl_context);
+      tdc_wait_for_old_versions(thd, &thd->mdl_context);
       mdl_free_locks(&thd->mdl_context);
       break;
     case OT_DISCOVER:
@@ -8490,6 +8491,58 @@ bool remove_table_from_cache(THD *thd, c
 
 
 /**
+   A callback to the server internals that is used to address
+   special cases of the locking protocol.
+   Invoked when acquiring an exclusive lock, for each thread that
+   has a conflicting shared metadata lock.
+
+   This function:
+     - aborts waiting of the thread on a data lock, to make it notice
+       the pending exclusive lock and back off.
+     - if the thread is an INSERT DELAYED thread, sends it a KILL
+       signal to terminate it.
+
+   @note This function does not wait for the thread to give away its
+         locks. Waiting is done outside for all threads at once.
+
+   @param thd    Current thread context
+   @param in_use The thread to wake up
+
+   @retval  TRUE  if the thread was woken up
+   @retval  FALSE otherwise (e.g. it was not waiting for a table-level lock).
+
+   @note It is one of two places where border between MDL and the
+         rest of the server is broken.
+*/
+
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use)
+{
+  bool signalled= FALSE;
+  if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+      !in_use->killed)
+  {
+    in_use->killed= THD::KILL_CONNECTION;
+    pthread_mutex_lock(&in_use->mysys_var->mutex);
+    if (in_use->mysys_var->current_cond)
+      pthread_cond_broadcast(in_use->mysys_var->current_cond);
+    pthread_mutex_unlock(&in_use->mysys_var->mutex);
+    signalled= TRUE;
+  }
+  pthread_mutex_lock(&LOCK_open);
+  for (TABLE *thd_table= in_use->open_tables;
+       thd_table ;
+       thd_table= thd_table->next)
+  {
+    /* TODO With new MDL check for db_stat is probably a legacy */
+    if (thd_table->db_stat)
+      signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+  }
+  pthread_mutex_unlock(&LOCK_open);
+  return signalled;
+}
+
+
+/**
    Remove all instances of the table from cache assuming that current thread
    has exclusive meta-data lock on it (optionally leave instances belonging
    to the current thread in cache).
@@ -8539,6 +8592,58 @@ void expel_table_from_cache(THD *leave_t
   }
 }
 
+
+/**
+   Wait until there are no old versions of tables in the table
+   definition cache for the metadata locks that we try to acquire.
+
+   @param thd      Thread context
+   @param context  Metadata locking context with locks.
+*/
+
+static void tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+{
+  MDL_EL *l;
+  TABLE_SHARE *share;
+  const char *proc_info;
+  LEX_STRING key;
+
+  /*
+    FIXME: Reuse wait_for_condition() here and handle situations
+           when thread is killed.
+  */
+  proc_info= thd->proc_info;
+  thd_proc_info(thd, "Waiting for table");
+
+  while (1)
+  {
+    /*
+      Here we have situation as in mdl_wait_for_locks() we need to
+      get rid of offending HANDLERs to avoid deadlock.
+      TODO: We should also investigate in which situations we have
+            to broadcast on COND_refresh because of this.
+    */
+    mysql_ha_flush(thd);
+    pthread_mutex_lock(&LOCK_open);
+    for (l= mdl_get_first_lock(context); l; l= mdl_get_next_lock(context, l))
+    {
+      mdl_get_tdc_key(l, &key);
+      if ((share= (TABLE_SHARE*) hash_search(&table_def_cache, (uchar*) key.str,
+                                             key.length)) &&
+          share->version != refresh_version &&
+          share->used_tables)
+        break;
+    }
+    if (!l)
+    {
+      pthread_mutex_unlock(&LOCK_open);
+      break;
+    }
+    pthread_cond_wait(&COND_refresh, &LOCK_open);
+    pthread_mutex_unlock(&LOCK_open);
+  }
+  thd_proc_info(thd, proc_info);
+}
 
 
 int setup_ftfuncs(SELECT_LEX *select_lex)
diff -Nrup a/sql/sql_class.cc b/sql/sql_class.cc
--- a/sql/sql_class.cc	2008-02-18 17:31:34 +03:00
+++ b/sql/sql_class.cc	2008-03-06 15:26:56 +03:00
@@ -197,10 +197,10 @@ bool foreign_key_prefix(Key *a, Key *b)
 ** Thread specific functions
 ****************************************************************************/
 
-Open_tables_state::Open_tables_state(ulong version_arg)
+Open_tables_state::Open_tables_state(THD *thd, ulong version_arg)
   :version(version_arg), state_flags(0U)
 {
-  reset_open_tables_state();
+  reset_open_tables_state(thd);
 }
 
 /*
@@ -481,7 +481,7 @@ Diagnostics_area::disable_status()
 THD::THD()
    :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION,
               /* statement id */ 0),
-   Open_tables_state(refresh_version), rli_fake(0),
+   Open_tables_state(this, refresh_version), rli_fake(0),
    lock_id(&main_lock_id),
    user_time(0), in_sub_stmt(0),
    binlog_table_maps(0), binlog_flags(0UL),
@@ -2767,7 +2767,7 @@ void THD::reset_n_backup_open_tables_sta
 {
   DBUG_ENTER("reset_n_backup_open_tables_state");
   backup->set_open_tables_state(this);
-  reset_open_tables_state();
+  reset_open_tables_state(this);
   state_flags|= Open_tables_state::BACKUPS_AVAIL;
   DBUG_VOID_RETURN;
 }
diff -Nrup a/sql/sql_class.h b/sql/sql_class.h
--- a/sql/sql_class.h	2008-02-18 17:31:34 +03:00
+++ b/sql/sql_class.h	2008-03-06 15:26:56 +03:00
@@ -911,21 +911,21 @@ public:
   */
   Open_tables_state() : state_flags(0U) { }
 
-  Open_tables_state(ulong version_arg);
+  Open_tables_state(THD *thd, ulong version_arg);
 
   void set_open_tables_state(Open_tables_state *state)
   {
     *this= *state;
   }
 
-  void reset_open_tables_state()
+  void reset_open_tables_state(THD *thd)
   {
     open_tables= temporary_tables= handler_tables= derived_tables= 0;
     extra_lock= lock= locked_tables= 0;
     prelocked_mode= NON_PRELOCKED;
     state_flags= 0U;
-    mdl_context_init(&mdl_context);
-    mdl_context_init(&handler_mdl_context);
+    mdl_context_init(&mdl_context, thd);
+    mdl_context_init(&handler_mdl_context, thd);
   }
 };
 
Thread
bk commit into 6.0 tree (dlenev:1.2561) WL#3726dlenev6 Mar