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-04-08 19:42:53+04:00, dlenev@stripped +11 -0
WL#3726 "DDL locking for all metadata objects".
Work in progress.
Ensure that threads waiting for metadata locks can be killed.
mysql-test/r/kill.result@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +68 -0
Added tests checking that DDL and DML statements waiting for metadata
locks can be interrupted by KILL command.
mysql-test/t/kill.test@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +195 -0
Added tests checking that DDL and DML statements waiting for metadata
locks can be interrupted by KILL command.
sql/lock.cc@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +3 -2
mdl_acquire_exclusive_locks() now returns TRUE in situations when our
operation requested was aborted due to thread/statement being killed.
sql/meta_lock.cc@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +86 -33
mdl_acquire_exclusive_locks(), mdl_upgrade_shared_lock_to_exclusive(),
mdl_wait_for_locks():
When entering long waits on condition variable register this fact
using THD::enter_cond() method to allow other threads to wake up
ours. Being woken up after waiting on condition variable check
the value of THD::killed flag to be able to distinguish situations
in which our thread was killed from other cases. When killed inform
caller that we have failed to perform requested operation using
return value.
sql/meta_lock.h@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +3 -3
mdl_acquire_exclusive_locks(), mdl_upgrade_shared_lock_to_exclusive(),
mdl_wait_for_locks():
These functions now return bool value which allows to distinguish
situations when requested operation succeeded from situations when
operation was aborted because our thread/statement was killed.
sql/mysql_priv.h@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +1 -3
Now wait_while_table_is_used() returns TRUE in cases when this
operation is failed since waiting thread/statement was killed.
We no longer need drop_locked_tables() and abort_locked_tables().
sql/sql_base.cc@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +37 -100
Adjusted code invoking MDL operation that can cause waiting to the
fact that those operations now return TRUE in case when they were
aborted since the waiting thread was killed.
Got rid of drop_locked_tables() and abort_locked_tables() since now
thanks to separation of metadata locks and TABLE instances we no longer
need them (we can use close_cached_table() instead).
Change tdc_wait_for_old_versions() to ensure that thread waiting in this
function can be killed (use THD::enter_cond()/exit_cond() and check
THD::killed flag around waiting on condition variable).
sql/sql_delete.cc@stripped, 2008-04-08 19:42:48+04:00, dlenev@stripped +2 -1
mdl_acquire_exclusive_locks() now returns TRUE in situations when our
operation requested was aborted due to thread/statement being killed.
sql/sql_partition.cc@stripped, 2008-04-08 19:42:49+04:00, dlenev@stripped +3 -3
Now abort_and_upgrade_lock() can return non-0 values.
sql/sql_show.cc@stripped, 2008-04-08 19:42:49+04:00, dlenev@stripped +5 -1
mdl_wait_for_locks() now returns TRUE in situations when our waiting
was aborted due to thread/statement being killed.
sql/sql_table.cc@stripped, 2008-04-08 19:42:49+04:00, dlenev@stripped +77 -40
Since we have separated metadata locks and TABLE instances we can
some code in mysql_rm_table_part2() can be simplified.
Adjusted places that use mdl_upgrade_shared_lock_to_exclusive() to take
into account situations when thread upgrading metadata lock is killed.
As part of these efforts changed close_cached_table() and
wait_while_table_is_used() to return TRUE as indication of aborted
operation.
diff -Nrup a/mysql-test/r/kill.result b/mysql-test/r/kill.result
--- a/mysql-test/r/kill.result 2007-11-22 03:52:41 +03:00
+++ b/mysql-test/r/kill.result 2008-04-08 19:42:48 +04:00
@@ -136,3 +136,71 @@ KILL CONNECTION_ID();
# of close of the connection socket
SELECT 1;
Got one of the listed errors
+#
+# Additional test for WL#3726 "DDL locking for all metadata objects"
+# Check that DDL and DML statements waiting for metadata locks can
+# be killed. Note that we don't cover all situations here since it
+# can be tricky to write test case for some of them (e.g. REPAIR or
+# ALTER and other statements under LOCK TABLES).
+#
+# QQ: May be there is a better place for these tests ?
+drop tables if exists t1, t2, t3;
+create table t1 (i int primary key);
+# Test for RENAME TABLE
+lock table t1 read;
+rename table t1 to t2;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Test for DROP TABLE
+drop table t1;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Test for CREATE TRIGGER
+create trigger t1_bi before insert on t1 for each row set @a:=1;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+#
+# Tests for various kinds of ALTER TABLE
+#
+# Full-blown ALTER which should copy table
+alter table t1 add column j int;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Two kinds of simple ALTER
+alter table t1 rename to t2;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+alter table t1 disable keys;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Fast ALTER
+alter table t1 alter column i set default 100;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Special case which is triggered only for MERGE tables.
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+alter table t2 alter column i set default 100;;
+kill query ID;
+ERROR 70100: Query execution was interrupted
+# Test for DML waiting for meta-data lock
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+rename tables t1 to t3, t2 to t1;;
+insert into t2 values (1);;
+kill query ID2;
+ERROR 70100: Query execution was interrupted
+unlock tables;
+# Test for DML waiting for tables to be flushed
+lock tables t1 read;
+flush tables;;
+select * from t1;;
+kill query ID2;
+ERROR 70100: Query execution was interrupted
+unlock tables;
+# Cleanup.
+drop table t3;
+drop table t1;
diff -Nrup a/mysql-test/t/kill.test b/mysql-test/t/kill.test
--- a/mysql-test/t/kill.test 2007-11-22 03:52:41 +03:00
+++ b/mysql-test/t/kill.test 2008-04-08 19:42:48 +04:00
@@ -321,3 +321,198 @@ KILL CONNECTION_ID();
--echo # of close of the connection socket
--error 2013, 2006
SELECT 1;
+
+
+--echo #
+--echo # Additional test for WL#3726 "DDL locking for all metadata objects"
+--echo # Check that DDL and DML statements waiting for metadata locks can
+--echo # be killed. Note that we don't cover all situations here since it
+--echo # can be tricky to write test case for some of them (e.g. REPAIR or
+--echo # ALTER and other statements under LOCK TABLES).
+--echo #
+--echo # QQ: May be there is a better place for these tests ?
+
+connection default;
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+
+create table t1 (i int primary key);
+connect (blocker, localhost, root, , );
+connect (dml, localhost, root, , );
+connect (ddl, localhost, root, , );
+
+--echo # Test for RENAME TABLE
+connection blocker;
+lock table t1 read;
+connection ddl;
+let $ID= `select connection_id()`;
+--send rename table t1 to t2;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DROP TABLE
+--send drop table t1;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "drop table t1";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for CREATE TRIGGER
+--send create trigger t1_bi before insert on t1 for each row set @a:=1;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "create trigger t1_bi before insert on t1 for each row set @a:=1";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo #
+--echo # Tests for various kinds of ALTER TABLE
+--echo #
+--echo # Full-blown ALTER which should copy table
+--send alter table t1 add column j int;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 add column j int";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Two kinds of simple ALTER
+--send alter table t1 rename to t2;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 rename to t2";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--send alter table t1 disable keys;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 disable keys";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Fast ALTER
+--send alter table t1 alter column i set default 100;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 alter column i set default 100";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Special case which is triggered only for MERGE tables.
+connection blocker;
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+connection ddl;
+--send alter table t2 alter column i set default 100;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t2 alter column i set default 100";
+--replace_result $ID ID
+eval kill query $ID;
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DML waiting for meta-data lock
+connection blocker;
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+connection ddl;
+# Let us add pending exclusive metadata lock on t2
+--send rename tables t1 to t3, t2 to t1;
+connection dml;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "rename tables t1 to t3, t2 to t1";
+let $ID2= `select connection_id()`;
+--send insert into t2 values (1);
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "insert into t2 values (1)";
+--replace_result $ID2 ID2
+eval kill query $ID2;
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+connection blocker;
+unlock tables;
+connection ddl;
+--reap
+
+--echo # Test for DML waiting for tables to be flushed
+connection blocker;
+lock tables t1 read;
+connection ddl;
+# Let us locked table t1 as old
+--send flush tables;
+connection dml;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Flushing tables" and
+ info = "flush tables";
+--send select * from t1;
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "select * from t1";
+--replace_result $ID2 ID2
+eval kill query $ID2;
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+connection blocker;
+unlock tables;
+connection ddl;
+--reap
+
+--echo # Cleanup.
+connection default;
+drop table t3;
+drop table t1;
diff -Nrup a/sql/lock.cc b/sql/lock.cc
--- a/sql/lock.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/lock.cc 2008-04-08 19:42:48 +04:00
@@ -948,7 +948,7 @@ static MYSQL_LOCK *get_lock_data(THD *th
@retval
0 ok
@retval
- 1 Fatal error (end of memory ?)
+ 1 error (OOM or thread was killed)
*/
bool lock_table_names(THD *thd, TABLE_LIST *table_list)
@@ -968,7 +968,8 @@ bool lock_table_names(THD *thd, TABLE_LI
mdl_set_lock_type(mdl, MDL_EXCLUSIVE);
mdl_add_lock(&thd->mdl_context, mdl);
}
- mdl_acquire_exclusive_locks(&thd->mdl_context);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return 1;
return 0;
end:
diff -Nrup a/sql/meta_lock.cc b/sql/meta_lock.cc
--- a/sql/meta_lock.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/meta_lock.cc 2008-04-08 19:42:48 +04:00
@@ -463,6 +463,9 @@ bool mdl_acquire_shared_lock(MDL_EL *l)
}
+static void release_lock(MDL_EL *l);
+
+
/**
Acquire exclusive locks. The context must contain the list of
locks to be acquired. There must be no granted locks in the
@@ -473,28 +476,32 @@ bool mdl_acquire_shared_lock(MDL_EL *l)
@param context A context containing requests for exclusive locks
The context may not have other lock requests.
+
+ @note In case of failure (for example, if our thread was killed)
+ resets lock requests back to their initial state (MDL_SHARED
+ and MDL_NORMAL_PRIO).
+
+ @retval FALSE Success
+ @retval TRUE Failure (thread was killed)
*/
-void mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
{
MDL_EL *l, *lh;
MDL_LOCK *lock;
bool signalled= FALSE;
- const char *proc_info;
+ const char *old_msg;
I_P_List_iterator<MDL_EL, MDL_EL_context> it(context->locks);
+ THD *thd= context->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.
- */
- proc_info= context->thd->proc_info;
- thd_proc_info(context->thd, "Waiting for table");
+ DBUG_ASSERT(thd == current_thd);
safe_mutex_assert_not_owner(&LOCK_open);
pthread_mutex_lock(&LOCK_mdl);
+
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
while ((l= it++))
{
DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
@@ -523,8 +530,7 @@ void mdl_acquire_exclusive_locks(MDL_CON
if ((lh= lock->active_readers.head()))
{
- signalled= notify_thread_having_shared_lock(context->thd,
- lh->ctx->thd);
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
break;
}
else if (!lock->active_writers.is_empty() ||
@@ -555,6 +561,24 @@ void mdl_acquire_exclusive_locks(MDL_CON
set_timespec(abstime, 10);
pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
}
+ if (thd->killed)
+ {
+ /* Remove our pending lock requests from the locks. */
+ it.rewind();
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+ release_lock(l);
+ /* Return lock request to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ context->locks.remove(l);
+ }
+ /* Pending requests for shared locks can be satisfied now. */
+ pthread_cond_broadcast(&COND_mdl);
+ thd->exit_cond(old_msg);
+ return TRUE;
+ }
}
it.rewind();
while ((l= it++))
@@ -567,8 +591,9 @@ void mdl_acquire_exclusive_locks(MDL_CON
(*lock->cached_object_release_hook)(l->lock->cached_object);
lock->cached_object= NULL;
}
- pthread_mutex_unlock(&LOCK_mdl);
- thd_proc_info(context->thd, proc_info);
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return FALSE;
}
@@ -582,9 +607,16 @@ void mdl_acquire_exclusive_locks(MDL_CON
@param type Id of object type
@param db Name of the database
@param name Name of the object
+
+ @note In case of failure to upgrade locks (e.g. because upgrader
+ was killed) leaves locks in their original state (locked
+ in shared mode).
+
+ @retval FALSE Success
+ @retval TRUE Failure (thread was killed)
*/
-void mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
const char *db, const char *name)
{
char key[MAX_DBKEY_LENGTH];
@@ -593,11 +625,13 @@ void mdl_upgrade_shared_lock_to_exclusiv
MDL_EL *l, *lh;
MDL_LOCK *lock;
I_P_List_iterator<MDL_EL, MDL_EL_context> it(context->locks);
+ const char *old_msg;
+ THD *thd= context->thd;
DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive");
DBUG_PRINT("enter", ("db=%s name=%s", db, name));
- DBUG_ASSERT(context->thd == current_thd);
+ DBUG_ASSERT(thd == current_thd);
int4store(key, type);
key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
@@ -605,6 +639,9 @@ void mdl_upgrade_shared_lock_to_exclusiv
safe_mutex_assert_not_owner(&LOCK_open);
pthread_mutex_lock(&LOCK_mdl);
+
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
while ((l= it++))
if (l->key_length == key_length && !memcmp(l->key, key, key_length)
&&
l->type == MDL_SHARED)
@@ -632,8 +669,7 @@ void mdl_upgrade_shared_lock_to_exclusiv
if ((lh= lock->active_readers.head()))
{
DBUG_PRINT("info", ("found active readers"));
- signalled= notify_thread_having_shared_lock(context->thd,
- lh->ctx->thd);
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
break;
}
else if (!lock->active_writers.is_empty())
@@ -661,6 +697,23 @@ void mdl_upgrade_shared_lock_to_exclusiv
DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping"));
pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
}
+ if (thd->killed)
+ {
+ it.rewind();
+ while ((l= it++))
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+ l->state= MDL_ACQUIRED;
+ lock= l->lock;
+ lock->active_readers_waiting_upgrade.remove(l);
+ lock->active_readers.push_front(l);
+ }
+ /* Pending requests for shared locks can be satisfied now. */
+ pthread_cond_broadcast(&COND_mdl);
+ thd->exit_cond(old_msg);
+ DBUG_RETURN(TRUE);
+ }
}
it.rewind();
@@ -678,8 +731,9 @@ void mdl_upgrade_shared_lock_to_exclusiv
lock->cached_object= 0;
}
- pthread_mutex_unlock(&LOCK_mdl);
- DBUG_VOID_RETURN;
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ DBUG_RETURN(FALSE);
}
@@ -749,26 +803,23 @@ bool mdl_try_acquire_exclusive_lock(MDL_
Does not acquire the locks!
@param context Context with which lock requests are associated.
+
+ @retval FALSE Success. One can try to obtain metadata locks.
+ @retval TRUE Failure (thread was killed)
*/
-void mdl_wait_for_locks(MDL_CONTEXT *context)
+bool mdl_wait_for_locks(MDL_CONTEXT *context)
{
MDL_EL *l;
MDL_LOCK *lock;
- const char *proc_info;
I_P_List_iterator<MDL_EL, MDL_EL_context> it(context->locks);
+ const char *old_msg;
+ THD *thd= context->thd;
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= context->thd->proc_info;
- thd_proc_info(context->thd, "Waiting for table");
+ DBUG_ASSERT(thd == current_thd);
- while (1)
+ while (!thd->killed)
{
/*
We have to check if there are some HANDLERs open by this thread
@@ -782,6 +833,7 @@ void mdl_wait_for_locks(MDL_CONTEXT *con
*/
mysql_ha_flush(context->thd);
pthread_mutex_lock(&LOCK_mdl);
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
it.rewind();
while ((l= it++))
{
@@ -798,9 +850,10 @@ void mdl_wait_for_locks(MDL_CONTEXT *con
break;
}
pthread_cond_wait(&COND_mdl, &LOCK_mdl);
- pthread_mutex_unlock(&LOCK_mdl);
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
}
- thd_proc_info(context->thd, proc_info);
+ return thd->killed;
}
diff -Nrup a/sql/meta_lock.h b/sql/meta_lock.h
--- a/sql/meta_lock.h 2008-03-28 20:46:58 +03:00
+++ b/sql/meta_lock.h 2008-04-08 19:42:48 +04:00
@@ -170,12 +170,12 @@ inline void mdl_set_lock_priority(MDL_EL
}
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,
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
+bool 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);
+bool mdl_wait_for_locks(MDL_CONTEXT *context);
void mdl_release_locks(MDL_CONTEXT *context);
void mdl_release_exclusive_locks(MDL_CONTEXT *context);
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h 2008-03-06 21:40:06 +03:00
+++ b/sql/mysql_priv.h 2008-04-08 19:42:48 +04:00
@@ -1083,7 +1083,7 @@ bool check_dup(const char *db, const cha
bool compare_record(TABLE *table);
bool append_file_to_dir(THD *thd, const char **filename_ptr,
const char *table_name);
-void wait_while_table_is_used(THD *thd, TABLE *table,
+bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function);
bool table_def_init(void);
void table_def_free(void);
@@ -1286,8 +1286,6 @@ bool open_new_frm(THD *thd, TABLE_SHARE
bool wait_for_tables(THD *thd);
bool table_is_used(TABLE *table, bool wait_for_name_lock);
void unlock_locked_tables(THD *thd);
-void drop_locked_tables(THD *thd, const char *db, const char *table_name);
-void abort_locked_tables(THD *thd,const char *db, const char *table_name);
void execute_init_command(THD *thd, sys_var_str *init_command_var,
rw_lock_t *var_mutex);
extern Field *not_found_field;
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/sql_base.cc 2008-04-08 19:42:48 +04:00
@@ -109,7 +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 tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
static bool
has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables);
@@ -1072,6 +1072,12 @@ bool close_cached_tables(THD *thd, TABLE
{
mysql_lock_abort(thd, table, TRUE);
VOID(pthread_mutex_unlock(&LOCK_open));
+ /*
+ FIXME: Adjust the code to scenario in which thread doing
+ FLUSH is killed once code responsible for global
+ shared metadata lock is in place (at this point we
+ are likely to rework significant part of this code).
+ */
mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
table->s->db.str,
table->s->table_name.str);
@@ -2346,8 +2352,10 @@ bool name_lock_locked_table(THD *thd, TA
Ensures that table is opened only by this thread and that no
other statement will open this table.
*/
- wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
- DBUG_RETURN(FALSE);
+ if (wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN))
+ DBUG_RETURN(TRUE);
+ else
+ DBUG_RETURN(FALSE);
}
DBUG_RETURN(TRUE);
@@ -2762,8 +2770,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
if (table_list->open_table_type)
{
mdl_set_lock_type(mdl, MDL_EXCLUSIVE);
- // This case can be significantly optimized
- mdl_acquire_exclusive_locks(&thd->mdl_context);
+ /* TODO: This case can be significantly optimized. */
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(0);
}
else
{
@@ -3673,81 +3682,6 @@ void unlock_locked_tables(THD *thd)
}
-/**
- Remove tables which we are going to drop while being under LOCK TABLES
- from the list of open tables and from the lock object.
-
- @param thd Thread thandler
- @param db Database
- @param table_name Table name
-
- @note Metadata locks are left intact.
-*/
-
-void drop_locked_tables(THD *thd,const char *db, const char *table_name)
-{
- TABLE *table,*next,**prev;
- prev= &thd->open_tables;
- DBUG_ENTER("drop_locked_tables");
-
- /*
- Note that we need to hold LOCK_open while changing the
- open_tables list. Another thread may work on it.
- (See: remove_table_from_cache(), mysql_wait_completed_table())
- Closing a MERGE child before the parent would be fatal if the
- other thread tries to abort the MERGE lock in between.
- */
- for (table= thd->open_tables; table ; table=next)
- {
- next=table->next;
- if (!strcmp(table->s->table_name.str, table_name) &&
- !strcmp(table->s->db.str, db))
- {
- /* If MERGE child, forward lock handling to parent. */
- mysql_lock_remove(thd, thd->locked_tables,
- table->parent ? table->parent : table, TRUE);
- /*
- When closing a MERGE parent or child table, detach the children first.
- Clear child table references in case this object is opened again.
- */
- if (table->child_l || table->parent)
- detach_merge_children(table, TRUE);
-
- free_cache_entry(table);
- }
- else
- {
- *prev=table;
- prev= &table->next;
- }
- }
- *prev=0;
- DBUG_VOID_RETURN;
-}
-
-
-/*
- If we have the table open, which only happens when a LOCK TABLE has been
- done on the table, change the lock type to a lock that will abort all
- other threads trying to get the lock.
-*/
-
-void abort_locked_tables(THD *thd,const char *db, const char *table_name)
-{
- TABLE *table;
- for (table= thd->open_tables; table ; table= table->next)
- {
- if (!strcmp(table->s->table_name.str, table_name) &&
- !strcmp(table->s->db.str, db))
- {
- /* If MERGE child, forward lock handling to parent. */
- mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
- break;
- }
- }
-}
-
-
/*
Function to assign a new table map id to a table share.
@@ -4131,15 +4065,15 @@ static bool handle_failed_open_table_att
switch (action)
{
case OT_BACK_OFF_AND_RETRY:
- mdl_wait_for_locks(&thd->mdl_context);
- tdc_wait_for_old_versions(thd, &thd->mdl_context);
+ result= (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:
mdl_set_lock_type(table->mdl, MDL_EXCLUSIVE);
mdl_add_lock(&thd->mdl_context, table->mdl);
- mdl_acquire_exclusive_locks(&thd->mdl_context);
-
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return TRUE;
pthread_mutex_lock(&LOCK_open);
expel_table_from_cache(0, table->db, table->table_name);
ha_create_table_from_engine(thd, table->db, table->table_name);
@@ -4152,8 +4086,8 @@ static bool handle_failed_open_table_att
case OT_REPAIR:
mdl_set_lock_type(table->mdl, MDL_EXCLUSIVE);
mdl_add_lock(&thd->mdl_context, table->mdl);
- mdl_acquire_exclusive_locks(&thd->mdl_context);
-
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return TRUE;
pthread_mutex_lock(&LOCK_open);
expel_table_from_cache(0, table->db, table->table_name);
pthread_mutex_unlock(&LOCK_open);
@@ -8635,21 +8569,14 @@ void expel_table_from_cache(THD *leave_t
@param context Metadata locking context with locks.
*/
-static void tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
{
MDL_EL *l;
TABLE_SHARE *share;
- const char *proc_info;
+ const char *old_msg;
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)
+ while (!thd->killed)
{
/*
Here we have situation as in mdl_wait_for_locks() we need to
@@ -8659,6 +8586,7 @@ static void tdc_wait_for_old_versions(TH
*/
mysql_ha_flush(thd);
pthread_mutex_lock(&LOCK_open);
+
I_P_List_iterator<MDL_EL, MDL_EL_context> it= mdl_get_locks(context);
while ((l= it++))
{
@@ -8674,10 +8602,12 @@ static void tdc_wait_for_old_versions(TH
pthread_mutex_unlock(&LOCK_open);
break;
}
+ old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table");
pthread_cond_wait(&COND_refresh, &LOCK_open);
- pthread_mutex_unlock(&LOCK_open);
+ /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */
+ thd->exit_cond(old_msg);
}
- thd_proc_info(thd, proc_info);
+ return thd->killed;
}
@@ -8816,8 +8746,15 @@ int abort_and_upgrade_lock(ALTER_PARTITI
/* If MERGE child, forward lock handling to parent. */
mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
lpt->table, TRUE);
- mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0, lpt->db,
- lpt->table_name);
+ if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0,
+ lpt->db, lpt->table_name))
+ {
+ mysql_lock_downgrade_write(lpt->thd,
+ lpt->table->parent ? lpt->table->parent :
+ lpt->table,
+ lpt->old_lock_type);
+ DBUG_RETURN(1);
+ }
VOID(pthread_mutex_lock(&LOCK_open));
expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
VOID(pthread_mutex_unlock(&LOCK_open));
diff -Nrup a/sql/sql_delete.cc b/sql/sql_delete.cc
--- a/sql/sql_delete.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/sql_delete.cc 2008-04-08 19:42:48 +04:00
@@ -1024,7 +1024,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST
mdl= mdl_alloc_lock(0, table_list->db, table_list->table_name,
thd->mem_root);
mdl_set_lock_type(mdl, MDL_EXCLUSIVE);
mdl_add_lock(&thd->mdl_context, mdl);
- mdl_acquire_exclusive_locks(&thd->mdl_context);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(TRUE);
VOID(pthread_mutex_lock(&LOCK_open));
expel_table_from_cache(0, table_list->db, table_list->table_name);
VOID(pthread_mutex_unlock(&LOCK_open));
diff -Nrup a/sql/sql_partition.cc b/sql/sql_partition.cc
--- a/sql/sql_partition.cc 2008-02-18 17:31:35 +03:00
+++ b/sql/sql_partition.cc 2008-04-08 19:42:49 +04:00
@@ -6177,7 +6177,7 @@ uint fast_alter_partition_table(THD *thd
write_log_drop_partition(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_3") ||
(not_completed= FALSE) ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_5") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_6") ||
@@ -6242,7 +6242,7 @@ uint fast_alter_partition_table(THD *thd
ERROR_INJECT_CRASH("crash_add_partition_2") ||
mysql_change_partitions(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_3") ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_4") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_5") ||
@@ -6330,7 +6330,7 @@ uint fast_alter_partition_table(THD *thd
write_log_final_change_partition(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_4") ||
(not_completed= FALSE) ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_6") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_7") ||
diff -Nrup a/sql/sql_show.cc b/sql/sql_show.cc
--- a/sql/sql_show.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/sql_show.cc 2008-04-08 19:42:49 +04:00
@@ -3061,7 +3061,11 @@ static int fill_schema_table_from_frm(TH
is LOCK_open) instead of obtaning full-blown metadata lock.
*/
while (mdl_acquire_shared_lock(&mdl))
- mdl_wait_for_locks(&thd->mdl_context);
+ if (mdl_wait_for_locks(&thd->mdl_context))
+ {
+ /* Probably we have been killed, let the caller handle the situation. */
+ return 1;
+ }
if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY)
{
diff -Nrup a/sql/sql_table.cc b/sql/sql_table.cc
--- a/sql/sql_table.cc 2008-03-28 20:46:58 +03:00
+++ b/sql/sql_table.cc 2008-04-08 19:42:49 +04:00
@@ -53,6 +53,7 @@ static bool
mysql_prepare_alter_table(THD *thd, TABLE *table,
HA_CREATE_INFO *create_info,
Alter_info *alter_info);
+static bool close_cached_table(THD *thd, TABLE *table);
#ifndef DBUG_OFF
@@ -1654,13 +1655,12 @@ int mysql_rm_table_part2(THD *thd, TABLE
{
if (thd->locked_tables)
{
- abort_locked_tables(thd, db, table->table_name);
- mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
- db, table->table_name);
- pthread_mutex_lock(&LOCK_open);
- drop_locked_tables(thd, db, table->table_name);
- expel_table_from_cache(0, db, table->table_name);
- pthread_mutex_unlock(&LOCK_open);
+ TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name);
+ if (close_cached_table(thd, tab))
+ {
+ error= -1;
+ goto err_with_placeholders;
+ }
}
if (thd->killed)
@@ -3837,54 +3837,69 @@ mysql_rename_table(handlerton *base, con
}
-/*
- Force all other threads to stop using the table
+/**
+ Force all other threads to stop using the table by upgrading
+ metadata lock on it and remove unused TABLE instances from cache.
- SYNOPSIS
- wait_while_table_is_used()
- thd Thread handler
- table Table to remove from cache
- function HA_EXTRA_PREPARE_FOR_DELETE if table is to be deleted
- HA_EXTRA_FORCE_REOPEN if table is not be used
- NOTES
- When returning, the table will be unusable for other threads until
- the table is closed.
+ @param thd Thread handler
+ @param table Table to remove from cache
+ @param function HA_EXTRA_PREPARE_FOR_DELETE if table is to be deleted
+ HA_EXTRA_FORCE_REOPEN if table is not be used
+
+ @note When returning, the table will be unusable for other threads
+ until metadata lock is downgraded.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
*/
-void wait_while_table_is_used(THD *thd, TABLE *table,
+bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function)
{
+ enum thr_lock_type old_lock_type;
+
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
table->s->table_name.str, (ulong) table->s,
table->db_stat, table->s->version));
VOID(table->file->extra(function));
- /* Mark all tables that are in use as 'old' */
+
+ old_lock_type= table->reginfo.lock_type;
mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */
- mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
table->s->db.str,
- table->s->table_name.str);
+ if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
+ table->s->db.str,
+ table->s->table_name.str))
+ {
+ mysql_lock_downgrade_write(thd, table, old_lock_type);
+ DBUG_RETURN(TRUE);
+ }
+
VOID(pthread_mutex_lock(&LOCK_open));
expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
VOID(pthread_mutex_unlock(&LOCK_open));
- DBUG_VOID_RETURN;
+ DBUG_RETURN(FALSE);
}
-/*
- Close a cached table
- SYNOPSIS
- close_cached_table()
- thd Thread handler
- table Table to remove from cache
+/**
+ Upgrade metadata lock on the table and close all its instances.
+
+ @param thd Thread handler
+ @param table Table to remove from cache
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
*/
-static void close_cached_table(THD *thd, TABLE *table)
+static bool close_cached_table(THD *thd, TABLE *table)
{
DBUG_ENTER("close_cached_table");
- wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE))
+ DBUG_RETURN(TRUE);
+
/* Close lock if this is not got with LOCK TABLES */
if (thd->lock)
{
@@ -3896,7 +3911,7 @@ static void close_cached_table(THD *thd,
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
unlink_open_table(thd, table, TRUE);
VOID(pthread_mutex_unlock(&LOCK_open));
- DBUG_VOID_RETURN;
+ DBUG_RETURN(FALSE);
}
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
@@ -3945,7 +3960,8 @@ static int prepare_for_repair(THD *thd,
thd->mem_root);
mdl_set_lock_type(mdl, MDL_EXCLUSIVE);
mdl_add_lock(&thd->mdl_context, mdl);
- mdl_acquire_exclusive_locks(&thd->mdl_context);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(0);
pthread_mutex_lock(&LOCK_open);
if (!(share= (get_table_share(thd, table_list, key, key_length, 0,
@@ -4009,7 +4025,8 @@ static int prepare_for_repair(THD *thd,
if (table_list->table)
{
/* If we could open the table, close it */
- close_cached_table(thd, table);
+ if (close_cached_table(thd, table))
+ goto end;
table_list->table= 0;
}
// After this point we have X mdl lock in both cases
@@ -5718,7 +5735,15 @@ int mysql_fast_or_online_alter_table(THD
The final .frm file is already created as a temporary file
and will be renamed to the original table name.
*/
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ {
+ /*
+ QQ: is there any way to rollback results of previous
+ phases of alter
+ */
+ error= 1;
+ goto err;
+ }
VOID(pthread_mutex_lock(&LOCK_open));
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
keys_onoff);
@@ -6522,13 +6547,15 @@ view_err:
case LEAVE_AS_IS:
break;
case ENABLE:
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err;
DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
/* COND_refresh will be signaled in close_thread_tables() */
break;
case DISABLE:
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err;
error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
/* COND_refresh will be signaled in close_thread_tables() */
break;
@@ -6551,8 +6578,13 @@ view_err:
/*
Then do a 'simple' rename of the table. First we need to close all
instances of 'source' table.
+ Note that if close_cached_table() returns error here (i.e. if
+ this thread was killed) then it must be that previous step of
+ simple rename did nothing and therefore we can safely reture
+ without additional clean-up.
*/
- close_cached_table(thd, table);
+ if (close_cached_table(thd, table))
+ goto err;
/*
Then, we want check once again that target table does not exist.
Actually the order of these two steps does not matter since
@@ -6872,7 +6904,8 @@ view_err:
}
else
{
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err1;
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
alter_info->keys_onoff);
error= ha_commit_stmt(thd);
@@ -6941,7 +6974,11 @@ view_err:
if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name);
- wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE))
+ {
+ VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
+ goto err;
+ }
VOID(pthread_mutex_lock(&LOCK_open));
| Thread |
|---|
| • bk commit into 6.0 tree (dlenev:1.2565) WL#3726 | dlenev | 8 Apr |