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-18 12:34:26+04:00, dlenev@stripped +20 -0
WL#3726 "DDL locking for all metadata objects".
Work in progress.
Ensure that FLUSH TABLES WITH GLOBAL READ LOCK cannot sneak in at
the moment when FLUSH or ALTER executed under LOCK TABLES is
reopening tables and they are protected by only by metadata lock.
In order to achieve this FLUSH TABLES WITH GLOBAL READ LOCK now
takes global shared metadata lock before flushing all tables.
Also under LOCK TABLES we prohibit flushing of read-locked tables
since this might lead to deadlock. As consequence it is no longer
allowed to do FLUSH TABLES WITH READ LOCK under LOCK TABLES.
QQ: Global shared metadata lock, global read lock and DDL blocker
look somewhat redundant. We should investigate if we can get
rid of some of these entities/associated code...
mysql-test/r/flush.result@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +5 -0
FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES.
Updated test accordingly.
mysql-test/r/flush_table.result@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +5 -12
Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked
for read. Updated test accordingly.
mysql-test/t/flush.test@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +9 -5
FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES.
Updated test accordingly.
mysql-test/t/flush_table.test@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +8 -17
Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked
for read. Updated test accordingly.
mysql-test/t/lock_multi.test@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +9 -17
Adjusted test case to the changes of status in various places caused
by change in implementation FLUSH TABLES WITH READ LOCK, which is now
takes global metadata lock before flushing tables and therefore waits
on at these places.
mysql-test/t/trigger_notembedded.test@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +1 -1
Adjusted test case to the changes of status in various places caused
by change in implementation FLUSH TABLES WITH READ LOCK, which is now
takes global metadata lock before flushing tables and therefore waits
on at these places.
sql/ha_ndbcluster.cc@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +2 -2
close_cached_tables() no longer has wait_for_placeholder argument.
Instead of relying on this parameter and related behavior FLUSH TABLES
WITH READ LOCK now takes global shared metadata lock.
sql/ha_ndbcluster_binlog.cc@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +9 -9
close_cached_tables() no longer has wait_for_placeholder argument.
Instead of relying on this parameter and related behavior FLUSH TABLES
WITH READ LOCK now takes global shared metadata lock.
sql/lock.cc@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +21 -0
lock_global_read_lock(), unlock_global_read_lock():
To avoid problems with global read lock sneaking in at the moment
when we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES and
when tables being reopened are protected only by metadata locks
we also have to take global shared meta data lock.
sql/meta_lock.cc@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +181 -25
Introduced concept of global shared metadata lock and upgradable shared locks.
Global shared lock is incompatible with exclusive locks and with upgradable
shared locks (which are the only shared locks now upgradable to exclusive).
This allows to use global shared metadata lock in FLUSH TABLES WITH GLOBAL
READ LOCK implementation to ensure that it cannot sneak in at the moment
when FLUSH or ALTER executed under LOCK TABLES is reopening tables and they
are protected by only by metadata lock.
Because of introduction of this new kinds of locks mdl_acquire_shared_lock()
can now also return error in case other than conflicting lock held by other
connection, so its signature was changed.
sql/meta_lock.h@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +32 -1
Introduced concept of global shared metadata lock and upgradable shared locks.
Global shared lock is incompatible with exclusive locks and with upgradable
shared locks (which are the only shared locks now upgradable to exclusive).
This allows to use global shared metadata lock in FLUSH TABLES WITH GLOBAL
READ LOCK implementation to ensure that it cannot sneak in at the moment
when FLUSH or ALTER executed under LOCK TABLES is reopening tables and they
are protected by only by metadata lock.
Because of introduction of this new kinds of locks mdl_acquire_shared_lock()
can now also return error in case other than conflicting lock held by other
connection, so its signature was changed.
sql/mysql_priv.h@stripped, 2008-04-18 12:34:17+04:00, dlenev@stripped +2 -2
reopen_tables(), close_cached_tables():
No longer have mark_share_as_old and wait_for_placeholder arguments.
Instead of relying on this parameters and related behavior
FLUSH TABLES WITH READ LOCK now takes global shared metadata lock.
sql/set_var.cc@stripped, 2008-04-18 12:34:18+04:00, dlenev@stripped +1 -1
close_cached_tables() no longer has wait_for_placeholder argument.
Instead of relying on this parameter and related behavior FLUSH TABLES
WITH READ LOCK now takes global shared metadata lock.
sql/sql_base.cc@stripped, 2008-04-18 12:34:18+04:00, dlenev@stripped +192 -166
close_cached_tables():
Get rid of wait_for_placeholder parameter. Instead of relying on
it and related behavior FLUSH TABLES WITH READ LOCK now takes global
shared metadata lock.
For FLUSH TABLES statement which mention particular tables now does
metadata lock upgrade, flushing and waiting for tables to be flushed
only for tables explicitly mentioned. Without this change metadata
upgrade which happens during FLUSH or waiting for tables to be flushed
might lead to deadlocks in certain situations.
open_table():
Mark metadata locks as upgradable according to flag in table list
element. We need this additional flag in TABLE_LIST since unlike
attribute of metadata lock its value is not reset during back-off
process.
Also now mdl_acquire_shared_lock() can return other error other
than "there is conflicting lock -- retry" so its signature was
adjusted accordingly.
reopen_tables():
Removed mark_share_as_old parameter. Instead of relying on it and
related behavior FLUSH TABLES WITH READ LOCK now takes global shared
metadata lock.
sql/sql_parse.cc@stripped, 2008-04-18 12:34:18+04:00, dlenev@stripped +74 -16
LOCK TABLES needs to take upgradable shared metadata lock on tables
which are going to be locked for write since for these tables we allow
statements upgrade metadata locks to exclusive such as ALTER TABLE or
FLUSH TABLE.
Now when one does FLUSH TABLES under LOCK TABLES tables to be flushed
should be write-locked. This is required since in otherwise metadata
lock upgrade which happens during FLUSH might lead to deadlock. As
consequence one no longer can issue FLUSH TABLES WITH READ LOCK under
LOCK TABLES.
sql/sql_show.cc@stripped, 2008-04-18 12:34:18+04:00, dlenev@stripped +16 -4
Now mdl_acquire_shared_lock() can also return other error other
than "there is conflicting lock -- retry" so its signature was
adjusted accordingly.
sql/sql_table.cc@stripped, 2008-04-18 12:34:19+04:00, dlenev@stripped +4 -1
mysql_repair_table(), mysql_alter_table():
Mark subject tables of this operations as requiring upgradable
shared metadata (instead of plain shared metadata lock).
mysql_alter_table():
Also reopen_tables() no longer has mark_share_as_old argument.
Instead of relying on this parameter and related behavior
FLUSH TABLES WITH READ LOCK now takes global shared metadata
lock.
sql/sql_trigger.cc@stripped, 2008-04-18 12:34:19+04:00, dlenev@stripped +1 -1
reopen_tables() no longer has mark_share_as_old argument.
Instead of relying on this parameter and related behavior
FLUSH TABLES WITH READ LOCK now takes global shared metadata
lock.
sql/sql_view.cc@stripped, 2008-04-18 12:34:19+04:00, dlenev@stripped +3 -0
Since we allow to lock view with LOCK TABLES for write and then
everywhere treat its underlying tables as ordinary tables locked
for write it is important to set TABLE_LIST::mdl_upgradable properly
for these tables.
sql/table.h@stripped, 2008-04-18 12:34:19+04:00, dlenev@stripped +5 -0
Introduced new TABLE_LIST::mdl_upgradable member for marking elements
of table list for which we need to take upgradable shared metadata
lock instead of plain shared metadata lock.
diff -Nrup a/mysql-test/r/flush.result b/mysql-test/r/flush.result
--- a/mysql-test/r/flush.result 2007-11-22 15:32:32 +03:00
+++ b/mysql-test/r/flush.result 2008-04-18 12:34:17 +04:00
@@ -33,6 +33,9 @@ flush tables with read lock;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
lock table t1 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+unlock tables;
+flush tables with read lock;
lock table t1 write;
ERROR HY000: Can't execute the query because you have a conflicting read lock
lock table t1 read;
@@ -46,6 +49,7 @@ flush tables with read lock;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
lock table t1 read, t2 read, t3 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
drop table t1, t2, t3;
create table t1 (c1 int);
@@ -69,6 +73,7 @@ ERROR HY000: Can't execute the given com
unlock tables;
lock tables t1 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
drop table t1, t2;
set session low_priority_updates=default;
diff -Nrup a/mysql-test/r/flush_table.result b/mysql-test/r/flush_table.result
--- a/mysql-test/r/flush_table.result 2006-10-05 01:48:21 +04:00
+++ b/mysql-test/r/flush_table.result 2008-04-18 12:34:17 +04:00
@@ -3,21 +3,14 @@ create table t1 (a int not null auto_inc
insert into t1 values(0);
lock table t1 read;
flush table t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+lock table t1 write;
+flush table t1;
check table t1;
Table Op Msg_type Msg_text
test.t1 check status OK
unlock tables;
-lock table t1 read;
-lock table t1 read;
-flush table t1;
-select * from t1;
-a
-1
-unlock tables;
-select * from t1;
-a
-1
-unlock tables;
lock table t1 write;
lock table t1 read;
flush table t1;
@@ -26,7 +19,7 @@ a
1
unlock tables;
unlock tables;
-lock table t1 read;
+lock table t1 write;
lock table t1 write;
flush table t1;
select * from t1;
diff -Nrup a/mysql-test/t/flush.test b/mysql-test/t/flush.test
--- a/mysql-test/t/flush.test 2007-11-22 15:32:32 +03:00
+++ b/mysql-test/t/flush.test 2008-04-18 12:34:17 +04:00
@@ -68,10 +68,13 @@ drop table t1;
create table t1 (c1 int);
lock table t1 write;
# Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
lock table t1 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+flush tables with read lock;
+unlock tables;
flush tables with read lock;
--error 1223
lock table t1 write;
@@ -84,12 +87,12 @@ create table t2 (c1 int);
create table t3 (c1 int);
lock table t1 read, t2 read, t3 write;
# Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
lock table t1 read, t2 read, t3 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
-# Release all table locks and the global read lock.
unlock tables;
drop table t1, t2, t3;
@@ -157,6 +160,7 @@ flush tables with read lock;
unlock tables;
lock tables t1 read;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
unlock tables;
diff -Nrup a/mysql-test/t/flush_table.test b/mysql-test/t/flush_table.test
--- a/mysql-test/t/flush_table.test 2006-01-04 13:18:53 +03:00
+++ b/mysql-test/t/flush_table.test 2008-04-18 12:34:17 +04:00
@@ -15,30 +15,21 @@ insert into t1 values(0);
# Test for with read lock + flush
lock table t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
flush table t1;
-check table t1;
unlock tables;
-# Test for with 2 read lock in different thread + flush
+# Test for with write lock + flush
-lock table t1 read;
-connect (locker,localhost,root,,test);
-connection locker;
-lock table t1 read;
-connection default;
-send flush table t1;
-connection locker;
---sleep 2
-select * from t1;
-unlock tables;
-connection default;
-reap;
-select * from t1;
+lock table t1 write;
+flush table t1;
+check table t1;
unlock tables;
# Test for with a write lock and a waiting read lock + flush
lock table t1 write;
+connect (locker,localhost,root,,test);
connection locker;
send lock table t1 read;
connection default;
@@ -51,9 +42,9 @@ reap;
unlock tables;
connection default;
-# Test for with a read lock and a waiting write lock + flush
+# Test for with a write lock and a waiting write lock + flush
-lock table t1 read;
+lock table t1 write;
connection locker;
send lock table t1 write;
connection default;
diff -Nrup a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test
--- a/mysql-test/t/lock_multi.test 2008-02-06 14:50:48 +03:00
+++ b/mysql-test/t/lock_multi.test 2008-04-18 12:34:17 +04:00
@@ -151,7 +151,7 @@ send SELECT user.Select_priv FROM user,
connection locker;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info =
+ where state = "Table lock" and info =
"SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1";
--source include/wait_condition.inc
# Make test case independent from earlier grants.
@@ -181,7 +181,7 @@ send FLUSH TABLES WITH READ LOCK;
connection writer;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+ where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
--source include/wait_condition.inc
# This must not block.
CREATE TABLE t2 (c1 int);
@@ -209,7 +209,7 @@ send FLUSH TABLES WITH READ LOCK;
connection writer;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+ where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
--source include/wait_condition.inc
--error 1100
CREATE TABLE t2 AS SELECT * FROM t1;
@@ -366,7 +366,7 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
alter table t1 add column j int;
connect (insert,localhost,root,,test,,);
@@ -374,7 +374,7 @@ connection insert;
--echo connection: insert
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
--send insert into t1 values (1,2);
--echo connection: default
@@ -421,18 +421,14 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
flush tables;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
unlock tables;
-let $wait_condition=
- select count(*) = 0 from information_schema.processlist
- where state = "Flushing tables";
---source include/wait_condition.inc
connection flush;
--reap
connection default;
@@ -491,18 +487,14 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
flush tables;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
drop table t1;
-let $wait_condition=
- select count(*) = 0 from information_schema.processlist
- where state = "Flushing tables";
---source include/wait_condition.inc
connection flush;
--reap
connection default;
diff -Nrup a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test
--- a/mysql-test/t/trigger_notembedded.test 2007-12-14 00:18:51 +03:00
+++ b/mysql-test/t/trigger_notembedded.test 2008-04-18 12:34:17 +04:00
@@ -895,7 +895,7 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
create trigger t1_bi before insert on t1 for each row begin end;
unlock tables;
diff -Nrup a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
--- a/sql/ha_ndbcluster.cc 2008-02-18 17:31:31 +03:00
+++ b/sql/ha_ndbcluster.cc 2008-04-18 12:34:17 +04:00
@@ -540,7 +540,7 @@ int ha_ndbcluster::ndb_err(NdbTransactio
bzero((char*) &table_list,sizeof(table_list));
table_list.db= m_dbname;
table_list.alias= table_list.table_name= m_tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
break;
}
default:
@@ -8664,7 +8664,7 @@ int handle_trailing_share(THD *thd, NDB_
safe_mutex_assert_owner(&LOCK_open);
else
VOID(pthread_mutex_lock(&LOCK_open));
- close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
if (!have_lock_open)
VOID(pthread_mutex_unlock(&LOCK_open));
diff -Nrup a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
--- a/sql/ha_ndbcluster_binlog.cc 2008-03-04 18:21:05 +03:00
+++ b/sql/ha_ndbcluster_binlog.cc 2008-04-18 12:34:17 +04:00
@@ -909,7 +909,7 @@ int ndbcluster_setup_binlog_table_shares
{
if (ndb_extra_logging)
sql_print_information("NDB Binlog: ndb tables writable");
- close_cached_tables(NULL, NULL, TRUE, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, TRUE, FALSE);
}
pthread_mutex_unlock(&LOCK_open);
/* Signal injector thread that all is setup */
@@ -1727,7 +1727,7 @@ ndb_handle_schema_change(THD *thd, Ndb *
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)dbname;
table_list.alias= table_list.table_name= (char *)tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
/* ndb_share reference create free */
DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
share->key, share->use_count));
@@ -1860,7 +1860,7 @@ ndb_binlog_thread_handle_schema_event(TH
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
/* ndb_share reference temporary free */
if (share)
@@ -1995,7 +1995,7 @@ ndb_binlog_thread_handle_schema_event(TH
pthread_mutex_unlock(&ndb_schema_share_mutex);
/* end protect ndb_schema_share */
- close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, FALSE, FALSE);
// fall through
case NDBEVENT::TE_ALTER:
ndb_handle_schema_change(thd, ndb, pOp, event_data);
@@ -2143,7 +2143,7 @@ ndb_binlog_thread_handle_schema_event_po
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
break;
case SOT_RENAME_TABLE:
@@ -2161,7 +2161,7 @@ ndb_binlog_thread_handle_schema_event_po
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
{
if (ndb_extra_logging > 9)
@@ -2203,7 +2203,7 @@ ndb_binlog_thread_handle_schema_event_po
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
if (share)
{
@@ -2280,7 +2280,7 @@ ndb_binlog_thread_handle_schema_event_po
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)schema->db;
table_list.alias= table_list.table_name= (char *)schema->name;
- close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
if (schema->node_id != g_ndb_cluster_connection->node_id())
{
@@ -4444,7 +4444,7 @@ restart:
if (ndb_extra_logging)
sql_print_information("NDB Binlog: ndb tables writable");
- close_cached_tables((THD*) 0, 0, (TABLE_LIST*) 0, FALSE, FALSE);
+ close_cached_tables((THD*) 0, 0, (TABLE_LIST*) 0, FALSE);
{
static char db[]= "";
diff -Nrup a/sql/lock.cc b/sql/lock.cc
--- a/sql/lock.cc 2008-04-08 19:42:48 +04:00
+++ b/sql/lock.cc 2008-04-18 12:34:17 +04:00
@@ -1136,6 +1136,25 @@ bool lock_global_read_lock(THD *thd)
thd->global_read_lock= GOT_GLOBAL_READ_LOCK;
global_read_lock++;
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
+ /*
+ When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES,
+ tables being reopened are protected only by meta-data locks at
+ some point. To avoid sneaking in with our global read lock at
+ this moment we have to take global shared meta data lock.
+ */
+ if (mdl_acquire_global_shared_lock(&thd->mdl_context))
+ {
+ /* Our thread was killed -- return back to initial state. */
+ pthread_mutex_lock(&LOCK_global_read_lock);
+ if (!(--global_read_lock))
+ {
+ DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
+ pthread_cond_broadcast(&COND_global_read_lock);
+ }
+ pthread_mutex_unlock(&LOCK_global_read_lock);
+ thd->global_read_lock= 0;
+ DBUG_RETURN(1);
+ }
}
/*
We DON'T set global_read_lock_blocks_commit now, it will be set after
@@ -1156,6 +1175,8 @@ void unlock_global_read_lock(THD *thd)
DBUG_PRINT("info",
("global_read_lock: %u global_read_lock_blocks_commit: %u",
global_read_lock, global_read_lock_blocks_commit));
+
+ mdl_release_global_shared_lock(&thd->mdl_context);
pthread_mutex_lock(&LOCK_global_read_lock);
tmp= --global_read_lock;
diff -Nrup a/sql/meta_lock.cc b/sql/meta_lock.cc
--- a/sql/meta_lock.cc 2008-04-08 19:42:48 +04:00
+++ b/sql/meta_lock.cc 2008-04-18 12:34:17 +04:00
@@ -64,6 +64,10 @@ struct MDL_LOCK
pthread_mutex_t LOCK_mdl;
pthread_cond_t COND_mdl;
HASH mdl_locks;
+uint global_shared_locks_pending;
+uint global_shared_locks_acquired;
+uint global_intention_exclusive_locks_acquired;
+
extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length,
@@ -99,6 +103,8 @@ void mdl_init()
pthread_cond_init(&COND_mdl, NULL);
hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0,
mdl_locks_key, 0, 0);
+ global_shared_locks_pending= global_shared_locks_acquired= 0;
+ global_intention_exclusive_locks_acquired= 0;
}
@@ -137,6 +143,7 @@ void mdl_context_init(MDL_CONTEXT *conte
{
DBUG_ASSERT(context->locks.is_empty());
context->thd= thd;
+ context->has_global_shared_lock= FALSE;
}
@@ -155,6 +162,7 @@ void mdl_context_init(MDL_CONTEXT *conte
void mdl_context_destroy(MDL_CONTEXT *context)
{
DBUG_ASSERT(context->locks.is_empty());
+ DBUG_ASSERT(!context->has_global_shared_lock);
}
@@ -259,6 +267,7 @@ void mdl_init_lock(MDL_EL *mdl, char *ke
mdl->type= MDL_SHARED;
mdl->state= MDL_PENDING;
mdl->prio= MDL_NORMAL_PRIO;
+ mdl->upgradable= FALSE;
#ifndef DBUG_OFF
mdl->ctx= 0;
mdl->lock= 0;
@@ -357,17 +366,26 @@ void mdl_add_lock(MDL_CONTEXT *context,
we release all the locks acquired so-far but do not free them, since
we know that the respective lock requests will be used again.
+ Also resets lock requests back to their initial state (i.e.
+ sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+
@param context Context to be cleared.
*/
void mdl_free_locks(MDL_CONTEXT *context)
{
-#ifndef DBUG_OFF
MDL_EL *l;
I_P_List_iterator<MDL_EL, MDL_EL_context> it(context->locks);
while ((l= it++))
+ {
+ /* Reset lock request back to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
+#ifndef DBUG_OFF
l->ctx= 0;
#endif
+ }
context->locks.empty();
}
@@ -404,26 +422,42 @@ static void release_lock_object(MDL_LOCK
This function must be called after the lock is added to a context.
- @param lock Lock request object for lock to be acquired
-
- @retval FALSE success
- @retval TRUE a conflicting lock exists. Another attempt
- should be made after releasing all current
- locks and waiting for conflicting lock go
- away (using mdl_wait_for_locks()).
+ @param lock [in] Lock request object for lock to be acquired
+ @param retry [out] Indicates that conflicting lock exists and another
+ attempt should be made after releasing all current
+ locks and waiting for conflicting lock go away
+ (using mdl_wait_for_locks()).
+
+ @retval FALSE Success.
+ @retval TRUE Failure. Either error occured or conflicting lock exists.
+ In the latter case "retry" parameter is set to TRUE.
*/
-bool mdl_acquire_shared_lock(MDL_EL *l)
+bool mdl_acquire_shared_lock(MDL_EL *l, bool *retry)
{
MDL_LOCK *lock;
- bool result= FALSE;
+ *retry= FALSE;
DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING);
safe_mutex_assert_not_owner(&LOCK_open);
+ if (l->ctx->has_global_shared_lock && l->upgradable)
+ {
+ my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+ return TRUE;
+ }
+
pthread_mutex_lock(&LOCK_mdl);
+ if (l->upgradable &&
+ (global_shared_locks_acquired || global_shared_locks_pending))
+ {
+ pthread_mutex_unlock(&LOCK_mdl);
+ *retry= TRUE;
+ return TRUE;
+ }
+
if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key, l->key_length)))
{
lock= get_lock_object();
@@ -432,6 +466,8 @@ bool mdl_acquire_shared_lock(MDL_EL *l)
my_hash_insert(&mdl_locks, (uchar*)lock);
l->state= MDL_ACQUIRED;
l->lock= lock;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
}
else
{
@@ -453,13 +489,15 @@ bool mdl_acquire_shared_lock(MDL_EL *l)
lock->users++;
l->state= MDL_ACQUIRED;
l->lock= lock;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
}
else
- result= TRUE;
+ *retry= TRUE;
}
pthread_mutex_unlock(&LOCK_mdl);
- return result;
+ return *retry;
}
@@ -482,7 +520,7 @@ static void release_lock(MDL_EL *l);
and MDL_NORMAL_PRIO).
@retval FALSE Success
- @retval TRUE Failure (thread was killed)
+ @retval TRUE Failure
*/
bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
@@ -498,6 +536,12 @@ bool mdl_acquire_exclusive_locks(MDL_CON
safe_mutex_assert_not_owner(&LOCK_open);
+ if (context->has_global_shared_lock)
+ {
+ my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+ return TRUE;
+ }
+
pthread_mutex_lock(&LOCK_mdl);
old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
@@ -528,9 +572,13 @@ bool mdl_acquire_exclusive_locks(MDL_CON
{
lock= l->lock;
- if ((lh= lock->active_readers.head()))
+ if (global_shared_locks_acquired || global_shared_locks_pending)
{
- signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ /*
+ There is active or pending global shared lock we have
+ to wait until it goes away.
+ */
+ signalled= TRUE;
break;
}
else if (!lock->active_writers.is_empty() ||
@@ -544,6 +592,11 @@ bool mdl_acquire_exclusive_locks(MDL_CON
signalled= TRUE;
break;
}
+ else if ((lh= lock->active_readers.head()))
+ {
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ break;
+ }
}
if (!l)
break;
@@ -572,6 +625,7 @@ bool mdl_acquire_exclusive_locks(MDL_CON
/* Return lock request to its initial state. */
l->type= MDL_SHARED;
l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
context->locks.remove(l);
}
/* Pending requests for shared locks can be satisfied now. */
@@ -583,6 +637,7 @@ bool mdl_acquire_exclusive_locks(MDL_CON
it.rewind();
while ((l= it++))
{
+ global_intention_exclusive_locks_acquired++;
lock= l->lock;
lock->waiting_writers.remove(l);
lock->active_writers.push_front(l);
@@ -648,6 +703,7 @@ bool mdl_upgrade_shared_lock_to_exclusiv
{
DBUG_PRINT("info", ("found shared lock for upgrade"));
DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ DBUG_ASSERT(l->upgradable);
l->state= MDL_PENDING_UPGRADE;
lock= l->lock;
lock->active_readers.remove(l);
@@ -666,6 +722,9 @@ bool mdl_upgrade_shared_lock_to_exclusiv
lock= l->lock;
+ DBUG_ASSERT(global_shared_locks_acquired == 0 &&
+ global_intention_exclusive_locks_acquired);
+
if ((lh= lock->active_readers.head()))
{
DBUG_PRINT("info", ("found active readers"));
@@ -749,8 +808,8 @@ bool mdl_upgrade_shared_lock_to_exclusiv
on the table to be created. In this statement we don't want to
block and wait for the lock if the table already exists.
- @param context The context containing the lock request
- @param lock The lock request
+ @param context [in] The context containing the lock request
+ @param lock [in] The lock request
@retval FALSE the lock was granted
@retval TRUE there were conflicting locks.
@@ -768,6 +827,19 @@ bool mdl_try_acquire_exclusive_lock(MDL_
safe_mutex_assert_not_owner(&LOCK_open);
pthread_mutex_lock(&LOCK_mdl);
+
+ /*
+ QQ: It is not clear what should we do about global shared lock here...
+ Alternatives are:
+ - Check that there are no active or pending global shared locks
+ and wait if there is one. But this may lead to deadlocks.
+ - Check that there are no active or pending global shared locks
+ and emit error if there is one.
+ - Eliminate scenarios in which mdl_try_acquire_exclusive_lock()
+ can not be called with active global shared locks - i.e.
+ prohibit CREATE TABLE under LOCK TABLES...
+ */
+
if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key, l->key_length)))
{
lock= get_lock_object();
@@ -777,6 +849,7 @@ bool mdl_try_acquire_exclusive_lock(MDL_
l->state= MDL_ACQUIRED;
l->lock= lock;
lock= 0;
+ global_intention_exclusive_locks_acquired++;
}
pthread_mutex_unlock(&LOCK_mdl);
@@ -794,6 +867,51 @@ bool mdl_try_acquire_exclusive_lock(MDL_
/**
+ Acquire global shared metadata lock.
+
+ Holding this lock will block all requests for exclusive locks
+ and shared locks which can be potentially upgraded to exclusive
+ (see MDL_EL::upgradable).
+
+ @param context Current metadata locking context.
+
+ @retval FALSE Success -- the lock was granted.
+ @retval TRUE Failure -- our thread was killed.
+*/
+
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context)
+{
+ THD *thd= context->thd;
+ const char *old_msg;
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(thd == current_thd);
+ DBUG_ASSERT(!context->has_global_shared_lock);
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ global_shared_locks_pending++;
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+ while (!thd->killed && global_intention_exclusive_locks_acquired)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+
+ global_shared_locks_pending--;
+ if (thd->killed)
+ {
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return TRUE;
+ }
+ global_shared_locks_acquired++;
+ context->has_global_shared_lock= TRUE;
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return FALSE;
+}
+
+
+/**
Wait until there will be no locks that conflict with lock requests
in the context.
@@ -837,8 +955,16 @@ bool mdl_wait_for_locks(MDL_CONTEXT *con
it.rewind();
while ((l= it++))
{
- DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING);
- if ((lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key, l->key_length)) &&
+ DBUG_ASSERT(l->state == MDL_PENDING);
+ if ((l->upgradable || l->type == MDL_EXCLUSIVE) &&
+ (global_shared_locks_acquired || global_shared_locks_pending))
+ break;
+ /*
+ To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock.
+ */
+ if (l->type == MDL_SHARED &&
+ (lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)) &&
!(lock->active_writers.is_empty() &&
lock->active_readers_waiting_upgrade.is_empty() &&
lock->waiting_writers.is_empty()))
@@ -879,6 +1005,9 @@ static void release_lock(MDL_EL *l)
if (lock->cached_object)
(*lock->cached_object_release_hook)(l->lock->cached_object);
release_lock_object(lock);
+ if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED ||
+ l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->upgradable)
+ global_intention_exclusive_locks_acquired--;
}
else
{
@@ -886,12 +1015,17 @@ static void release_lock(MDL_EL *l)
{
case MDL_SHARED:
lock->active_readers.remove(l);
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired--;
break;
case MDL_EXCLUSIVE:
if (l->state == MDL_PENDING)
lock->waiting_writers.remove(l);
else
+ {
lock->active_writers.remove(l);
+ global_intention_exclusive_locks_acquired--;
+ }
break;
default:
/* TODO Really? How about problems during lock upgrade ? */
@@ -912,9 +1046,6 @@ static void release_lock(MDL_EL *l)
It is also used to release shared locks in the end of an SQL
statement.
- Also resets lock requests back to their initial state (i.e.
- sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
-
@param context The context with which the locks to be released
are associated.
*/
@@ -945,9 +1076,11 @@ void mdl_release_locks(MDL_CONTEXT *cont
l->lock= 0;
#endif
}
- /* Return lock request to its initial state. */
- l->type= MDL_SHARED;
- l->prio= MDL_NORMAL_PRIO;
+ /*
+ We will return lock request to its initial state only in
+ mdl_free_locks() since we need to know type of lock
+ request and if it is upgradable in mdl_wait_for_locks().
+ */
}
/* Inefficient but will do for a while */
pthread_cond_broadcast(&COND_mdl);
@@ -990,6 +1123,7 @@ void mdl_release_exclusive_locks(MDL_CON
/* Return lock request to its initial state. */
l->type= MDL_SHARED;
l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
context->locks.remove(l);
}
}
@@ -1023,6 +1157,7 @@ void mdl_release_lock(MDL_CONTEXT *conte
/* Return lock request to its initial state. */
lr->type= MDL_SHARED;
lr->prio= MDL_NORMAL_PRIO;
+ lr->upgradable= FALSE;
context->locks.remove(lr);
pthread_cond_broadcast(&COND_mdl);
pthread_mutex_unlock(&LOCK_mdl);
@@ -1049,11 +1184,32 @@ void mdl_downgrade_exclusive_locks(MDL_C
if (l->type == MDL_EXCLUSIVE)
{
DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ if (!l->upgradable)
+ global_intention_exclusive_locks_acquired--;
lock= l->lock;
lock->active_writers.remove(l);
l->type= MDL_SHARED;
lock->active_readers.push_front(l);
}
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Release global shared metadata lock.
+
+ @param context Current context
+*/
+
+void mdl_release_global_shared_lock(MDL_CONTEXT *context)
+{
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(context->has_global_shared_lock);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ global_shared_locks_acquired--;
+ context->has_global_shared_lock= FALSE;
pthread_cond_broadcast(&COND_mdl);
pthread_mutex_unlock(&LOCK_mdl);
}
diff -Nrup a/sql/meta_lock.h b/sql/meta_lock.h
--- a/sql/meta_lock.h 2008-04-08 19:42:48 +04:00
+++ b/sql/meta_lock.h 2008-04-18 12:34:17 +04:00
@@ -55,6 +55,12 @@ struct MDL_EL
enum enum_mdl_type type;
enum enum_mdl_state state;
enum enum_mdl_prio prio;
+ /**
+ TRUE -- if shared lock corresponding to this lock request at some
+ point might be upgraded to an exclusive lock and therefore conflicts
+ with global shared lock, FALSE -- otherwise.
+ */
+ bool upgradable;
private:
/**
@@ -127,6 +133,7 @@ struct MDL_EL_lock
struct MDL_CONTEXT
{
I_P_List <MDL_EL, MDL_EL_context> locks;
+ bool has_global_shared_lock;
THD *thd;
};
@@ -169,11 +176,34 @@ inline void mdl_set_lock_priority(MDL_EL
lock->prio= prio;
}
-bool mdl_acquire_shared_lock(MDL_EL *lock);
+/**
+ Mark request for shared lock as upgradable. Can be only applied
+ to pending locks.
+*/
+
+inline void mdl_set_upgradable(MDL_EL *lock)
+{
+ DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+ lock->upgradable= TRUE;
+}
+
+/**
+ Auxiliary function which allows to mark all elements in
+ table list as requiring upgradable metadata locks.
+*/
+
+inline void mdl_set_all_upgradable(TABLE_LIST *tables)
+{
+ for (; tables; tables= tables->next_global)
+ tables->mdl_upgradable= TRUE;
+}
+
+bool mdl_acquire_shared_lock(MDL_EL *l, bool *retry);
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);
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context);
bool mdl_wait_for_locks(MDL_CONTEXT *context);
@@ -181,6 +211,7 @@ void mdl_release_locks(MDL_CONTEXT *cont
void mdl_release_exclusive_locks(MDL_CONTEXT *context);
void mdl_release_lock(MDL_CONTEXT *context, MDL_EL *lock);
void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context);
+void mdl_release_global_shared_lock(MDL_CONTEXT *context);
bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db,
const char *name);
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h 2008-04-08 19:42:48 +04:00
+++ b/sql/mysql_priv.h 2008-04-18 12:34:17 +04:00
@@ -1275,7 +1275,7 @@ void detach_merge_children(TABLE *table,
bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
TABLE_LIST *new_child_list, TABLE_LIST **new_last);
bool reopen_table(TABLE *table);
-bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
+bool reopen_tables(THD *thd, bool get_locks);
void close_data_files_and_morph_locks(THD *thd, const char *db,
const char *table_name);
void close_handle_and_leave_table_as_lock(TABLE *table);
@@ -1727,7 +1727,7 @@ TABLE *open_performance_schema_table(THD
void close_performance_schema_table(THD *thd, Open_tables_state *backup);
bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh, bool wait_for_placeholders);
+ bool wait_for_refresh);
bool close_cached_connection_tables(THD *thd, bool wait_for_refresh,
LEX_STRING *connect_string,
bool have_lock = FALSE);
diff -Nrup a/sql/set_var.cc b/sql/set_var.cc
--- a/sql/set_var.cc 2008-02-08 19:54:40 +03:00
+++ b/sql/set_var.cc 2008-04-18 12:34:18 +04:00
@@ -3925,7 +3925,7 @@ bool sys_var_opt_readonly::update(THD *t
can cause to wait on a read lock, it's required for the client application
to unlock everything, and acceptable for the server to wait on all locks.
*/
- if (result= close_cached_tables(thd, NULL, FALSE, TRUE, TRUE))
+ if (result= close_cached_tables(thd, NULL, FALSE, TRUE))
goto end_with_read_lock;
if (result= make_global_read_lock_block_commit(thd))
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc 2008-04-08 19:42:48 +04:00
+++ b/sql/sql_base.cc 2008-04-18 12:34:18 +04:00
@@ -295,7 +295,7 @@ void table_def_free(void)
DBUG_ENTER("table_def_free");
if (table_def_inited)
{
- close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, FALSE, FALSE);
table_def_inited= 0;
pthread_mutex_destroy(&LOCK_table_share);
hash_free(&table_def_cache);
@@ -942,6 +942,36 @@ void free_io_cache(TABLE *table)
}
+/**
+ Auxiliary function which allows to kill delayed threads for
+ particular table identified by its share.
+
+ @param share Table share.
+*/
+
+static void kill_delayed_threads_for_table(TABLE_SHARE *share)
+{
+ for (TABLE *tab= share->used_tables; tab; tab= tab->share_next)
+ {
+ THD *in_use= tab->in_use;
+
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ ! in_use->killed)
+ {
+ in_use->killed= THD::KILL_CONNECTION;
+ pthread_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ {
+ pthread_mutex_lock(in_use->mysys_var->current_mutex);
+ pthread_cond_broadcast(in_use->mysys_var->current_cond);
+ pthread_mutex_unlock(in_use->mysys_var->current_mutex);
+ }
+ pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ }
+ }
+}
+
+
/*
Close all tables which aren't in use by any thread
@@ -949,18 +979,23 @@ void free_io_cache(TABLE *table)
@param tables List of tables to remove from the cache
@param have_lock If LOCK_open is locked
@param wait_for_refresh Wait for a impending flush
- @param wait_for_placeholders Wait for tables being reopened so that the GRL
- won't proceed while write-locked tables are being reopened by other
- threads.
- @remark THD can be NULL, but then wait_for_refresh must be FALSE
- and tables must be NULL.
+ @note THD can be NULL, but then wait_for_refresh must be FALSE
+ and tables must be NULL.
+
+ @note When called as part of FLUSH TABLES WITH READ LOCK this function
+ ignores metadata locks held by other threads. In order to avoid
+ situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment
+ when some write-locked table is being reopened (by FLUSH TABLES or
+ ALTER TABLE) we have to rely on additional global shared metadata
+ lock taken by thread trying to obtain global read lock.
*/
bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh, bool wait_for_placeholders)
+ bool wait_for_refresh)
{
- bool result=0;
+ bool result= FALSE;
+ bool found= TRUE;
DBUG_ENTER("close_cached_tables");
DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
@@ -969,193 +1004,178 @@ bool close_cached_tables(THD *thd, TABLE
if (!tables)
{
refresh_version++; // Force close of open tables
- while (unused_tables)
- free_cache_entry(unused_tables);
- /* Free table shares */
- while (oldest_unused_share->next)
- {
- pthread_mutex_lock(&oldest_unused_share->mutex);
- VOID(hash_delete(&table_def_cache, (uchar*) oldest_unused_share));
- }
DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
refresh_version));
- if (wait_for_refresh)
- {
- /*
- Other threads could wait in a loop in open_and_lock_tables(),
- trying to lock one or more of our tables.
-
- If they wait for the locks in thr_multi_lock(), their lock
- request is aborted. They loop in open_and_lock_tables() and
- enter open_table(). Here they notice the table is refreshed and
- wait for COND_refresh. Then they loop again in
- open_and_lock_tables() and this time open_table() succeeds. At
- this moment, if we (the FLUSH TABLES thread) are scheduled and
- on another FLUSH TABLES enter close_cached_tables(), they could
- awake while we sleep below, waiting for others threads (us) to
- close their open tables. If this happens, the other threads
- would find the tables unlocked. They would get the locks, one
- after the other, and could do their destructive work. This is an
- issue if we have LOCK TABLES in effect.
-
- The problem is that the other threads passed all checks in
- open_table() before we refresh the table.
-
- The fix for this problem is to set some_tables_deleted for all
- threads with open tables. These threads can still get their
- locks, but will immediately release them again after checking
- this variable. They will then loop in open_and_lock_tables()
- again. There they will wait until we update all tables version
- below.
-
- Setting some_tables_deleted is done by remove_table_from_cache()
- in the other branch.
-
- In other words (reviewer suggestion): You need this setting of
- some_tables_deleted for the case when table was opened and all
- related checks were passed before incrementing refresh_version
- (which you already have) but attempt to lock the table happened
- after the call to close_old_data_files() i.e. after removal of
- current thread locks.
- */
- for (uint idx=0 ; idx < table_def_cache.records ; idx++)
- {
- TABLE_SHARE *share=(TABLE_SHARE*) hash_element(&table_def_cache, idx);
- for (TABLE *table= share->used_tables; table; table= table->share_next)
- {
- /*
- TODO: Check that TABLE_SHARE::used_tables and TABLE::in_use
- are in sync !
-
- QQ: Is this code redundant now because of meta-data lock upgrade?
- Also what about doing zap_thread_having_shared_lock() at some
- point.
- */
- table->in_use->some_tables_deleted= 1;
- }
- }
- }
+ kill_delayed_threads();
}
else
{
bool found=0;
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
- if (remove_table_from_cache(thd, table->db, table->table_name,
- RTFC_OWNED_BY_THD_FLAG))
+ TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+
+ if (share)
+ {
+ share->version= 0;
+ kill_delayed_threads_for_table(share);
found=1;
+ }
}
if (!found)
wait_for_refresh=0; // Nothing to wait for
}
-#ifndef EMBEDDED_LIBRARY
- if (!tables)
- kill_delayed_threads();
-#endif
- if (wait_for_refresh)
+
+ /*
+ Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+ this we automatically close all tables which were marked as "old".
+
+ QQ: is it a good idea when we are flushing particular table ?
+ */
+ while (unused_tables)
+ free_cache_entry(unused_tables);
+ /* Free table shares */
+ while (oldest_unused_share->next)
{
- /*
- If there is any table that has a lower refresh_version, wait until
- this is closed (or this thread is killed) before returning
- */
- thd->mysys_var->current_mutex= &LOCK_open;
- thd->mysys_var->current_cond= &COND_refresh;
- thd_proc_info(thd, "Flushing tables");
+ pthread_mutex_lock(&oldest_unused_share->mutex);
+ VOID(hash_delete(&table_def_cache, (uchar*) oldest_unused_share));
+ }
+
+ if (!wait_for_refresh)
+ {
+ if (!have_lock)
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(result);
+ }
+
+ DBUG_ASSERT(!have_lock);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ if (thd->locked_tables)
+ {
/*
- TODO: Investigate if we should upgrade meta-data lock before incrementing
- refresh_version/marking table for flush.
+ If we are under LOCK TABLES we need to reopen tables without
+ opening a door for any concurrent threads to sneak in and get
+ lock on our tables. To achieve this we use exclusive metadata
+ locks.
*/
- for (TABLE *table= thd->open_tables; table ; table=table->next)
+ if (!tables)
{
- if (table->needs_reopen_or_name_lock())
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
{
- 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).
+ Checking TABLE::db_stat is essential in case when we have
+ several instances of the table open and locked.
*/
- mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
- table->s->db.str,
- table->s->table_name.str);
- VOID(pthread_mutex_lock(&LOCK_open));
- expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
- mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
- close_handle_and_leave_table_as_lock(table);
+ if (tab->db_stat)
+ {
+ char dbname[NAME_LEN+1];
+ char tname[NAME_LEN+1];
+ /*
+ Since close_data_files_and_morph_locks() frees share's memroot
+ we need to make copies of database and table names.
+ */
+ strmov(dbname, tab->s->db.str);
+ strmov(tname, tab->s->table_name.str);
+ if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+ {
+ result= TRUE;
+ goto err_with_reopen;
+ }
+ VOID(pthread_mutex_lock(&LOCK_open));
+ close_data_files_and_morph_locks(thd, dbname, tname);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ }
}
}
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ TABLE *tab= find_locked_table(thd->open_tables, table->db,
+ table->table_name);
+ /*
+ Checking TABLE::db_stat is essential in case when we have
+ several instances of the table open and locked.
+ */
+ if (tab->db_stat)
+ {
+ if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+ {
+ result= TRUE;
+ goto err_with_reopen;
+ }
+ VOID(pthread_mutex_lock(&LOCK_open));
+ close_data_files_and_morph_locks(thd, table->db, table->table_name);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ }
+ }
+ }
+ }
- bool found=1;
- /* Wait until all threads has closed all the tables we had locked */
- DBUG_PRINT("info",
- ("Waiting for other threads to close their open tables"));
- /*
- FIXME Here if wait_for_placeholders is true we somehow should wait for
- tables with TABLE::open_placeholder set (i.e. those tables that
- are in the process of being reopened). But it is not that easy
- to do since they are not accessible through "table_def_cache".
+ /* Wait until all threads have closed all the tables we are flushing. */
+ DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
- Actually much better way to achieve the same goal is to take
- global shared meta data lock at the beginning of this function
- (and we have to implement it first).
+ while (found && ! thd->killed)
+ {
+ found= FALSE;
+ /*
+ To avoid self and other kinds of deadlock we have to flush open HANDLERs.
*/
- while (found && ! thd->killed)
- {
- found=0;
- /*
- To avoid self and other kinds of deadlock we have to
- flush open HANDLERs.
- */
- VOID(pthread_mutex_unlock(&LOCK_open));
- mysql_ha_flush(thd);
- VOID(pthread_mutex_lock(&LOCK_open));
+ mysql_ha_flush(thd);
+
+ VOID(pthread_mutex_lock(&LOCK_open));
+
+ thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables");
+ if (!tables)
+ {
for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE_SHARE *share=(TABLE_SHARE*) hash_element(&table_def_cache, idx);
- if (share->version != refresh_version)
- {
- found=1;
- DBUG_PRINT("signal", ("Waiting for COND_refresh"));
- pthread_cond_wait(&COND_refresh,&LOCK_open);
- break;
- }
+ TABLE_SHARE *share=(TABLE_SHARE*) hash_element(&table_def_cache, idx);
+ if (share->version != refresh_version)
+ {
+ found= TRUE;
+ break;
+ }
}
}
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+ if (share && share->version != refresh_version)
+ {
+ found= TRUE;
+ break;
+ }
+ }
+ }
+
+ if (found)
+ {
+ DBUG_PRINT("signal", ("Waiting for COND_refresh"));
+ pthread_cond_wait(&COND_refresh,&LOCK_open);
+ }
+
+ thd->exit_cond(NULL);
+ }
+
+err_with_reopen:
+ if (thd->locked_tables)
+ {
+ VOID(pthread_mutex_lock(&LOCK_open));
/*
No other thread has the locked tables open; reopen them and get the
old locks. This should always succeed (unless some external process
has removed the tables)
*/
thd->in_lock_tables=1;
- result=reopen_tables(thd,1,1);
+ result|= reopen_tables(thd, 1);
thd->in_lock_tables=0;
- /* Set version for table */
- for (TABLE *table=thd->open_tables; table ; table= table->next)
- {
- /*
- Preserve the version (0) of write locked tables so that a impending
- global read lock won't sneak in.
- */
- if (table->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
- table->s->version= refresh_version;
- }
VOID(pthread_mutex_unlock(&LOCK_open));
mdl_downgrade_exclusive_locks(&thd->mdl_context);
- VOID(pthread_mutex_lock(&LOCK_open));
- }
- if (!have_lock)
- VOID(pthread_mutex_unlock(&LOCK_open));
- if (wait_for_refresh)
- {
- pthread_mutex_lock(&thd->mysys_var->mutex);
- thd->mysys_var->current_mutex= 0;
- thd->mysys_var->current_cond= 0;
- thd_proc_info(thd, 0);
- pthread_mutex_unlock(&thd->mysys_var->mutex);
}
DBUG_RETURN(result);
}
@@ -1208,7 +1228,7 @@ bool close_cached_connection_tables(THD
}
if (tables)
- result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE);
+ result= close_cached_tables(thd, tables, TRUE, FALSE);
if (!have_lock)
VOID(pthread_mutex_unlock(&LOCK_open));
@@ -2776,11 +2796,15 @@ TABLE *open_table(THD *thd, TABLE_LIST *
}
else
{
+ bool retry;
+
+ if (table_list->mdl_upgradable)
+ mdl_set_upgradable(mdl);
mdl_set_lock_priority(mdl, (flags & MYSQL_LOCK_IGNORE_FLUSH) ?
MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
- if (mdl_acquire_shared_lock(mdl))
+ if (mdl_acquire_shared_lock(mdl, &retry))
{
- if (action)
+ if (action && retry)
*action= OT_BACK_OFF_AND_RETRY;
DBUG_RETURN(0);
}
@@ -3355,8 +3379,6 @@ static bool reattach_merge(THD *thd, TAB
@param thd Thread context
@param get_locks Should we get locks after reopening tables ?
- @param mark_share_as_old Mark share as old to protect from a impending
- global read lock.
@note Since this function can't properly handle prelocking and
create placeholders it should be used in very special
@@ -3370,7 +3392,7 @@ static bool reattach_merge(THD *thd, TAB
@return FALSE in case of success, TRUE - otherwise.
*/
-bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
+bool reopen_tables(THD *thd, bool get_locks)
{
TABLE *table,*next,**prev;
TABLE **tables,**tables_ptr; // For locks
@@ -3440,11 +3462,15 @@ bool reopen_tables(THD *thd, bool get_lo
prev= &table->next;
/* Do not handle locks of MERGE children. */
if (get_locks && !db_stat && !table->parent)
- *tables_ptr++= table; // need new lock on this
- if (mark_share_as_old)
{
- table->s->version=0;
- table->open_placeholder= 0;
+ *tables_ptr++= table; // need new lock on this
+ /*
+ We rely on having exclusive metadata lock on the table to be
+ able safely re-acquire table locks on it.
+ */
+ DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+ table->s->db.str,
+ table->s->table_name.str));
}
}
}
@@ -3649,7 +3675,7 @@ bool wait_for_tables(THD *thd)
/* Now we can open all tables without any interference */
thd_proc_info(thd, "Reopen tables");
thd->version= refresh_version;
- result=reopen_tables(thd,0,0);
+ result=reopen_tables(thd, 0);
}
pthread_mutex_unlock(&LOCK_open);
thd_proc_info(thd, 0);
diff -Nrup a/sql/sql_parse.cc b/sql/sql_parse.cc
--- a/sql/sql_parse.cc 2008-03-06 21:40:08 +03:00
+++ b/sql/sql_parse.cc 2008-04-18 12:34:18 +04:00
@@ -46,6 +46,7 @@ int execute_backup_command(THD*,LEX*);
static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
static bool check_show_create_table_access(THD *thd, TABLE_LIST *table);
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables);
const char *any_db="*any*"; // Special symbol for check_access
@@ -3155,6 +3156,7 @@ end_with_restore_list:
thd->options|= OPTION_TABLE_LOCK;
mdl_alloc_locks(all_tables, &thd->locked_tables_root);
thd->mdl_el_root= &thd->locked_tables_root;
+ adjust_mdl_locks_upgradability(all_tables);
if (!(res= simple_open_n_lock_tables(thd, all_tables)))
{
@@ -6393,23 +6395,15 @@ bool reload_acl_and_cache(THD *thd, ulon
if ((options & REFRESH_READ_LOCK) && thd)
{
/*
- We must not try to aspire a global read lock if we have a write
- locked table. This would lead to a deadlock when trying to
- reopen (and re-lock) the table after the flush.
+ On the first hand we need write lock on the tables to be flushed,
+ on the other hand we must not try to aspire a global read lock
+ if we have a write locked table as this would lead to a deadlock
+ when trying to reopen (and re-lock) the table after the flush.
*/
if (thd->locked_tables)
{
- THR_LOCK_DATA **lock_p= thd->locked_tables->locks;
- THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count;
-
- for (; lock_p < end_p; lock_p++)
- {
- if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE)
- {
- my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
- return 1;
- }
- }
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ return 1;
}
/*
Writing to the binlog could cause deadlocks, as we don't log
@@ -6419,7 +6413,7 @@ bool reload_acl_and_cache(THD *thd, ulon
if (lock_global_read_lock(thd))
return 1; // Killed
result= close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, TRUE);
+ FALSE : TRUE);
if (make_global_read_lock_block_commit(thd)) // Killed
{
/* Don't leave things in a half-locked state */
@@ -6428,8 +6422,36 @@ bool reload_acl_and_cache(THD *thd, ulon
}
}
else
+ {
+ if (thd && thd->locked_tables)
+ {
+ /*
+ If we are under LOCK TABLES we should have a write
+ lock on tables which we are going to flush.
+ */
+ if (tables)
+ {
+ for (TABLE_LIST *t= tables; t; t= t->next_local)
+ if (!find_write_locked_table(thd, t->db, t->table_name))
+ return 1;
+ }
+ else
+ {
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ if (tab->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ tab->s->table_name.str);
+ return 1;
+ }
+ }
+ }
+ }
+
result= close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, FALSE);
+ FALSE : TRUE);
+ }
my_dbopt_cleanup();
}
if (options & REFRESH_HOSTS)
@@ -7306,6 +7328,42 @@ bool parse_sql(THD *thd,
/* That's it. */
return mysql_parse_status || thd->is_fatal_error;
+}
+
+
+/**
+ Auxiliary function which marks metadata locks for all tables
+ on which we plan to take write lock as upgradable.
+*/
+
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables)
+{
+ TABLE_LIST *tab, *otab;
+
+ for (tab= tables; tab; tab= tab->next_global)
+ {
+ if (tab->lock_type >= TL_WRITE_ALLOW_WRITE)
+ tab->mdl_upgradable= TRUE;
+ else
+ {
+ /*
+ QQ: It would be really nice to get rid of this loop but
+ currently we need it to cover when some tables are
+ locked for write and some for read. See similar
+ code in find_write_locked_table() and open_table().
+ */
+ for (otab= tables; otab; otab= otab->next_global)
+ if (otab->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ otab->db_length == tab->db_length &&
+ otab->table_name_length == tab->table_name_length &&
+ !strcmp(otab->db, tab->db) &&
+ !strcmp(otab->table_name, tab->table_name))
+ {
+ tab->mdl_upgradable= TRUE;
+ break;
+ }
+ }
+ }
}
/**
diff -Nrup a/sql/sql_show.cc b/sql/sql_show.cc
--- a/sql/sql_show.cc 2008-04-08 19:42:49 +04:00
+++ b/sql/sql_show.cc 2008-04-18 12:34:18 +04:00
@@ -3044,6 +3044,7 @@ static int fill_schema_table_from_frm(TH
uint key_length;
MDL_EL mdl;
char mdlkey[MAX_DBKEY_LENGTH];
+ bool retry;
bzero((char*) &table_list, sizeof(TABLE_LIST));
bzero((char*) &tbl, sizeof(TABLE));
@@ -3060,12 +3061,23 @@ static int fill_schema_table_from_frm(TH
simply obtaining internal lock of data-dictionary (ATM it
is LOCK_open) instead of obtaning full-blown metadata lock.
*/
- while (mdl_acquire_shared_lock(&mdl))
- if (mdl_wait_for_locks(&thd->mdl_context))
+ while (1)
+ {
+ if (mdl_acquire_shared_lock(&mdl, &retry))
{
- /* Probably we have been killed, let the caller handle the situation. */
- return 1;
+ if (!retry || mdl_wait_for_locks(&thd->mdl_context))
+ {
+ /*
+ Some error occured or we have been killed while waiting
+ for conflicting locks to go away, let the caller to handle
+ the situation.
+ */
+ return 1;
+ }
+ continue;
}
+ break;
+ }
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-04-08 19:42:49 +04:00
+++ b/sql/sql_table.cc 2008-04-18 12:34:19 +04:00
@@ -4522,6 +4522,7 @@ send_result_message:
bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
{
DBUG_ENTER("mysql_repair_table");
+ mdl_set_all_upgradable(tables);
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
"repair", TL_WRITE, 1,
test(check_opt->sql_flags & TT_USEFRM),
@@ -6413,6 +6414,8 @@ view_err:
DBUG_RETURN(error);
}
+ table_list->mdl_upgradable= TRUE;
+
if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ)))
DBUG_RETURN(TRUE);
table->use_all_columns();
@@ -7032,7 +7035,7 @@ end_online:
if (thd->locked_tables && new_name == table_name && new_db == db)
{
thd->in_lock_tables= 1;
- error= reopen_tables(thd, 1, 1);
+ error= reopen_tables(thd, 1);
thd->in_lock_tables= 0;
if (error)
goto err_with_placeholders;
diff -Nrup a/sql/sql_trigger.cc b/sql/sql_trigger.cc
--- a/sql/sql_trigger.cc 2008-03-04 18:21:06 +03:00
+++ b/sql/sql_trigger.cc 2008-04-18 12:34:19 +04:00
@@ -489,7 +489,7 @@ bool mysql_create_or_drop_trigger(THD *t
/* Make table suitable for reopening */
close_data_files_and_morph_locks(thd, tables->db, tables->table_name);
thd->in_lock_tables= 1;
- if (reopen_tables(thd, 1, 1))
+ if (reopen_tables(thd, 1))
{
/* To be safe remove this table from the set of LOCKED TABLES */
unlink_open_table(thd, tables->table, FALSE);
diff -Nrup a/sql/sql_view.cc b/sql/sql_view.cc
--- a/sql/sql_view.cc 2008-03-04 18:21:06 +03:00
+++ b/sql/sql_view.cc 2008-04-18 12:34:19 +04:00
@@ -1254,7 +1254,10 @@ bool mysql_make_view(THD *thd, File_pars
anyway.
*/
for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
+ {
tbl->lock_type= table->lock_type;
+ tbl->mdl_upgradable= table->mdl_upgradable;
+ }
/*
If the view is mergeable, we might want to
INSERT/UPDATE/DELETE into tables of this view. Preserve the
diff -Nrup a/sql/table.h b/sql/table.h
--- a/sql/table.h 2008-02-18 17:31:37 +03:00
+++ b/sql/table.h 2008-04-18 12:34:19 +04:00
@@ -1119,6 +1119,11 @@ struct TABLE_LIST
case.
*/
enum {NORMAL_OPEN= 0, OPEN_OR_CREATE, TAKE_EXCLUSIVE_MDL} open_table_type;
+ /**
+ Indicates that for this table/view we need to take shared metadata
+ lock which should be upgradable to exclusive metadata lock.
+ */
+ bool mdl_upgradable;
/* For transactional locking. */
int lock_timeout; /* NOWAIT or WAIT [X] */
bool lock_transactional; /* If transactional lock requested. */
| Thread |
|---|
| • bk commit into 6.0 tree (dlenev:1.2566) WL#3726 | dlenev | 18 Apr |