From: Date: December 4 2007 12:22pm Subject: bk commit into 5.1 tree (istruewing:1.2624) BUG#30273 List-Archive: http://lists.mysql.com/commits/39192 X-Bug: 30273 Message-Id: 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; }