List:Commits« Previous MessageNext Message »
From:Ingo Struewing Date:December 4 2007 12:22pm
Subject:bk commit into 5.1 tree (istruewing:1.2624) BUG#30273
View as plain text  
Below is the list of changes that have just been committed into a local
5.1 repository of istruewing. When istruewing 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, 2007-12-04 12:22:46+01:00, istruewing@stripped +7 -0
  Bug#30273 - merge tables: Can't lock file (errno: 155)
  
  This changeset contains the test for the bug fix.
  
  The bug was not repeatable with the test suite.
  The flush had to happen while the other thread was between
  opening the merge table and attaching the children. This is a
  very short time interval.
  
  To make such race conditions repeatable, the changeset contains
  a new facility. It is only contained in a debug server.
  The "test synchronization" facility allows to place
  synchronization points in the code, where signals can be sent
  and/or waited for. This is controlled by user variables.

  mysql-test/r/merge.result@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +25
-0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added test result.

  mysql-test/t/merge.test@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +44 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added test. It uses the new test synchronization facility.

  sql/item_func.cc@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +232 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Defined functions for the new test synchronization facility.

  sql/mysql_priv.h@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +27 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added declarations for the new test synchronization facility.

  sql/mysqld.cc@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +21 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added definitions, initialization, and cleanup
    for the new test synchronization facility.

  sql/sql_base.cc@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +7 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added calls to the new test synchronization facility.

  sql/sql_parse.cc@stripped, 2007-12-04 12:22:44+01:00, istruewing@stripped +3 -0
    Bug#30273 - merge tables: Can't lock file (errno: 155)
    Added a call to the new test synchronization facility.

diff -Nrup a/mysql-test/r/merge.result b/mysql-test/r/merge.result
--- a/mysql-test/r/merge.result	2007-11-18 20:28:35 +01:00
+++ b/mysql-test/r/merge.result	2007-12-04 12:22:44 +01:00
@@ -1947,3 +1947,28 @@ test.t1	optimize	status	OK
 FLUSH TABLES m1, t1;
 UNLOCK TABLES;
 DROP TABLE t1, m1;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+connection con1
+SET @mysql_test_sync_before_merge_attach= 'merge_attach:flush_tables';
+INSERT INTO m1 VALUES (2);
+connection default;
+SET @mysql_test_sync_before_flush_tables= ':merge_attach';
+SET @mysql_test_sync_after_flush_tables= 'flush_tables';
+FLUSH TABLE m1;
+SET @mysql_test_sync_before_flush_tables= NULL;
+SET @mysql_test_sync_after_flush_tables= NULL;
+SET @mysql_test_sync_after_use_db= ':merge_attach';
+USE test;
+SET @mysql_test_sync_after_use_db= 'flush_tables';
+USE test;
+SET @mysql_test_sync_after_use_db= ':';
+USE test;
+SET @mysql_test_sync_after_use_db= NULL;
+USE test;
+connection con1
+connection default;
+SELECT * FROM m1;
+c1
+2
+DROP TABLE m1, t1;
diff -Nrup a/mysql-test/t/merge.test b/mysql-test/t/merge.test
--- a/mysql-test/t/merge.test	2007-11-18 20:28:35 +01:00
+++ b/mysql-test/t/merge.test	2007-12-04 12:22:44 +01:00
@@ -1358,4 +1358,48 @@ OPTIMIZE TABLE t1;
 FLUSH TABLES m1, t1;
 UNLOCK TABLES;
 DROP TABLE t1, m1;
+#
+# In-depth test.
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+    --echo connection con1
+    connect (con1,localhost,root,,);
+    # Wait for flush before attaching children.
+    SET @mysql_test_sync_before_merge_attach= 'merge_attach:flush_tables';
+    send INSERT INTO m1 VALUES (2);
+--echo connection default;
+connection default;
+#
+# Wait for con1 to reach attach_merge_children(), then signal flush.
+SET @mysql_test_sync_before_flush_tables= ':merge_attach';
+SET @mysql_test_sync_after_flush_tables= 'flush_tables';
+FLUSH TABLE m1;
+#
+# Clear user variables.
+SET @mysql_test_sync_before_flush_tables= NULL;
+SET @mysql_test_sync_after_flush_tables= NULL;
+#
+# con1 runs a second time into attach_merge_children() after flush.
+# Wait for con1 to reach attach_merge_children().
+SET @mysql_test_sync_after_use_db= ':merge_attach';
+USE test;
+## Provoke a signal to 'flush_tables' to kick con1 out of its wait.
+SET @mysql_test_sync_after_use_db= 'flush_tables';
+USE test;
+#
+# Clear user variable and coverage testing: Empty wait_signal.
+SET @mysql_test_sync_after_use_db= ':';
+USE test;
+# Clear user variable and coverage testing: Null value.
+SET @mysql_test_sync_after_use_db= NULL;
+USE test;
+#
+    --echo connection con1
+    connection con1;
+    reap;
+    disconnect con1;
+--echo connection default;
+connection default;
+SELECT * FROM m1;
+DROP TABLE m1, t1;
 
diff -Nrup a/sql/item_func.cc b/sql/item_func.cc
--- a/sql/item_func.cc	2007-11-17 08:20:48 +01:00
+++ b/sql/item_func.cc	2007-12-04 12:22:44 +01:00
@@ -5664,3 +5664,235 @@ longlong Item_func_uuid_short::val_int()
   pthread_mutex_unlock(&LOCK_uuid_generator);
   return (longlong) val;
 }
+
+
+#if !defined(DBUG_OFF)
+
+/**
+  Get the value of a user variable.
+
+  @param[in]    thd             thread handle
+  @param[in]    name            name of user variable
+  @param[in]    name_len        length of name of user variable
+  @param[out]   result          value of the user variable
+
+  @return       status
+    @retval     FALSE           ok, variable value is in *result
+    @retval     TRUE            error, no such variable or null
+
+  @note
+    This function comment applies to all get_user_var() functions.
+*/
+
+#ifdef DEMO_IMPLEMENTATION
+bool get_user_var(THD *thd, const char *name, size_t name_len, longlong *result)
+{
+  user_var_entry *user_var;
+  my_bool null_value;
+
+  if (!(user_var= (user_var_entry*) hash_search(&thd->user_vars,
+                                                (uchar*) name, name_len)))
+    return TRUE;
+  *result= user_var->val_int(&null_value);
+  return null_value;
+}
+#endif
+
+bool get_user_var(THD *thd, const char *name, size_t name_len, String *result)
+{
+  user_var_entry *user_var;
+  my_bool null_value;
+
+  if (!(user_var= (user_var_entry*) hash_search(&thd->user_vars,
+                                                (uchar*) name, name_len)))
+    return TRUE;
+  /* Requesting 6 decimals. No reason. Just an arbitrary value. */
+  if (!user_var->val_str(&null_value, result, 6))
+    return TRUE;
+  return null_value;
+}
+
+
+/**
+  Send a signal and optionally wait for a signal if a user variable is set.
+
+  @param[in]    thd             thread handle
+  @param[in]    user_var_name   name of user variable
+  @param[in]    name_len        length of name of user variable
+
+  @description
+    This function is used to synchronize threads so that they wait at
+    critical places without sleeping a fixed time.
+
+    Nomenclature: A "signal" is a string value that can be set in a
+    global variable. "Sending" a signal means assigning the string value
+    for this signal to the global variable and broadcast a pthread
+    condition. "Waiting" for a signal means to loop on a global pthread
+    condition variable until the global variable is assigned the signal
+    to wait for.
+
+    The global objects used for this facility are:
+
+    SIGNAL_mysql_test_sync     String variable to hold a "signal".
+    COND_mysql_test_sync       Condition variable for signalling and waiting.
+    LOCK_mysql_test_sync       Mutex to synchronize access to the above.
+
+    If the user variable 'user_var_name' contains a string like
+
+        [send_signal][:[wait_signal]]
+
+    then, if "send_signal" is present, the global variable
+    'SIGNAL_mysql_test_sync' is assigned "send_signal" and the
+    condition 'COND_mysql_test_sync' is broadcast. If a colon and
+    "wait_signal" is present, it waits on 'COND_mysql_test_sync' until
+    'SIGNAL_mysql_test_sync' becomes "wait_signal" assigned. If the
+    user variable value is empty, 'SIGNAL_mysql_test_sync' is cleared
+    and 'COND_mysql_test_sync' is broadcast. There are the following
+    options:
+
+        empty                      send empty signal, do not wait
+        send_signal                send send_signal, do not wait
+        send_signal:               send send_signal, do not wait
+        send_signal:wait_signal    send send_signal, wait for wait_signal
+        :wait_signal               do not send signal, wait for wait_signal
+        :                          do not send signal, do not wait (no-op)
+
+    Example:
+
+      test file:
+
+        connection con1;
+        SET @mysql_test_sync_after_open_table= 'open_table:flush_tables';
+        send INSERT INTO t1 VALUES (1);
+        connection con2;
+        SET @mysql_test_sync_before_flush_tables= ':open_table';
+        SET @mysql_test_sync_after_flush_tables= 'flush_tables';
+        FLUSH TABLE t1;
+
+      code:
+
+        in open_table():
+        ... open table ...
+        MYSQL_TEST_SYNC("mysql_test_sync_after_open_table");
+
+        in close_cached_tables():
+        MYSQL_TEST_SYNC("mysql_test_sync_before_flush_tables");
+        ... flush tables ...
+        MYSQL_TEST_SYNC("mysql_test_sync_after_flush_tables");
+
+    MYSQL_TEST_SYNC is a macro that has no code in a non-debug server.
+
+  @todo
+    Currently we do not clear 'SIGNAL_mysql_test_sync' after the wait.
+    This allows other threads to synchronize at this signal too.
+    But one may need to do an explicit cleanup to avoid skipping a wait
+    when 'SIGNAL_mysql_test_sync' is still set to "wait_signal".
+
+  @todo
+    If we want to support test cases that would otherwise fail in a
+    non-debug server, we either take the risk that users can want to
+    set user variables with names that conflict with the this facility,
+    or we find a way to set a thread specific variable by some SQL
+    statement, which enables (and perhaps disables) this facility.
+
+  @todo
+    One day we might find a test that cannot be synchronized with a
+    global signal. We could add a signal, condition, and mutex into THD
+    then. The user variable value could then be written as
+
+        [send_signal[,thread_id]][:[wait_signal[,THD]]]
+
+    Missing thread_id would use the global variable to be compatible
+    with the current facility. Waiting happens on the global signal and
+    condition or the own threads signal and condition, depending on the
+    presence of the literal ",THD".
+*/
+
+void mysql_test_sync(THD *thd, const char *user_var_name, size_t name_len)
+{
+  String var_value;
+  DBUG_ENTER("mysql_test_sync");
+  DBUG_PRINT("mysql_test_sync", ("user_var_name: '%s'", user_var_name));
+
+  /* During bootstrap or in non-THD threads, thd can be NULL. */
+  if (!thd)
+    DBUG_VOID_RETURN;
+
+  /* Get value of user variable. */
+  if (!get_user_var(thd, user_var_name, name_len, &var_value))
+  {
+    /* Variable is set, non-null. */
+    const char  *old_proc_info;
+    char        *old_query;
+    uint        old_query_length;
+    int         offset;
+    String      signal;
+    DBUG_PRINT("mysql_test_sync", ("user_var_value: '%s'", var_value.c_ptr()));
+
+    /* Extract "send_signal" and send it if present in the value. */
+    offset= var_value.strstr(String(":", system_charset_info));
+    if (offset)
+    {
+      /*
+        offset > 0: Colon present, "send_signal" non-empty.
+        offset < 0: No colon present, only a "send_signal" string.
+                    "send_signal" could be empty in this case.
+        If "send_signal" is empty, we clear SIGNAL_mysql_test_sync and
+        broadcast COND_mysql_test_sync anyway.
+      */
+      signal.set(var_value, 0, offset > 0 ? offset : var_value.length());
+      DBUG_PRINT("mysql_test_sync", ("broadcast '%s'", signal.c_ptr()));
+      pthread_mutex_lock(&LOCK_mysql_test_sync);
+      SIGNAL_mysql_test_sync.copy(signal);
+      pthread_cond_broadcast(&COND_mysql_test_sync);
+      pthread_mutex_unlock(&LOCK_mysql_test_sync);
+
+      if (offset < 0)
+      {
+        /* No "wait_signal" present, nothing to wait for. */
+        DBUG_VOID_RETURN;
+      }
+    }
+    /* else: offset == 0: "send_signal" is empty. Do not broadcast. */
+
+    /*
+      Extract "wait_signal" and wait, if non-empty.
+
+      Here, var_value can contain ":" or ":wait_signal". Otherwise
+      'offset' would be < 0 and we won't come here.
+    */
+    signal.set(var_value, offset + 1, var_value.length() - offset - 1);
+    if (!signal.length())
+    {
+        /* No "wait_signal" present, nothing to wait for. */
+        DBUG_VOID_RETURN;
+    }
+
+    /* Save "process" information for INFORMATION_SCHEMA.PROCESSLIST. */
+    old_proc_info=      thd->proc_info;
+    old_query=          thd->query;
+    old_query_length=   thd->query_length;
+
+    /* Change "process" information for INFORMATION_SCHEMA.PROCESSLIST. */
+    thd->proc_info=     "MYSQL_TEST_SYNC";
+    thd->query=         (char*) user_var_name;
+    thd->query_length=  name_len;
+
+    DBUG_PRINT("mysql_test_sync", ("wait for '%s'", signal.c_ptr()));
+    pthread_mutex_lock(&LOCK_mysql_test_sync);
+    while (stringcmp(&SIGNAL_mysql_test_sync, &signal))
+      pthread_cond_wait(&COND_mysql_test_sync, &LOCK_mysql_test_sync);
+    /* @todo Maybe we could do here: SIGNAL_mysql_test_sync.length(0); */
+    pthread_mutex_unlock(&LOCK_mysql_test_sync);
+    DBUG_PRINT("mysql_test_sync", ("awoke from '%s'  for user_var_name: '%s'",
+                                   signal.c_ptr(), user_var_name));
+
+    /* Restore "process" information for INFORMATION_SCHEMA.PROCESSLIST. */
+    thd->proc_info= old_proc_info;
+    thd->query= old_query;
+    thd->query_length= old_query_length;
+  }
+  DBUG_VOID_RETURN;
+}
+
+#endif
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2007-11-15 20:25:41 +01:00
+++ b/sql/mysql_priv.h	2007-12-04 12:22:44 +01:00
@@ -2347,6 +2347,33 @@ bool load_collation(MEM_ROOT *mem_root,
                     CHARSET_INFO *dflt_cl,
                     CHARSET_INFO **cl);
 
+#if !defined(DBUG_OFF)
+/*
+  Declarations for the test synchronization facility.
+  1. Global string variable to hold a "signal".
+  2. Global condition variable for signalling and waiting.
+  3. Global mutex to synchronize access to the above.
+  4. Functions to retrieve the value of user variables.
+  5. Function to signal and/or wait based on the value of a user variable.
+  6. Macro to skip code generation in a non-debug server.
+  See mysqld.cc for the variable definitions and [de-]initialization.
+  See item_func.cc for the function definitions and more information.
+*/
+extern String SIGNAL_mysql_test_sync;
+extern pthread_cond_t COND_mysql_test_sync;
+extern pthread_mutex_t LOCK_mysql_test_sync;
+extern bool get_user_var(THD *thd, const char *name, size_t name_len,
+                         longlong *result);
+extern bool get_user_var(THD *thd, const char *name, size_t name_len,
+                         String *result);
+extern void mysql_test_sync(THD *thd, const char *user_var_name,
+                            size_t name_len);
+#define MYSQL_TEST_SYNC(_thd_, _user_var_name_)                 \
+        mysql_test_sync(_thd_, STRING_WITH_LEN(_user_var_name_))
+#else
+#define MYSQL_TEST_SYNC(_thd_, _user_var_name_) do{}while(0)
+#endif
+
 #endif /* MYSQL_SERVER */
 #endif /* MYSQL_CLIENT */
 
diff -Nrup a/sql/mysqld.cc b/sql/mysqld.cc
--- a/sql/mysqld.cc	2007-10-31 22:10:56 +01:00
+++ b/sql/mysqld.cc	2007-12-04 12:22:44 +01:00
@@ -612,6 +612,13 @@ static pthread_t select_thread;
 static uint thr_kill_signal;
 #endif
 
+#if !defined(DBUG_OFF)
+/* Definitions for the test synchronization facility. See item_func.cc */
+String SIGNAL_mysql_test_sync;
+pthread_cond_t COND_mysql_test_sync;
+pthread_mutex_t LOCK_mysql_test_sync;
+#endif
+
 /* OS specific variables */
 
 #ifdef __WIN__
@@ -1264,6 +1271,10 @@ void clean_up(bool print_message)
   /* do the broadcast inside the lock to ensure that my_end() is not called */
   (void) pthread_cond_broadcast(&COND_thread_count);
   (void) pthread_mutex_unlock(&LOCK_thread_count);
+#if !defined(DBUG_OFF)
+  /* Cleanup for the test synchronization facility. See item_func.cc */
+  SIGNAL_mysql_test_sync.free();
+#endif
 
   /*
     The following lines may never be executed as the main thread may have
@@ -1344,6 +1355,11 @@ static void clean_up_mutexes()
   (void) pthread_cond_destroy(&COND_thread_cache);
   (void) pthread_cond_destroy(&COND_flush_thread_cache);
   (void) pthread_cond_destroy(&COND_manager);
+#if !defined(DBUG_OFF)
+  /* Cleanup for the test synchronization facility. See item_func.cc */
+  (void) pthread_cond_destroy(&COND_mysql_test_sync);
+  (void) pthread_mutex_destroy(&LOCK_mysql_test_sync);
+#endif
 }
 
 #endif /*EMBEDDED_LIBRARY*/
@@ -3092,6 +3108,11 @@ static int init_thread_environment()
 #ifdef HAVE_REPLICATION
   (void) pthread_mutex_init(&LOCK_rpl_status, MY_MUTEX_INIT_FAST);
   (void) pthread_cond_init(&COND_rpl_status, NULL);
+#endif
+#if !defined(DBUG_OFF)
+  /* Initialization for the test synchronization facility. See item_func.cc */
+  (void) pthread_cond_init(&COND_mysql_test_sync,NULL);
+  (void) pthread_mutex_init(&LOCK_mysql_test_sync, MY_MUTEX_INIT_FAST);
 #endif
   (void) pthread_mutex_init(&LOCK_server_started, MY_MUTEX_INIT_FAST);
   (void) pthread_cond_init(&COND_server_started,NULL);
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2007-11-15 20:25:41 +01:00
+++ b/sql/sql_base.cc	2007-12-04 12:22:44 +01:00
@@ -897,6 +897,8 @@ bool close_cached_tables(THD *thd, bool 
   DBUG_ENTER("close_cached_tables");
   DBUG_ASSERT(thd || (!if_wait_for_refresh && !tables));
 
+  MYSQL_TEST_SYNC(thd, "mysql_test_sync_before_flush_tables");
+
   if (!have_lock)
     VOID(pthread_mutex_lock(&LOCK_open));
   if (!tables)
@@ -995,6 +997,9 @@ bool close_cached_tables(THD *thd, bool 
     close_old_data_files(thd,thd->open_tables,1,1);
     mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL,
                    TRUE);
+
+    MYSQL_TEST_SYNC(thd, "mysql_test_sync_after_flush_tables");
+
     bool found=1;
     /* Wait until all threads has closed all the tables we had locked */
     DBUG_PRINT("info",
@@ -4015,6 +4020,8 @@ static int attach_merge_children(TABLE_L
   DBUG_ENTER("attach_merge_children");
   DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
                       parent->s->table_name.str, (long) parent));
+
+  MYSQL_TEST_SYNC(current_thd, "mysql_test_sync_before_merge_attach");
 
   /* Must not call this with attached children. */
   DBUG_ASSERT(!parent->children_attached);
diff -Nrup a/sql/sql_parse.cc b/sql/sql_parse.cc
--- a/sql/sql_parse.cc	2007-11-15 20:25:41 +01:00
+++ b/sql/sql_parse.cc	2007-12-04 12:22:44 +01:00
@@ -3067,6 +3067,9 @@ end_with_restore_list:
     if (!mysql_change_db(thd, &db_str, FALSE))
       send_ok(thd);
 
+    /* Use this if you want to signal and/or wait without side effects. */
+    MYSQL_TEST_SYNC(thd, "mysql_test_sync_after_use_db");
+
     break;
   }
 
Thread
bk commit into 5.1 tree (istruewing:1.2624) BUG#30273Ingo Struewing4 Dec