List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:July 27 2009 6:57pm
Subject:Re: bzr commit into mysql-5.4 branch (jon.hauglid:2804) Bug#42546
View as plain text  
* Jon Olav Hauglid <Jon.Hauglid@stripped> [09/06/23 16:30]:

>  2804 Jon Olav Hauglid	2009-06-23
>       Bug#42546 Backup: RESTORE fails, thinking it finds an existing table
>       
>       The problem occured when a MDL locking conflict happened for a non-existent 
>       table between a CREATE and a INSERT statement. The code for CREATE 
>       interpreted this lock conflict to mean that the table existed, 
>       which meant that the statement failed when it should not have.

There are many things that still need to be done, and 
I will have fairly radical suggestions.

First of all, there are 3 use cases susceptible to the bug:

1) CREATE TABLE [IF NOT EXISTS]
2) CREATE TABLE LIKE
3) ALTER TABLE ... RENAME

Each case will require a different fix.

1) CREATE TABLE [IF NOT EXISTS].

In the foreign keys tree we prohibit CREATE TABLE
under LOCK TABLES. Since we prohibit other dangerous operations
under LOCK TABLES in 5.4, it's better to do all incompatible
changes related to LOCK TABLES in one release, rather than 
span multiple releases.

Once this is done, we can use the normal open_and_lock_tables() 
function for CREATE TABLE, which internally will use
acquire_exclusive_locks(). 

For an example how it is done, please look at CREATE TABLE .. SELECT
implementation in 5.4, and CREATE TABLE implementation in
6.1-fk-stage:

sql_parse.cc, line ~2631:

      if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
      {
        lex->link_first_table_back(create_table, link_to_local);
        /* Set strategies: reset default or 'prepared' values. */
        create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
        create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL;
        if (!thd->locked_tables_mode)
          global_schema_lock_guard.lock();
      }

      if (!(res= open_and_lock_tables(thd, lex->query_tables)))
      {
        /*


In other words, we should use open_strategy and lock_strategy
mechanism to open the table.

This change is already implemented in 6.1-fk-stage and it
would be ideal if you just backport it from there. You need
to be looking at 6.1-fk-stage since I will request a backport
of class Prelocking_strategy from this tree for Bug#30977.

Please ask me and Dmitri for pointers what bits exactly need 
to be backported.

2) CREATE TABLE LIKE

Once we prohibit CREATE TABLE (all forms, including CREATE TABLE.. LIKE)
under LOCK TABLES, we can
significantly simplify CREATE TABLE .. LIKE implementation,
and make it use the path of CREATE TABLE .. SELECT.

Once it's done, we solve the problem for CREATE TABLE ... LIKE,
since CREATE TABLE ... SELECT is using open_strategy/lock_strategy,
and thus is not susceptible to the bug.

Note, that CREATE TABLE ... SELECT has been prohibited under LOCK TABLES
since long time ago.

CREATE TABLE ... LIKE has been rewritten in 6.1-fk-stage to use 
CREATE TABLE ... SELECT implementation, so please backport the change
from there.

3) ALTER TABLE ... RENAME. 

This case is difficult. ALTER TABLE .. RENAME is allowed under LOCK TABLES,
and we don't plan to change it yet -- there are many people using
this feature (we've been getting bug reports about it :-)).

So we need to keep try_acquire_exclusive_lock() under LOCK TABLES
in ALTER TABLE ... RENAME, but use normal open_and_lock_tables()
otherwise.

Note, that this change is also already done in 6.1-fk-stage.

Please, again, backport it.

Now, to the patch itself.

I will post my patch, and will explain how it differs, and what
other issues I found with your patch.

1) Please add a test case for CREATE TABLE LIKE and ALTER TABLE RENAME.
2) Please demonstrate that under LOCK TABLES (ALTER TABLE .. RENAME)
we still have this bug.
3) Please kill lock_table_name_if_not_exists(). (I did it in my patch).

Some comments about your patch that are irrelevant since I request
some radically different things:

4) In your patch, you try to release the target mdl ticket in some
cases when it has not been taken (you release it if it the request
was allocated successfully, but acquire_exclusive_locks() may fail).
5) There is some code duplication when producing an error message
if the table exists.
6) Calling acquire_exclusive_locks() to get an 
exclusive lock in CREATE TABLE .. LIKE, and then doing 
open_tables() is still dangerous, since open_tables() may fall into waiting
while holding the exclusive lock.
 It's better to call open_tables() for both tables
altogether, but set the right open_strategy and lock_strategy for
the first (allegedly non-existent) table. This is how it's done
in CREATE TABLE .. SELECT. 

Since doing everything I request in this review may be difficult,
please do it one step at a time:

- first check that the patch I am sending to you below works. It
solves the problem for CREATE TABLE, but is incomplete.
- then fix CREATE TABLE ... LIKE by backporting changes from 6.1-fk-stage,
and commit.
- then rewrite CREATE TABLE [IF NOT EXISTS] to use open_tables().
Again by backporting 6.1-fk-stage stages. Commit again.
- finally, fix ALTER TABLE.

If you still think it's too difficult, please don't hesitate
to bring this up.

=== modified file 'mysql-test/r/create.result'
--- mysql-test/r/create.result	2009-05-28 21:29:31 +0000
+++ mysql-test/r/create.result	2009-07-27 14:20:07 +0000
@@ -371,7 +371,7 @@ ERROR 42S01: Table 't3' already exists
 create table non_existing_database.t1 like t1;
 ERROR 42000: Unknown database 'non_existing_database'
 create table t3 like non_existing_table;
-ERROR 42S02: Table 'test.non_existing_table' doesn't exist
+ERROR 42S01: Table 't3' already exists
 create temporary table t3 like t1;
 ERROR 42S01: Table 't3' already exists
 drop table t1, t2, t3;
@@ -1934,3 +1934,29 @@ END ;|
 ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the same
action time and event for one table'
 DROP TABLE t1;
 DROP TABLE B;
+#
+# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
+#
+DROP TABLE IF EXISTS t1;
+# Start insert on the not-yet existing table
+# Wait after taking the MDL
+SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
+                 EXECUTE 2';
+INSERT INTO t1 VALUES(1,"def");
+# Need to sync twice. Number 1 is consumed by General Log.
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+SET DEBUG_SYNC= 'now SIGNAL finish';
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+# Now INSERT has a MDL on the non-existent table t1.
+#
+# Continue the INSERT once CREATE waits for exclusive lock
+SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+# Try to create that table.
+CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM;
+# Insert fails
+ERROR 42S02: Table 'test.t1' doesn't exist
+SET DEBUG_SYNC= 'RESET';
+SHOW TABLES;
+DROP TABLE IF EXISTS t1;
+Tables_in_test
+t1

=== modified file 'mysql-test/t/create.test'
--- mysql-test/t/create.test	2009-05-28 21:29:31 +0000
+++ mysql-test/t/create.test	2009-07-27 14:20:07 +0000
@@ -1,3 +1,5 @@
+--source include/have_debug_sync.inc
+
 #
 # Check some special create statements.
 #
@@ -306,7 +308,7 @@ create table t3 like t1;
 create table t3 like mysqltest.t3;
 --error 1049
 create table non_existing_database.t1 like t1;
---error ER_NO_SUCH_TABLE
+--error ER_TABLE_EXISTS_ERROR
 create table t3 like non_existing_table;
 --error 1050
 create temporary table t3 like t1;
@@ -1600,3 +1602,51 @@ END ;|
 
 DROP TABLE t1;
 DROP TABLE B;
+
+###########################################################################
+
+--echo #
+--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
+--echo #
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect(con1, localhost, root,,);
+connection con1;
+
+--echo # Start insert on the not-yet existing table
+--echo # Wait after taking the MDL
+SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
+                 EXECUTE 2';
+--send INSERT INTO t1 VALUES(1,"def")
+
+connection default;
+
+--echo # Need to sync twice. Number 1 is consumed by General Log.
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+SET DEBUG_SYNC= 'now SIGNAL finish';
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+--echo # Now INSERT has a MDL on the non-existent table t1.
+
+--echo #
+--echo # Continue the INSERT once CREATE waits for exclusive lock
+SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+--echo # Try to create that table.
+--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM
+
+--echo # Insert fails
+connection con1;
+--error ER_NO_SUCH_TABLE
+--reap
+
+connection default;
+
+SET DEBUG_SYNC= 'RESET';
+
+SHOW TABLES;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings

=== modified file 'sql/mdl.cc'
--- sql/mdl.cc	2009-03-31 13:00:58 +0000
+++ sql/mdl.cc	2009-07-27 17:21:20 +0000
@@ -1163,17 +1163,16 @@ MDL_ticket::upgrade_shared_lock_to_exclu
   @param mdl_request [in] The lock request
   @param conflict   [out] Indicates that conflicting lock exists
 
-  @retval TRUE  Failure either conflicting lock exists or some error
-                occurred (probably OOM).
-  @retval FALSE Success, lock was acquired.
-
-  FIXME: Compared to lock_table_name_if_not_cached()
-         it gives slightly more false negatives.
+  @retval TRUE  Failure: some error occurred (probably OOM).
+  @retval FALSE Success: the lock might have not been acquired,
+                         check request.ticket to find out.
+                         If and only if the lock was acquired
+                         the request was added to the list of
+                         requests in this context.
 */
 
 bool
-MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request,
-                                        bool *conflict)
+MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request)
 {
   MDL_lock *lock;
   MDL_ticket *ticket;
@@ -1184,8 +1183,6 @@ MDL_context::try_acquire_exclusive_lock(
 
   safe_mutex_assert_not_owner(&LOCK_open);
 
-  *conflict= FALSE;
-
   pthread_mutex_lock(&LOCK_mdl);
 
   if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks,
@@ -1197,8 +1194,15 @@ MDL_context::try_acquire_exclusive_lock(
     {
       MDL_ticket::destroy(ticket);
       MDL_lock::destroy(lock);
-      goto err;
+      pthread_mutex_unlock(&LOCK_mdl);
+      return TRUE;
     }
+    /*
+      Upon successful acquisition, add the request to the list of the
+      requests of this context. The caller didn't do that.
+      Use a public API method to simplify debugging.
+    */
+    add_request(mdl_request);
     mdl_request->ticket= ticket;
     lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE;
     lock->granted.push_front(ticket);
@@ -1206,16 +1210,9 @@ MDL_context::try_acquire_exclusive_lock(
     ticket->m_state= MDL_ACQUIRED;
     ticket->m_lock= lock;
     global_lock.active_intention_exclusive++;
-    pthread_mutex_unlock(&LOCK_mdl);
-    return FALSE;
   }
-
-  /* There is some lock for the object. */
-  *conflict= TRUE;
-
-err:
   pthread_mutex_unlock(&LOCK_mdl);
-  return TRUE;
+  return FALSE;
 }
 
 

=== modified file 'sql/mdl.h'
--- sql/mdl.h	2009-03-04 20:29:16 +0000
+++ sql/mdl.h	2009-07-27 17:26:40 +0000
@@ -285,7 +285,7 @@ public:
 
   bool acquire_shared_lock(MDL_request *mdl_request, bool *retry);
   bool acquire_exclusive_locks();
-  bool try_acquire_exclusive_lock(MDL_request *mdl_request, bool *conflict);
+  bool try_acquire_exclusive_lock(MDL_request *mdl_request);
   bool acquire_global_shared_lock();
 
   bool wait_for_locks();

=== modified file 'sql/sql_base.cc'
--- sql/sql_base.cc	2009-07-24 20:28:43 +0000
+++ sql/sql_base.cc	2009-07-27 14:20:07 +0000
@@ -2596,6 +2596,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
       DEBUG_SYNC(thd, "before_open_table_wait_refresh");
       DBUG_RETURN(TRUE);
     }
+    DEBUG_SYNC(thd, "after_open_table_mdl_shared");
   }
 
   /*

=== modified file 'sql/sql_table.cc'
--- sql/sql_table.cc	2009-07-24 14:04:20 +0000
+++ sql/sql_table.cc	2009-07-27 18:42:31 +0000
@@ -1863,14 +1863,25 @@ int mysql_rm_table_part2(THD *thd, TABLE
         if (find_temporary_table(thd, table->db, table->table_name))
         {
           /*
-            Since we don't acquire metadata lock if we have found temporary
-            table, we should do something to avoid releasing it at the end.
+            A temporary table.
+
+            Don't try to find a corresponding MDL lock or assign it
+            to table->mdl_request. There can't be metadata
+            locks for temporary tables: they are local to the session.
+
+            Later in this function we release the MDL lock only if
+            table->mdl_requeset is not NULL. Thus here we
+            ensure that we won't release the metadata lock on the base
+            table locked with LOCK TABLES as a side effect of temporary
+            table drop.
           */
           table->mdl_request= NULL;
         }
         else
         {
           /*
+            Not a temporary table.
+
             Since 'tables' list can't contain duplicates (this is ensured
             by parser) it is safe to cache pointer to the TABLE instances
             in its elements.
@@ -2102,7 +2113,7 @@ err:
       Under LOCK TABLES we should release meta-data locks on the tables
       which were dropped. Otherwise we can rely on close_thread_tables()
       doing this. Unfortunately in this case we are likely to get more
-      false positives in lock_table_name_if_not_cached() function. So
+      false positives in try_acquire_exclusive_lock() function. So
       it makes sense to remove exclusive meta-data locks in all cases.
 
       Leave LOCK TABLES mode if we managed to drop all tables which were
@@ -3989,48 +4000,12 @@ warn:
 }
 
 
-/**
-   Auxiliary function which obtains exclusive meta-data lock on the
-   table if there are no shared or exclusive on it already.
-
-   See mdl_try_acquire_exclusive_lock() function for more info.
-
-   TODO: This function is here mostly to simplify current patch
-         and probably should be removed.
-   TODO: Investigate if it is kosher to leave lock request in the
-         context in the case when we fail to obtain the lock.
-*/
-
-static bool lock_table_name_if_not_cached(THD *thd, const char *db,
-                                          const char *table_name,
-                                          MDL_request **mdl_request)
+bool dd_check_table_exists(const char *db, const char *table_name)
 {
-  bool conflict;
-
-  if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
-    return TRUE;
-  (*mdl_request)->set_type(MDL_EXCLUSIVE);
-  thd->mdl_context.add_request(*mdl_request);
-  if (thd->mdl_context.try_acquire_exclusive_lock(*mdl_request, &conflict))
-  {
-    /*
-      To simplify our life under LOCK TABLES we remove unsatisfied
-      lock request from the context.
-    */
-    thd->mdl_context.remove_request(*mdl_request);
-    if (!conflict)
-    {
-      /* Probably OOM. */
-      return TRUE;
-    }
-    else
-      *mdl_request= NULL;
-  }
-  else
-  {
-    DEBUG_SYNC(thd, "locked_table_name");
-  }
-  return FALSE;
+  char dst_path[FN_REFLEN];
+  uint dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
+                                             db, table_name, reg_ext, 0);
+  return !access(dst_path, F_OK);
 }
 
 
@@ -4045,6 +4020,7 @@ bool mysql_create_table(THD *thd, const 
                         uint select_field_count)
 {
   MDL_request *target_mdl_request= NULL;
+  bool has_target_mdl_lock= FALSE;
   bool result;
   Ha_global_schema_lock_guard global_schema_lock_guard(thd);
   DBUG_ENTER("mysql_create_table");
@@ -4073,13 +4049,44 @@ bool mysql_create_table(THD *thd, const 
 
   if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
   {
-    if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request))
+    if (!(target_mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
     {
       result= TRUE;
       goto unlock;
     }
-    if (!target_mdl_request)
+    target_mdl_request->set_type(MDL_EXCLUSIVE);
+    /*
+      Take exclusive metadata lock on the table name. If we are in LOCK TABLES
+      mode, we cannot wait in case of lock conflict for fear of deadlock.
+    */
+    if (thd->locked_tables_mode)
+      result= thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request);
+    else
     {
+      thd->mdl_context.add_request(target_mdl_request);
+      result= thd->mdl_context.acquire_exclusive_locks();
+    }
+    if (result)
+      goto unlock;
+    /* Got lock. */
+    DEBUG_SYNC(thd, "locked_table_name");
+    has_target_mdl_lock= test(target_mdl_request->ticket);
+
+    /**
+      If ticket is NULL, try_acquire_exclusive_lock() 
+      found a conflict -- so the table is likely to already
+      exist. Note that this is not 100% accurate (see
+      Bug#42546 for a test case), but it's the best we can do.
+      If get_cached_object() is not NULL, the share exists,
+      therefore the table surely exists.
+      Finally, the table may exist on disk but not in the cache.
+      We can access disk, because we have an MDL lock.
+    */
+    if (target_mdl_request->ticket == NULL ||
+        target_mdl_request->ticket->get_cached_object() ||
+        dd_check_table_exists(db, table_name))
+    {
+      /* Table exists and is locked by some other thread. */
       if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
       {
         push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
@@ -4102,7 +4109,7 @@ bool mysql_create_table(THD *thd, const 
                                      select_field_count);
 
 unlock:
-  if (target_mdl_request)
+  if (has_target_mdl_lock)
   {
     thd->mdl_context.release_lock(target_mdl_request->ticket);
     thd->mdl_context.remove_request(target_mdl_request);
@@ -5149,6 +5156,7 @@ bool mysql_create_like_table(THD* thd, T
   MDL_request *target_mdl_request= NULL;
   char src_path[FN_REFLEN + 1], dst_path[FN_REFLEN + 1];
   uint dst_path_length;
+  bool has_mdl_lock= FALSE;
   char *db= table->db;
   char *table_name= table->table_name;
   int  err;
@@ -5180,7 +5188,7 @@ bool mysql_create_like_table(THD* thd, T
 
   DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
 
-  /* 
+  /*
     Check that destination tables does not exist. Note that its name
     was already checked when it was added to the table list.
   */
@@ -5193,10 +5201,17 @@ bool mysql_create_like_table(THD* thd, T
   }
   else
   {
-    if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request))
+    if (! (target_mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
+      goto err;
+    target_mdl_request->set_type(MDL_EXCLUSIVE);
+    if (thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request))
       goto err;
-    if (!target_mdl_request)
+    if (target_mdl_request->ticket == NULL)
       goto table_exists;
+
+    DEBUG_SYNC(thd, "locked_table_name");
+    has_mdl_lock= TRUE;
+
     dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1,
                                           db, table_name, reg_ext, 0);
     if (!access(dst_path, F_OK))
@@ -5369,7 +5384,7 @@ table_exists:
     my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
 
 err:
-  if (target_mdl_request)
+  if (has_mdl_lock)
   {
     thd->mdl_context.release_lock(target_mdl_request->ticket);
     thd->mdl_context.remove_request(target_mdl_request);
@@ -6838,6 +6853,7 @@ bool mysql_alter_table(THD *thd,char *ne
   TABLE *table, *new_table= 0;
   MDL_ticket *mdl_ticket;
   MDL_request *target_mdl_request= NULL;
+  bool has_target_mdl_lock= FALSE;
   int error= 0;
   char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
   char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -7077,14 +7093,21 @@ view_err:
       }
       else
       {
-        if (lock_table_name_if_not_cached(thd, new_db, new_name,
-                                          &target_mdl_request))
+        if (! (target_mdl_request= MDL_request::create(0, new_db, new_name,
thd->mem_root)))
+          DBUG_RETURN(TRUE);
+        target_mdl_request->set_type(MDL_EXCLUSIVE);
+        if (thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request))
           DBUG_RETURN(TRUE);
-        if (!target_mdl_request)
+
+        /* Table exists and is locked by some thread. */
+        if (target_mdl_request->ticket == NULL)
         {
 	  my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
 	  DBUG_RETURN(TRUE);
         }
+        DEBUG_SYNC(thd, "locked_table_name");
+        has_target_mdl_lock= TRUE;
+
 
         build_table_filename(new_name_buff, sizeof(new_name_buff) - 1,
                              new_db, new_name_buff, reg_ext, 0);
@@ -7741,7 +7764,7 @@ err:
                                  alter_info->datetime_field->field_name);
     thd->abort_on_warning= save_abort_on_warning;
   }
-  if (target_mdl_request)
+  if (has_target_mdl_lock)
   {
     thd->mdl_context.release_lock(target_mdl_request->ticket);
     thd->mdl_context.remove_request(target_mdl_request);





>     modified:
>       mysql-test/r/create.result
>       mysql-test/t/create.test
>       sql/sql_base.cc
>       sql/sql_table.cc
> === modified file 'mysql-test/r/create.result'
> --- a/mysql-test/r/create.result	2009-05-28 21:29:31 +0000
> +++ b/mysql-test/r/create.result	2009-06-23 12:08:59 +0000
> @@ -371,7 +371,7 @@ ERROR 42S01: Table 't3' already exists
>  create table non_existing_database.t1 like t1;
>  ERROR 42000: Unknown database 'non_existing_database'
>  create table t3 like non_existing_table;
> -ERROR 42S02: Table 'test.non_existing_table' doesn't exist
> +ERROR 42S01: Table 't3' already exists
>  create temporary table t3 like t1;
>  ERROR 42S01: Table 't3' already exists
>  drop table t1, t2, t3;
> @@ -1934,3 +1934,29 @@ END ;|
>  ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the
> same action time and event for one table'
>  DROP TABLE t1;
>  DROP TABLE B;
> +#
> +# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
> +#
> +DROP TABLE IF EXISTS t1;
> +# Start insert on the not-yet existing table
> +# Wait after taking the MDL
> +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
> +                 EXECUTE 2';
> +INSERT INTO t1 VALUES(1,"def");
> +# Need to sync twice. Number 1 is consumed by General Log.
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +SET DEBUG_SYNC= 'now SIGNAL finish';
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +# Now INSERT has a MDL on the non-existent table t1.
> +#
> +# Continue the INSERT once CREATE waits for exclusive lock
> +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
> +# Try to create that table.
> +CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM;
> +# Insert fails
> +ERROR 42S02: Table 'test.t1' doesn't exist
> +SET DEBUG_SYNC= 'RESET';
> +SHOW TABLES;
> +DROP TABLE IF EXISTS t1;
> +Tables_in_test
> +t1
> 
> === modified file 'mysql-test/t/create.test'
> --- a/mysql-test/t/create.test	2009-05-28 21:29:31 +0000
> +++ b/mysql-test/t/create.test	2009-06-23 12:08:59 +0000
> @@ -1,3 +1,5 @@
> +--source include/have_debug_sync.inc
> +
>  #
>  # Check some special create statements.
>  #
> @@ -306,7 +308,7 @@ create table t3 like t1;
>  create table t3 like mysqltest.t3;
>  --error 1049
>  create table non_existing_database.t1 like t1;
> ---error ER_NO_SUCH_TABLE
> +--error ER_TABLE_EXISTS_ERROR
>  create table t3 like non_existing_table;
>  --error 1050
>  create temporary table t3 like t1;
> @@ -1600,3 +1602,51 @@ END ;|
>  
>  DROP TABLE t1;
>  DROP TABLE B;
> +
> +###########################################################################
> +
> +--echo #
> +--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
> +--echo #
> +
> +--disable_warnings
> +DROP TABLE IF EXISTS t1;
> +--enable_warnings
> +
> +connect(con1, localhost, root,,);
> +connection con1;
> +
> +--echo # Start insert on the not-yet existing table
> +--echo # Wait after taking the MDL
> +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
> +                 EXECUTE 2';
> +--send INSERT INTO t1 VALUES(1,"def")
> +
> +connection default;
> +
> +--echo # Need to sync twice. Number 1 is consumed by General Log.
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +SET DEBUG_SYNC= 'now SIGNAL finish';
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +--echo # Now INSERT has a MDL on the non-existent table t1.
> +
> +--echo #
> +--echo # Continue the INSERT once CREATE waits for exclusive lock
> +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
> +--echo # Try to create that table.
> +--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM
> +
> +--echo # Insert fails
> +connection con1;
> +--error ER_NO_SUCH_TABLE
> +--reap
> +
> +connection default;
> +
> +SET DEBUG_SYNC= 'RESET';
> +
> +SHOW TABLES;
> +
> +--disable_warnings
> +DROP TABLE IF EXISTS t1;
> +--enable_warnings
> 
> === modified file 'sql/sql_base.cc'
> --- a/sql/sql_base.cc	2009-06-17 07:30:19 +0000
> +++ b/sql/sql_base.cc	2009-06-23 12:08:59 +0000
> @@ -2570,6 +2570,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
>        DEBUG_SYNC(thd, "before_open_table_wait_refresh");
>        DBUG_RETURN(TRUE);
>      }
> +    DEBUG_SYNC(thd, "after_open_table_mdl_shared");
>    }
>  
>    /*
> 
> === modified file 'sql/sql_table.cc'
> --- a/sql/sql_table.cc	2009-06-02 11:46:19 +0000
> +++ b/sql/sql_table.cc	2009-06-23 12:08:59 +0000
> @@ -4001,7 +4001,7 @@ static bool lock_table_name_if_not_cache
>                                            const char *table_name,
>                                            MDL_request **mdl_request)
>  {
> -  bool conflict;
> +  bool conflict;  
>  
>    if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
>      return TRUE;
> @@ -4019,7 +4019,7 @@ static bool lock_table_name_if_not_cache
>        /* Probably OOM. */
>        return TRUE;
>      }
> -    else
> +    else 
>        *mdl_request= NULL;
>    }
>    else
> @@ -4069,27 +4069,58 @@ bool mysql_create_table(THD *thd, const 
>  
>    if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
>    {
> -    if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> +    /* 
> +      Take exclusive metadata lock on the table name. If we are in LOCK TABLES
> mode,
> +      we cannot wait in case of lock conflict for fear of deadlock.
> +    */
> +    if (thd->locked_tables_mode)
>      {
> -      result= TRUE;
> -      goto unlock;
> +      if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> +      {
> +	result= TRUE;
> +	goto unlock;
> +      }
>      }
> -    if (!target_mdl_request)
> +    else 
>      {
> -      if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
> +      if (!(target_mdl_request= MDL_request::create(0, db, table_name,
> thd->mem_root)))
>        {
> -        push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> -                            ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
> -                            table_name);
> -        create_info->table_existed= 1;
> -        result= FALSE;
> +	result= TRUE;
> +	goto unlock;
>        }
> -      else
> +      target_mdl_request->set_type(MDL_EXCLUSIVE);
> +      thd->mdl_context.add_request(target_mdl_request);
> +      if (thd->mdl_context.acquire_exclusive_locks())
>        {
> -        my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
> -        result= TRUE;
> +	result= TRUE;
> +	goto unlock;
> +      }
> +      DEBUG_SYNC(thd, "locked_table_name");
> +    }
> +
> +    /* Check if the table exists already */
> +    if (!target_mdl_request->ticket->get_cached_object())
> +    {
> +      char dst_path[FN_REFLEN];
> +      uint dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> +						 db, table_name, reg_ext, 0);
> +      if (!access(dst_path, F_OK)) 
> +      {
> +	if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
> +	{
> +	  push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> +			      ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
> +			      table_name);
> +	  create_info->table_existed= 1;
> +	  result= FALSE;
> +	}
> +	else
> +	{
> +	  my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
> +	  result= TRUE;
> +	}
> +	goto unlock;
>        }
> -      goto unlock;
>      }
>    }
>    result= mysql_create_table_no_lock(thd, db, table_name, create_info,
> @@ -5159,24 +5190,12 @@ bool mysql_create_like_table(THD* thd, T
>    if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
>      global_schema_lock_guard.lock();
>  
> -  /*
> -    By opening source table and thus acquiring shared metadata lock on it
> -    we guarantee that it exists and no concurrent DDL operation will mess
> -    with it. Later we also take an exclusive metadata lock on target table
> -    name, which makes copying of .frm file, call to ha_create_table() and
> -    binlogging atomic against concurrent DML and DDL operations on target
> -    table. Thus by holding both these "locks" we ensure that our statement
> -    is properly isolated from all concurrent operations which matter.
> -  */
> -  if (open_tables(thd, &src_table, &not_used, 0))
> -    DBUG_RETURN(TRUE);
> +  /* Used by create-big.test */
> +  DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000);); 
>  
> -  strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS);
> -
> -  DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
> -
> -  /* 
> -    Check that destination tables does not exist. Note that its name
> +  /*
> +    Get exclusive metadata lock on the target table and
> +    check that destination table does not exist. Note that its name
>      was already checked when it was added to the table list.
>    */
>    if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
> @@ -5188,14 +5207,35 @@ bool mysql_create_like_table(THD* thd, T
>    }
>    else
>    {
> -    if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> -      goto err;
> -    if (!target_mdl_request)
> -      goto table_exists;
> -    dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> -                                          db, table_name, reg_ext, 0);
> -    if (!access(dst_path, F_OK))
> -      goto table_exists;
> +    /*
> +      Take exclusive metadata lock on the table name. If we are in LOCK TABLES
> mode,
> +      we cannot wait in case of lock conflict for fear of deadlock.
> +    */
> +    if (thd->locked_tables_mode)
> +    {
> +      if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> +	goto err;
> +    }
> +    else 
> +    {
> +      if (!(target_mdl_request= MDL_request::create(0, db, table_name,
> thd->mem_root)))
> +	goto err;
> +      target_mdl_request->set_type(MDL_EXCLUSIVE);
> +      thd->mdl_context.add_request(target_mdl_request);
> +      if (thd->mdl_context.acquire_exclusive_locks())
> +	goto err;
> +      DEBUG_SYNC(thd, "locked_table_name");
> +    }
> +
> +    /* Check if the table exists already */
> +    if (!target_mdl_request->ticket->get_cached_object())
> +    {
> +      dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> +					    db, table_name, reg_ext, 0);
> +      if (!access(dst_path, F_OK))
> +	goto table_exists;
> +    }
> +
>      /*
>        Make the metadata lock available to open_table() called to
>        reopen the table down the road.
> @@ -5203,6 +5243,20 @@ bool mysql_create_like_table(THD* thd, T
>      table->mdl_request= target_mdl_request;
>    }
>  
> +  /*
> +    By opening source table and thus acquiring shared metadata lock on it
> +    we guarantee that it exists and no concurrent DDL operation will mess
> +    with it. We already have an exclusive metadata lock on target table
> +    name, which makes copying of .frm file, call to ha_create_table() and
> +    binlogging atomic against concurrent DML and DDL operations on target
> +    table. Thus by holding both these "locks" we ensure that our statement
> +    is properly isolated from all concurrent operations which matter.
> +  */
> +  if (open_tables(thd, &src_table, &not_used, 0))
> +    DBUG_RETURN(TRUE);
> +
> +  strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS);
> +
>    DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000););
>  
>    /*
> 

-- 
Thread
bzr commit into mysql-5.4 branch (jon.hauglid:2804) Bug#42546Jon Olav Hauglid23 Jun
  • Re: bzr commit into mysql-5.4 branch (jon.hauglid:2804) Bug#42546Konstantin Osipov27 Jul
    • Re: bzr commit into mysql-5.4 branch (jon.hauglid:2804) Bug#42546Kristian Nielsen31 Jul
  • Re: bzr commit into mysql-5.4 branch (jon.hauglid:2804) Bug#42546Konstantin Osipov27 Jul