summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorKonstantin Osipov <kostja@sun.com>2009-11-30 18:55:03 +0300
committerKonstantin Osipov <kostja@sun.com>2009-11-30 18:55:03 +0300
commit69b9761f2913dfa9fa2989ef7f3e01b8d2d3e334 (patch)
tree7bd54aeb6a84931b5374760af2239149d840a0c1 /sql
parent0a9d4e675ad3b176909d30c5a6aa8ab1f0b7186b (diff)
downloadmariadb-git-69b9761f2913dfa9fa2989ef7f3e01b8d2d3e334.tar.gz
Initial import of WL#3726 "DDL locking for all metadata objects".
Backport of: ------------------------------------------------------------ revno: 2630.4.1 committer: Dmitry Lenev <dlenev@mysql.com> branch nick: mysql-6.0-3726-w timestamp: Fri 2008-05-23 17:54:03 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. ------------------------------------------------------------ This is the first patch in series. It transforms the metadata locking subsystem to use a dedicated module (mdl.h,cc). No significant changes in the locking protocol. The import passes the test suite with the exception of deprecated/removed 6.0 features, and MERGE tables. The latter are subject to a fix by WL#4144. Unfortunately, the original changeset comments got lost in a merge, thus this import has its own (largely insufficient) comments. This patch fixes Bug#25144 "replication / binlog with view breaks". Warning: this patch introduces an incompatible change: Under LOCK TABLES, it's no longer possible to FLUSH a table that was not locked for WRITE. Under LOCK TABLES, it's no longer possible to DROP a table or VIEW that was not locked for WRITE. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.2 committer: Dmitry Lenev <dlenev@mysql.com> branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 14:03:45 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.3 committer: Dmitry Lenev <dlenev@mysql.com> branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 14:08:51 +0400 message: WL#3726 "DDL locking for all metadata objects" Fixed failing Windows builds by adding mdl.cc to the lists of files needed to build server/libmysqld on Windows. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.4 committer: Dmitry Lenev <dlenev@mysql.com> branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 21:57:58 +0400 message: WL#3726 "DDL locking for all metadata objects". Fix for assert failures in kill.test which occured when one tried to kill ALTER TABLE statement on merge table while it was waiting in wait_while_table_is_used() for other connections to close this table. These assert failures stemmed from the fact that cleanup code in this case assumed that temporary table representing new version of table was open with adding to THD::temporary_tables list while code which were opening this temporary table wasn't always fulfilling this. This patch changes code that opens new version of table to always do this linking in. It also streamlines cleanup process for cases when error occurs while we have new version of table open. ****** WL#3726 "DDL locking for all metadata objects" Add libmysqld/mdl.cc to .bzrignore. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.6 committer: Dmitry Lenev <dlenev@mysql.com> branch nick: mysql-6.0-3726-w timestamp: Sun 2008-05-25 00:33:22 +0400 message: WL#3726 "DDL locking for all metadata objects". Addition to the fix of assert failures in kill.test caused by changes for this worklog. Make sure we close the new table only once.
Diffstat (limited to 'sql')
-rwxr-xr-xsql/CMakeLists.txt2
-rw-r--r--sql/Makefile.am5
-rw-r--r--sql/event_db_repository.cc4
-rw-r--r--sql/ha_ndbcluster.cc23
-rw-r--r--sql/ha_ndbcluster_binlog.cc33
-rw-r--r--sql/handler.cc9
-rw-r--r--sql/lock.cc382
-rw-r--r--sql/log_event.cc22
-rw-r--r--sql/log_event_old.cc36
-rw-r--r--sql/mdl.cc1342
-rw-r--r--sql/mdl.h260
-rw-r--r--sql/mysql_priv.h53
-rw-r--r--sql/mysqld.cc5
-rw-r--r--sql/rpl_rli.cc21
-rw-r--r--sql/rpl_rli.h1
-rw-r--r--sql/set_var.cc2
-rw-r--r--sql/sp_head.cc7
-rw-r--r--sql/sql_acl.cc16
-rw-r--r--sql/sql_base.cc2465
-rw-r--r--sql/sql_binlog.cc2
-rw-r--r--sql/sql_class.cc20
-rw-r--r--sql/sql_class.h13
-rw-r--r--sql/sql_db.cc4
-rw-r--r--sql/sql_delete.cc27
-rw-r--r--sql/sql_handler.cc136
-rw-r--r--sql/sql_insert.cc13
-rw-r--r--sql/sql_parse.cc118
-rw-r--r--sql/sql_partition.cc10
-rw-r--r--sql/sql_plist.h125
-rw-r--r--sql/sql_plugin.cc4
-rw-r--r--sql/sql_prepare.cc2
-rw-r--r--sql/sql_rename.cc17
-rw-r--r--sql/sql_servers.cc11
-rw-r--r--sql/sql_show.cc52
-rw-r--r--sql/sql_table.cc508
-rw-r--r--sql/sql_test.cc40
-rw-r--r--sql/sql_trigger.cc46
-rw-r--r--sql/sql_udf.cc3
-rw-r--r--sql/sql_update.cc4
-rw-r--r--sql/sql_view.cc92
-rw-r--r--sql/table.cc20
-rw-r--r--sql/table.h80
42 files changed, 4054 insertions, 1981 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 15c2d950ff9..a40cf04f37c 100755
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -76,7 +76,7 @@ SET (SQL_SOURCE
rpl_rli.cc rpl_mi.cc sql_servers.cc
sql_connect.cc scheduler.cc
sql_profile.cc event_parse_data.cc
- sql_signal.cc rpl_handler.cc
+ sql_signal.cc rpl_handler.cc mdl.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
${PROJECT_SOURCE_DIR}/include/mysqld_error.h
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 15ee0d588c4..95864dfbb1b 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -112,7 +112,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
event_data_objects.h event_scheduler.h \
sql_partition.h partition_info.h partition_element.h \
contributors.h sql_servers.h sql_signal.h records.h \
- sql_prepare.h rpl_handler.h replication.h
+ sql_prepare.h rpl_handler.h replication.h mdl.h \
+ sql_plist.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -158,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_plugin.cc sql_binlog.cc \
sql_builtin.cc sql_tablespace.cc partition_info.cc \
sql_servers.cc event_parse_data.cc sql_signal.cc \
- rpl_handler.cc
+ rpl_handler.cc mdl.cc
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
index 8faab5023da..0cf16e3a8a4 100644
--- a/sql/event_db_repository.cc
+++ b/sql/event_db_repository.cc
@@ -555,6 +555,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
DBUG_ENTER("Event_db_repository::open_event_table");
tables.init_one_table("mysql", "event", lock_type);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1109,6 +1110,7 @@ Event_db_repository::check_system_tables(THD *thd)
/* Check mysql.db */
tables.init_one_table("mysql", "db", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1126,6 +1128,7 @@ Event_db_repository::check_system_tables(THD *thd)
}
/* Check mysql.user */
tables.init_one_table("mysql", "user", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1146,6 +1149,7 @@ Event_db_repository::check_system_tables(THD *thd)
}
/* Check mysql.event */
tables.init_one_table("mysql", "event", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
index b71a7f602ab..a7c4cbf5d20 100644
--- a/sql/ha_ndbcluster.cc
+++ b/sql/ha_ndbcluster.cc
@@ -565,7 +565,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans)
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:
@@ -7276,6 +7276,20 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
}
+ /*
+ ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog
+ thread in situations when some tables are already open. This means that
+ code below will try to obtain exclusive metadata lock on some table
+ while holding shared meta-data lock on other tables. This might lead to
+ a deadlock, and therefore is disallowed by assertions of the metadata
+ locking subsystem. In order to temporarily make the code work, we must
+ reset and backup the open tables state, thus hide the existing locks
+ from MDL asserts. But in the essence this is violation of metadata
+ locking protocol which has to be closed ASAP.
+ */
+ Open_tables_state open_tables_state_backup;
+ thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
+
if (!global_read_lock)
{
// Delete old files
@@ -7299,8 +7313,11 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
}
+ thd->restore_backup_open_tables_state(&open_tables_state_backup);
+
+ /* Lock mutex before creating .FRM files. */
pthread_mutex_lock(&LOCK_open);
- // Create new files
+ /* Create new files. */
List_iterator_fast<char> it2(create_list);
while ((file_name_str=it2++))
{
@@ -8215,7 +8232,7 @@ int handle_trailing_share(NDB_SHARE *share)
table_list.db= share->db;
table_list.alias= table_list.table_name= share->table_name;
safe_mutex_assert_owner(&LOCK_open);
- close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
pthread_mutex_lock(&ndbcluster_mutex);
/* ndb_share reference temporary free */
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
index e34a22cf9f4..bf76960201d 100644
--- a/sql/ha_ndbcluster_binlog.cc
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -140,6 +140,8 @@ static Uint64 *p_latest_trans_gci= 0;
*/
static TABLE *ndb_binlog_index= 0;
static TABLE_LIST binlog_tables;
+static MDL_LOCK binlog_mdl_lock;
+static char binlog_mdlkey[MAX_DBKEY_LENGTH];
/*
Helper functions
@@ -282,7 +284,13 @@ static void run_query(THD *thd, char *buf, char *end,
thd_ndb->m_error_code,
(int) thd->is_error(), thd->is_slave_error);
}
+
+ /*
+ After executing statement we should unlock and close tables open
+ by it as well as release meta-data locks obtained by it.
+ */
close_thread_tables(thd);
+
/*
XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
can not be called from within a statement, and
@@ -921,7 +929,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd)
ndb_binlog_tables_inited= TRUE;
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 */
pthread_cond_signal(&injector_cond);
@@ -1735,7 +1743,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
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, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
if ((error= ndbcluster_binlog_open_table(thd, share,
table_share, table, 1)))
@@ -1841,7 +1849,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
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));
@@ -1962,7 +1970,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
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)
@@ -2079,7 +2087,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
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, tmp_share);
@@ -2236,7 +2244,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
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 (schema_type != SOT_ALTER_TABLE)
break;
@@ -2323,18 +2331,21 @@ struct ndb_binlog_index_row {
/*
Open the ndb_binlog_index table
*/
-static int open_ndb_binlog_index(THD *thd, TABLE_LIST *tables,
- TABLE **ndb_binlog_index)
+static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index)
{
static char repdb[]= NDB_REP_DB;
static char reptable[]= NDB_REP_TABLE;
const char *save_proc_info= thd->proc_info;
+ TABLE_LIST *tables= &binlog_tables;
bzero((char*) tables, sizeof(*tables));
tables->db= repdb;
tables->alias= tables->table_name= reptable;
tables->lock_type= TL_WRITE;
thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE;
+ mdl_init_lock(&binlog_mdl_lock, binlog_mdlkey, 0, tables->db,
+ tables->table_name);
+ tables->mdl_lock= &binlog_mdl_lock;
tables->required_type= FRMTYPE_TABLE;
uint counter;
thd->clear_error();
@@ -2374,7 +2385,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row)
for ( ; ; ) /* loop for need_reopen */
{
- if (!ndb_binlog_index && open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index))
+ if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index))
{
error= -1;
goto add_ndb_binlog_index_err;
@@ -2385,7 +2396,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row)
if (need_reopen)
{
TABLE_LIST *p_binlog_tables= &binlog_tables;
- close_tables_for_reopen(thd, &p_binlog_tables);
+ close_tables_for_reopen(thd, &p_binlog_tables, FALSE);
ndb_binlog_index= 0;
continue;
}
@@ -3893,7 +3904,7 @@ restart:
static char db[]= "";
thd->db= db;
if (ndb_binlog_running)
- open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index);
+ open_ndb_binlog_index(thd, &ndb_binlog_index);
thd->db= db;
}
do_ndbcluster_binlog_close_connection= BCCC_running;
diff --git a/sql/handler.cc b/sql/handler.cc
index 17a92b00b4f..9c32171eefd 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -2997,20 +2997,13 @@ static bool update_frm_version(TABLE *table)
if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
{
uchar version[4];
- char *key= table->s->table_cache_key.str;
- uint key_length= table->s->table_cache_key.length;
- TABLE *entry;
- HASH_SEARCH_STATE state;
int4store(version, MYSQL_VERSION_ID);
if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW)))
goto err;
- for (entry=(TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, &state);
- entry;
- entry= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, &state))
- entry->s->mysql_version= MYSQL_VERSION_ID;
+ table->s->mysql_version= MYSQL_VERSION_ID;
}
err:
if (file >= 0)
diff --git a/sql/lock.cc b/sql/lock.cc
index 56ae94ddc39..0c8c3095844 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -955,361 +955,56 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
*****************************************************************************/
/**
- Lock and wait for the named lock.
+ Obtain exclusive metadata locks on the list of tables.
- @param thd Thread handler
- @param table_list Lock first table in this list
+ @param thd Thread handle
+ @param table_list List of tables to lock
+ @note This function assumes that no metadata locks were acquired
+ before calling it. Also it cannot be called while holding
+ LOCK_open mutex. Both these invariants are enforced by asserts
+ in mdl_acquire_exclusive_locks() functions.
- @note
- Works together with global read lock.
-
- @retval
- 0 ok
- @retval
- 1 error
-*/
-
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list)
-{
- int lock_retcode;
- int error= -1;
- DBUG_ENTER("lock_and_wait_for_table_name");
-
- if (wait_if_global_read_lock(thd, 0, 1))
- DBUG_RETURN(1);
- pthread_mutex_lock(&LOCK_open);
- if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0)
- goto end;
- if (lock_retcode && wait_for_locked_table_names(thd, table_list))
- {
- unlock_table_name(thd, table_list);
- goto end;
- }
- error=0;
-
-end:
- pthread_mutex_unlock(&LOCK_open);
- start_waiting_global_read_lock(thd);
- DBUG_RETURN(error);
-}
-
-
-/**
- Put a not open table with an old refresh version in the table cache.
-
- @param thd Thread handler
- @param table_list Lock first table in this list
- @param check_in_use Do we need to check if table already in use by us
-
- @note
- One must have a lock on LOCK_open!
-
- @warning
- If you are going to update the table, you should use
- lock_and_wait_for_table_name instead of this function as this works
- together with 'FLUSH TABLES WITH READ LOCK'
-
- @note
- This will force any other threads that uses the table to release it
- as soon as possible.
-
- @return
- < 0 error
- @return
- == 0 table locked
- @return
- > 0 table locked, but someone is using it
-*/
-
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
-{
- TABLE *table;
- char key[MAX_DBKEY_LENGTH];
- char *db= table_list->db;
- uint key_length;
- bool found_locked_table= FALSE;
- HASH_SEARCH_STATE state;
- DBUG_ENTER("lock_table_name");
- DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name));
-
- key_length= create_table_def_key(thd, key, table_list, 0);
-
- if (check_in_use)
- {
- /* Only insert the table if we haven't insert it already */
- for (table=(TABLE*) my_hash_first(&open_cache, (uchar*)key,
- key_length, &state);
- table ;
- table = (TABLE*) my_hash_next(&open_cache,(uchar*) key,
- key_length, &state))
- {
- if (table->reginfo.lock_type < TL_WRITE)
- {
- if (table->in_use == thd)
- found_locked_table= TRUE;
- continue;
- }
-
- if (table->in_use == thd)
- {
- DBUG_PRINT("info", ("Table is in use"));
- table->s->version= 0; // Ensure no one can use this
- table->locked_by_name= 1;
- DBUG_RETURN(0);
- }
- }
- }
-
- if (thd->locked_tables && thd->locked_tables->table_count &&
- ! find_temporary_table(thd, table_list->db, table_list->table_name))
- {
- if (found_locked_table)
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias);
- else
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_list->alias);
-
- DBUG_RETURN(-1);
- }
-
- if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
- DBUG_RETURN(-1);
-
- table_list->table=table;
-
- /* Return 1 if table is in use */
- DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name,
- check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG)));
-}
-
-
-void unlock_table_name(THD *thd, TABLE_LIST *table_list)
-{
- if (table_list->table)
- {
- my_hash_delete(&open_cache, (uchar*) table_list->table);
- broadcast_refresh();
- }
-}
-
-
-static bool locked_named_table(THD *thd, TABLE_LIST *table_list)
-{
- for (; table_list ; table_list=table_list->next_local)
- {
- TABLE *table= table_list->table;
- if (table)
- {
- TABLE *save_next= table->next;
- bool result;
- table->next= 0;
- result= table_is_used(table_list->table, 0);
- table->next= save_next;
- if (result)
- return 1;
- }
- }
- return 0; // All tables are locked
-}
-
-
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list)
-{
- bool result=0;
- DBUG_ENTER("wait_for_locked_table_names");
-
- safe_mutex_assert_owner(&LOCK_open);
-
- while (locked_named_table(thd,table_list))
- {
- if (thd->killed)
- {
- result=1;
- break;
- }
- wait_for_condition(thd, &LOCK_open, &COND_refresh);
- pthread_mutex_lock(&LOCK_open);
- }
- DBUG_RETURN(result);
-}
-
-
-/**
- Lock all tables in list with a name lock.
-
- REQUIREMENTS
- - One must have a lock on LOCK_open when calling this
-
- @param thd Thread handle
- @param table_list Names of tables to lock
-
- @note
- If you are just locking one table, you should use
- lock_and_wait_for_table_name().
-
- @retval
- 0 ok
- @retval
- 1 Fatal error (end of memory ?)
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM or thread was killed).
*/
bool lock_table_names(THD *thd, TABLE_LIST *table_list)
{
- bool got_all_locks=1;
TABLE_LIST *lock_table;
+ MDL_LOCK *mdl_lock;
for (lock_table= table_list; lock_table; lock_table= lock_table->next_local)
{
- int got_lock;
- if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0)
- goto end; // Fatal error
- if (got_lock)
- got_all_locks=0; // Someone is using table
+ if (!(mdl_lock= mdl_alloc_lock(0, lock_table->db, lock_table->table_name,
+ thd->mem_root)))
+ goto end;
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
}
-
- /* If some table was in use, wait until we got the lock */
- if (!got_all_locks && wait_for_locked_table_names(thd, table_list))
- goto end;
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return 1;
return 0;
end:
- unlock_table_names(thd, table_list, lock_table);
+ mdl_remove_all_locks(&thd->mdl_context);
return 1;
}
/**
- Unlock all tables in list with a name lock.
-
- @param thd Thread handle.
- @param table_list Names of tables to lock.
-
- @note
- This function needs to be protected by LOCK_open. If we're
- under LOCK TABLES, this function does not work as advertised. Namely,
- it does not exclude other threads from using this table and does not
- put an exclusive name lock on this table into the table cache.
-
- @see lock_table_names
- @see unlock_table_names
-
- @retval TRUE An error occured.
- @retval FALSE Name lock successfully acquired.
-*/
-
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list)
-{
- if (lock_table_names(thd, table_list))
- return TRUE;
-
- /*
- Upgrade the table name locks from semi-exclusive to exclusive locks.
- */
- for (TABLE_LIST *table= table_list; table; table= table->next_global)
- {
- if (table->table)
- table->table->open_placeholder= 1;
- }
- return FALSE;
-}
-
-
-/**
- Test is 'table' is protected by an exclusive name lock.
-
- @param[in] thd The current thread handler
- @param[in] table_list Table container containing the single table to be
- tested
-
- @note Needs to be protected by LOCK_open mutex.
-
- @return Error status code
- @retval TRUE Table is protected
- @retval FALSE Table is not protected
-*/
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd,
- TABLE_LIST *table_list)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
-
- key_length= create_table_def_key(thd, key, table_list, 0);
-
- return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key,
- key_length);
-}
-
-
-/**
- Test is 'table key' is protected by an exclusive name lock.
-
- @param[in] thd The current thread handler.
- @param[in] key
- @param[in] key_length
-
- @note Needs to be protected by LOCK_open mutex
+ Release all metadata locks previously obtained by lock_table_names().
- @retval TRUE Table is protected
- @retval FALSE Table is not protected
- */
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
- int key_length)
-{
- HASH_SEARCH_STATE state;
- TABLE *table;
-
- for (table= (TABLE*) my_hash_first(&open_cache, key,
- key_length, &state);
- table ;
- table= (TABLE*) my_hash_next(&open_cache, key,
- key_length, &state))
- {
- if (table->in_use == thd &&
- table->open_placeholder == 1 &&
- table->s->version == 0)
- return TRUE;
- }
-
- return FALSE;
-}
+ @param thd Thread handle.
-/**
- Unlock all tables in list with a name lock.
-
- @param
- thd Thread handle
- @param
- table_list Names of tables to unlock
- @param
- last_table Don't unlock any tables after this one.
- (default 0, which will unlock all tables)
-
- @note
- One must have a lock on LOCK_open when calling this.
-
- @note
- This function will broadcast refresh signals to inform other threads
- that the name locks are removed.
-
- @retval
- 0 ok
- @retval
- 1 Fatal error (end of memory ?)
+ @note Cannot be called while holding LOCK_open mutex.
*/
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
- TABLE_LIST *last_table)
+void unlock_table_names(THD *thd)
{
DBUG_ENTER("unlock_table_names");
- for (TABLE_LIST *table= table_list;
- table != last_table;
- table= table->next_local)
- unlock_table_name(thd,table);
- broadcast_refresh();
+ mdl_release_locks(&thd->mdl_context);
+ mdl_remove_all_locks(&thd->mdl_context);
DBUG_VOID_RETURN;
}
@@ -1455,6 +1150,33 @@ 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.
+
+ TODO: We should change this code to acquire global shared metadata
+ lock before acquiring global read lock. But in order to do
+ this we have to get rid of all those places in which
+ wait_if_global_read_lock() is called before acquiring
+ metadata locks first. Also long-term we should get rid of
+ redundancy between metadata locks, global read lock and DDL
+ blocker (see WL#4399 and WL#4400).
+ */
+ 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
@@ -1476,6 +1198,8 @@ void unlock_global_read_lock(THD *thd)
("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;
if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT)
diff --git a/sql/log_event.cc b/sql/log_event.cc
index 3d35dec4fb0..fd0e20d690d 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -3036,7 +3036,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli,
}
else
{
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
}
/*
@@ -7238,8 +7238,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
DBUG_ASSERT(get_flags(STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -7322,7 +7321,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(actual_error);
}
@@ -7347,7 +7346,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
mysql_unlock_tables(thd, thd->lock);
thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(ERR_BAD_TABLE_DEF);
}
}
@@ -7530,12 +7529,6 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
}
} // if (table)
- /*
- We need to delay this clear until here bacause unpack_current_row() uses
- master-side table definitions stored in rli.
- */
- if (rli->tables_to_lock && get_flags(STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
if (error)
{
@@ -8064,7 +8057,8 @@ Table_map_log_event::~Table_map_log_event()
int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
{
RPL_TABLE_LIST *table_list;
- char *db_mem, *tname_mem;
+ char *db_mem, *tname_mem, *mdlkey;
+ MDL_LOCK *mdl_lock;
size_t dummy_len;
void *memory;
DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)");
@@ -8079,6 +8073,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
&table_list, (uint) sizeof(RPL_TABLE_LIST),
&db_mem, (uint) NAME_LEN + 1,
&tname_mem, (uint) NAME_LEN + 1,
+ &mdl_lock, sizeof(MDL_LOCK),
+ &mdlkey, MAX_DBKEY_LENGTH,
NullS)))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
@@ -8091,6 +8087,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
table_list->updating= 1;
strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len));
strmov(table_list->table_name, m_tblnam);
+ mdl_init_lock(mdl_lock, mdlkey, 0, table_list->db, table_list->table_name);
+ table_list->mdl_lock= mdl_lock;
int error= 0;
diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc
index fbcbb388236..030d51b3618 100644
--- a/sql/log_event_old.cc
+++ b/sql/log_event_old.cc
@@ -32,8 +32,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
*/
DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -91,7 +90,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(actual_error);
}
@@ -109,10 +108,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
{
if (ptr->m_tabledef.compatible_with(rli, ptr->table))
{
- mysql_unlock_tables(thd, thd->lock);
- thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF);
}
}
@@ -236,13 +233,6 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
}
}
- /*
- We need to delay this clear until the table def is no longer needed.
- The table def is needed in unpack_row().
- */
- if (rli->tables_to_lock && ev->get_flags(Old_rows_log_event::STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-
if (error)
{ /* error has occured during the transaction */
rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(),
@@ -1440,8 +1430,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
DBUG_ASSERT(get_flags(STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -1496,7 +1485,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
"Error in %s event: when locking tables",
get_type_str());
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(error);
}
@@ -1515,7 +1504,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
thd->binlog_flush_pending_rows_event(false);
TABLE_LIST *tables= rli->tables_to_lock;
- close_tables_for_reopen(thd, &tables);
+ close_tables_for_reopen(thd, &tables, FALSE);
uint tables_count= rli->tables_to_lock_count;
if ((error= open_tables(thd, &tables, &tables_count, 0)))
@@ -1533,7 +1522,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(error);
}
}
@@ -1552,10 +1541,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
{
if (ptr->m_tabledef.compatible_with(rli, ptr->table))
{
- mysql_unlock_tables(thd, thd->lock);
- thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(ERR_BAD_TABLE_DEF);
}
}
@@ -1726,13 +1713,6 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
}
} // if (table)
- /*
- We need to delay this clear until here bacause unpack_current_row() uses
- master-side table definitions stored in rli.
- */
- if (rli->tables_to_lock && get_flags(STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-
if (error)
{ /* error has occured during the transaction */
rli->report(ERROR_LEVEL, thd->net.last_errno,
diff --git a/sql/mdl.cc b/sql/mdl.cc
new file mode 100644
index 00000000000..00dcc12cdc8
--- /dev/null
+++ b/sql/mdl.cc
@@ -0,0 +1,1342 @@
+/* Copyright (C) 2007-2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+
+/*
+ TODO: Remove this dependency on mysql_priv.h. It's not
+ trivial step at the moment since currently we access to
+ some of THD members and use some of its methods here.
+*/
+#include "mysql_priv.h"
+#include "mdl.h"
+
+
+/**
+ The lock context. Created internally for an acquired lock.
+ For a given name, there exists only one MDL_LOCK_DATA instance,
+ and it exists only when the lock has been granted.
+ Can be seen as an MDL subsystem's version of TABLE_SHARE.
+*/
+
+struct MDL_LOCK_DATA
+{
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared;
+ /*
+ There can be several upgraders and active exclusive
+ belonging to the same context.
+ */
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared_waiting_upgrade;
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_exclusive;
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> waiting_exclusive;
+ uint users;
+ void *cached_object;
+ mdl_cached_object_release_hook cached_object_release_hook;
+
+ MDL_LOCK_DATA() : cached_object(0), cached_object_release_hook(0) {}
+
+ MDL_LOCK *get_key_owner()
+ {
+ return !active_shared.is_empty() ?
+ active_shared.head() :
+ (!active_shared_waiting_upgrade.is_empty() ?
+ active_shared_waiting_upgrade.head() :
+ (!active_exclusive.is_empty() ?
+ active_exclusive.head() : waiting_exclusive.head()));
+ }
+
+ bool has_no_other_users()
+ {
+ return (users == 1);
+ }
+};
+
+
+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,
+ my_bool not_used __attribute__((unused)))
+{
+ MDL_LOCK_DATA *entry=(MDL_LOCK_DATA*) record;
+ *length= entry->get_key_owner()->key_length;
+ return (uchar*) entry->get_key_owner()->key;
+}
+
+
+/**
+ Initialize the metadata locking subsystem.
+
+ This function is called at server startup.
+
+ In particular, initializes the new global mutex and
+ the associated condition variable: LOCK_mdl and COND_mdl.
+ These locking primitives are implementation details of the MDL
+ subsystem and are private to it.
+
+ Note, that even though the new implementation adds acquisition
+ of a new global mutex to the execution flow of almost every SQL
+ statement, the design capitalizes on that to later save on
+ look ups in the table definition cache. This leads to reduced
+ contention overall and on LOCK_open in particular.
+ Please see the description of mdl_acquire_shared_lock() for details.
+*/
+
+void mdl_init()
+{
+ pthread_mutex_init(&LOCK_mdl, NULL);
+ pthread_cond_init(&COND_mdl, NULL);
+ my_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;
+}
+
+
+/**
+ Release resources of metadata locking subsystem.
+
+ Destroys the global mutex and the condition variable.
+ Called at server shutdown.
+*/
+
+void mdl_destroy()
+{
+ DBUG_ASSERT(!mdl_locks.records);
+ pthread_mutex_destroy(&LOCK_mdl);
+ pthread_cond_destroy(&COND_mdl);
+ my_hash_free(&mdl_locks);
+}
+
+
+/**
+ Initialize a metadata locking context.
+
+ This is to be called when a new server connection is created.
+*/
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd)
+{
+ context->locks.empty();
+ context->thd= thd;
+ context->has_global_shared_lock= FALSE;
+}
+
+
+/**
+ Destroy metadata locking context.
+
+ Assumes and asserts that there are no active or pending locks
+ associated with this context at the time of the destruction.
+
+ Currently does nothing. Asserts that there are no pending
+ or satisfied lock requests. The pending locks must be released
+ prior to destruction. This is a new way to express the assertion
+ that all tables are closed before a connection is destroyed.
+*/
+
+void mdl_context_destroy(MDL_CONTEXT *context)
+{
+ DBUG_ASSERT(context->locks.is_empty());
+ DBUG_ASSERT(!context->has_global_shared_lock);
+}
+
+
+/**
+ Backup and reset state of meta-data locking context.
+
+ mdl_context_backup_and_reset(), mdl_context_restore() and
+ mdl_context_merge() are used by HANDLER implementation which
+ needs to open table for new HANDLER independently of already
+ open HANDLERs and add this table/metadata lock to the set of
+ tables open/metadata locks for HANDLERs afterwards.
+*/
+
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+ backup->locks.empty();
+ ctx->locks.swap(backup->locks);
+}
+
+
+/**
+ Restore state of meta-data locking context from backup.
+*/
+
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+ DBUG_ASSERT(ctx->locks.is_empty());
+ ctx->locks.swap(backup->locks);
+}
+
+
+/**
+ Merge meta-data locks from one context into another.
+*/
+
+void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src)
+{
+ MDL_LOCK *l;
+
+ DBUG_ASSERT(dst->thd == src->thd);
+
+ if (!src->locks.is_empty())
+ {
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(src->locks);
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->ctx);
+ l->ctx= dst;
+ dst->locks.push_front(l);
+ }
+ src->locks.empty();
+ }
+}
+
+
+/**
+ Initialize a lock request.
+
+ This is to be used for every lock request.
+
+ Note that initialization and allocation are split
+ into two calls. This is to allow flexible memory management
+ of lock requests. Normally a lock request is stored
+ in statement memory (e.g. is a member of struct TABLE_LIST),
+ but we would also like to allow allocation of lock
+ requests in other memory roots, for example in the grant
+ subsystem, to lock privilege tables.
+
+ The MDL subsystem does not own or manage memory of lock
+ requests. Instead it assumes that the life time of every lock
+ request encloses calls to mdl_acquire_shared_lock() and
+ mdl_release_locks().
+
+ @param mdl Pointer to an MDL_LOCK object to initialize
+ @param key_buff Pointer to the buffer for key for the lock request
+ (should be at least strlen(db) + strlen(name)
+ + 2 bytes, or, if the lengths are not known, MAX_DBNAME_LENGTH)
+ @param type Id of type of object to be locked
+ @param db Name of database to which the object belongs
+ @param name Name of of the object
+
+ Stores the database name, object name and the type in the key
+ buffer. Initializes mdl_el to point to the key.
+ We can't simply initialize mdl_el with type, db and name
+ by-pointer because of the underlying HASH implementation
+ requires the key to be a contiguous buffer.
+
+ The initialized lock request will have MDL_SHARED type and
+ normal priority.
+
+ Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2
+ Note that tables and views have the same lock type, since
+ they share the same name space in the SQL standard.
+*/
+
+void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db,
+ const char *name)
+{
+ int4store(key, type);
+ mdl->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+ mdl->key= key;
+ mdl->type= MDL_SHARED;
+ mdl->state= MDL_PENDING;
+ mdl->prio= MDL_NORMAL_PRIO;
+ mdl->upgradable= FALSE;
+#ifndef DBUG_OFF
+ mdl->ctx= 0;
+ mdl->lock_data= 0;
+#endif
+}
+
+
+/**
+ Allocate and initialize one lock request.
+
+ Same as mdl_init_lock(), but allocates the lock and the key buffer
+ on a memory root. Necessary to lock ad-hoc tables, e.g.
+ mysql.* tables of grant and data dictionary subsystems.
+
+ @param type Id of type of object to be locked
+ @param db Name of database to which object belongs
+ @param name Name of of object
+ @param root MEM_ROOT on which object should be allocated
+
+ @note The allocated lock request will have MDL_SHARED type and
+ normal priority.
+
+ @retval 0 Error
+ @retval non-0 Pointer to an object representing a lock request
+*/
+
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+ MEM_ROOT *root)
+{
+ MDL_LOCK *lock;
+ char *key;
+
+ if (!multi_alloc_root(root, &lock, sizeof(MDL_LOCK), &key,
+ MAX_DBKEY_LENGTH, NULL))
+ return NULL;
+
+ mdl_init_lock(lock, key, type, db, name);
+
+ return lock;
+}
+
+
+/**
+ Add a lock request to the list of lock requests of the context.
+
+ The procedure to acquire metadata locks is:
+ - allocate and initialize lock requests (mdl_alloc_lock())
+ - associate them with a context (mdl_add_lock())
+ - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly).
+
+ Associates a lock request with the given context.
+
+ @param context The MDL context to associate the lock with.
+ There should be no more than one context per
+ connection, to avoid deadlocks.
+ @param lock The lock request to be added.
+*/
+
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock)
+{
+ DBUG_ENTER("mdl_add_lock");
+ DBUG_ASSERT(lock->state == MDL_PENDING);
+ DBUG_ASSERT(!lock->ctx);
+ lock->ctx= context;
+ context->locks.push_front(lock);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Clear all lock requests in the context (clear the context).
+
+ Disassociates lock requests from the context.
+ All granted locks must be released prior to calling this
+ function.
+
+ In other words, the expected procedure to release locks is:
+ - mdl_release_locks();
+ - mdl_remove_all_locks();
+
+ We could possibly merge mdl_remove_all_locks() and mdl_release_locks(),
+ but this function comes in handy when we need to back off: in that case
+ 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_remove_all_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_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();
+}
+
+
+/**
+ Auxiliary functions needed for creation/destruction of MDL_LOCK_DATA
+ objects.
+
+ @todo This naive implementation should be replaced with one that saves
+ on memory allocation by reusing released objects.
+*/
+
+static MDL_LOCK_DATA* get_lock_data_object(void)
+{
+ return new MDL_LOCK_DATA();
+}
+
+
+static void release_lock_data_object(MDL_LOCK_DATA *lock)
+{
+ delete lock;
+}
+
+
+/**
+ Try to acquire one shared lock.
+
+ Unlike exclusive locks, shared locks are acquired one by
+ one. This is interface is chosen to simplify introduction of
+ the new locking API to the system. mdl_acquire_shared_lock()
+ is currently used from open_table(), and there we have only one
+ table to work with.
+
+ In future we may consider allocating multiple shared locks at once.
+
+ This function must be called after the lock is added to a context.
+
+ @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_LOCK *l, bool *retry)
+{
+ MDL_LOCK_DATA *lock_data;
+ *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_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->active_shared.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
+ }
+ else
+ {
+ if ((lock_data->active_exclusive.is_empty() &&
+ (l->prio == MDL_HIGH_PRIO ||
+ lock_data->waiting_exclusive.is_empty() &&
+ lock_data->active_shared_waiting_upgrade.is_empty())) ||
+ (!lock_data->active_exclusive.is_empty() &&
+ lock_data->active_exclusive.head()->ctx == l->ctx))
+ {
+ /*
+ When exclusive lock comes from the same context we can satisfy our
+ shared lock. This is required for CREATE TABLE ... SELECT ... and
+ ALTER VIEW ... AS ....
+ */
+ lock_data->active_shared.push_front(l);
+ lock_data->users++;
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
+ }
+ else
+ *retry= TRUE;
+ }
+ pthread_mutex_unlock(&LOCK_mdl);
+
+ return *retry;
+}
+
+
+static void release_lock(MDL_LOCK *l);
+
+
+/**
+ Acquire exclusive locks. The context must contain the list of
+ locks to be acquired. There must be no granted locks in the
+ context.
+
+ This is a replacement of lock_table_names(). It is used in
+ RENAME, DROP and other DDL SQL statements.
+
+ @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
+*/
+
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l, *lh;
+ MDL_LOCK_DATA *lock_data;
+ bool signalled= FALSE;
+ const char *old_msg;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ THD *thd= context->thd;
+
+ DBUG_ASSERT(thd == current_thd);
+
+ 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");
+
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+ if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->waiting_exclusive.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->lock_data= lock_data;
+ }
+ else
+ {
+ lock_data->waiting_exclusive.push_front(l);
+ lock_data->users++;
+ l->lock_data= lock_data;
+ }
+ }
+
+ while (1)
+ {
+ it.rewind();
+ while ((l= it++))
+ {
+ lock_data= l->lock_data;
+
+ if (global_shared_locks_acquired || global_shared_locks_pending)
+ {
+ /*
+ There is active or pending global shared lock we have
+ to wait until it goes away.
+ */
+ signalled= TRUE;
+ break;
+ }
+ else if (!lock_data->active_exclusive.is_empty() ||
+ !lock_data->active_shared_waiting_upgrade.is_empty())
+ {
+ /*
+ Exclusive MDL owner won't wait on table-level lock the same
+ applies to shared lock waiting upgrade (in this cases we already
+ have some table-level lock).
+ */
+ signalled= TRUE;
+ break;
+ }
+ else if ((lh= lock_data->active_shared.head()))
+ {
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ break;
+ }
+ }
+ if (!l)
+ break;
+ if (signalled)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ else
+ {
+ /*
+ Another thread obtained shared MDL-lock on some table but
+ has not yet opened it and/or tried to obtain data lock on
+ it. In this case we need to wait until this happens and try
+ to abort this thread once again.
+ */
+ struct timespec abstime;
+ 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;
+ l->upgradable= FALSE;
+ 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++))
+ {
+ global_intention_exclusive_locks_acquired++;
+ lock_data= l->lock_data;
+ lock_data->waiting_exclusive.remove(l);
+ lock_data->active_exclusive.push_front(l);
+ l->state= MDL_ACQUIRED;
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ lock_data->cached_object= NULL;
+ }
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return FALSE;
+}
+
+
+/**
+ Upgrade a shared metadata lock to exclusive.
+
+ Used in ALTER TABLE, when a copy of the table with the
+ new definition has been constructed.
+
+ @param context Context to which shared long belongs
+ @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)
+*/
+
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
+ const char *db, const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ bool signalled= FALSE;
+ MDL_LOCK *l, *lh;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_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(thd == current_thd);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ 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)
+ {
+ DBUG_PRINT("info", ("found shared lock for upgrade"));
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ DBUG_ASSERT(l->upgradable);
+ l->state= MDL_PENDING_UPGRADE;
+ lock_data= l->lock_data;
+ lock_data->active_shared.remove(l);
+ lock_data->active_shared_waiting_upgrade.push_front(l);
+ }
+
+ while (1)
+ {
+ DBUG_PRINT("info", ("looking at conflicting locks"));
+ it.rewind();
+ while ((l= it++))
+ {
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+
+ lock_data= l->lock_data;
+
+ DBUG_ASSERT(global_shared_locks_acquired == 0 &&
+ global_intention_exclusive_locks_acquired);
+
+ if ((lh= lock_data->active_shared.head()))
+ {
+ DBUG_PRINT("info", ("found active shared locks"));
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ break;
+ }
+ else if (!lock_data->active_exclusive.is_empty())
+ {
+ DBUG_PRINT("info", ("found active exclusive locks"));
+ signalled= TRUE;
+ break;
+ }
+ }
+ }
+ if (!l)
+ break;
+ if (signalled)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ else
+ {
+ /*
+ Another thread obtained shared MDL-lock on some table but
+ has not yet opened it and/or tried to obtain data lock on
+ it. In this case we need to wait until this happens and try
+ to abort this thread once again.
+ */
+ struct timespec abstime;
+ set_timespec(abstime, 10);
+ 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_data= l->lock_data;
+ lock_data->active_shared_waiting_upgrade.remove(l);
+ lock_data->active_shared.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();
+ while ((l= it++))
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+ lock_data= l->lock_data;
+ lock_data->active_shared_waiting_upgrade.remove(l);
+ lock_data->active_exclusive.push_front(l);
+ l->type= MDL_EXCLUSIVE;
+ l->state= MDL_ACQUIRED;
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ lock_data->cached_object= 0;
+ }
+
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Try to acquire an exclusive lock on the object if there are
+ no conflicting locks.
+
+ Similar to the previous function, but returns
+ immediately without any side effect if encounters a lock
+ conflict. Otherwise takes the lock.
+
+ This function is used in CREATE TABLE ... LIKE to acquire a lock
+ 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 [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.
+
+ FIXME: Compared to lock_table_name_if_not_cached()
+ it gives sligthly more false negatives.
+*/
+
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l)
+{
+ MDL_LOCK_DATA *lock_data;
+
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->active_exclusive.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ lock_data= 0;
+ global_intention_exclusive_locks_acquired++;
+ }
+ pthread_mutex_unlock(&LOCK_mdl);
+
+ /*
+ FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since
+ for such locks we assume that they have MDL_LOCK::lock properly set.
+ Long term we should clearly define relation between lock types,
+ presence in the context lists and MDL_LOCK::lock values.
+ */
+ if (lock_data)
+ context->locks.remove(l);
+
+ return lock_data;
+}
+
+
+/**
+ 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_LOCK::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.
+
+ This is a part of the locking protocol and must be used by the
+ acquirer of shared locks after a back-off.
+
+ 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)
+*/
+
+bool mdl_wait_for_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ const char *old_msg;
+ THD *thd= context->thd;
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(thd == current_thd);
+
+ while (!thd->killed)
+ {
+ /*
+ We have to check if there are some HANDLERs open by this thread
+ which conflict with some pending exclusive locks. Otherwise we
+ might have a deadlock in situations when we are waiting for
+ pending writer to go away, which in its turn waits for HANDLER
+ open by our thread.
+
+ TODO: investigate situations in which we need to broadcast on
+ COND_mdl because of above scenario.
+ */
+ 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++))
+ {
+ 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_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)) &&
+ !(lock_data->active_exclusive.is_empty() &&
+ lock_data->active_shared_waiting_upgrade.is_empty() &&
+ lock_data->waiting_exclusive.is_empty()))
+ break;
+ }
+ if (!l)
+ {
+ pthread_mutex_unlock(&LOCK_mdl);
+ break;
+ }
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ }
+ return thd->killed;
+}
+
+
+/**
+ Auxiliary function which allows to release particular lock
+ ownership of which is represented by lock request object.
+*/
+
+static void release_lock(MDL_LOCK *l)
+{
+ MDL_LOCK_DATA *lock_data;
+
+ DBUG_ENTER("release_lock");
+ DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4,
+ l->key + 4 + strlen(l->key + 4) + 1));
+
+ lock_data= l->lock_data;
+ if (lock_data->has_no_other_users())
+ {
+ my_hash_delete(&mdl_locks, (uchar *)lock_data);
+ DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
+ lock_data->cached_object));
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ release_lock_data_object(lock_data);
+ 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
+ {
+ switch (l->type)
+ {
+ case MDL_SHARED:
+ lock_data->active_shared.remove(l);
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired--;
+ break;
+ case MDL_EXCLUSIVE:
+ if (l->state == MDL_PENDING)
+ lock_data->waiting_exclusive.remove(l);
+ else
+ {
+ lock_data->active_exclusive.remove(l);
+ global_intention_exclusive_locks_acquired--;
+ }
+ break;
+ default:
+ /* TODO Really? How about problems during lock upgrade ? */
+ DBUG_ASSERT(0);
+ }
+ lock_data->users--;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all locks associated with the context, but leave them
+ in the context as lock requests.
+
+ This function is used to back off in case of a lock conflict.
+ It is also used to release shared locks in the end of an SQL
+ statement.
+
+ @param context The context with which the locks to be released
+ are associated.
+*/
+
+void mdl_release_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ DBUG_ENTER("mdl_release_locks");
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ {
+ DBUG_PRINT("info", ("found lock to release l=%p", l));
+ /*
+ We should not release locks which pending shared locks as these
+ are not associated with lock object and don't present in its
+ lists. Allows us to avoid problems in open_tables() in case of
+ back-off
+ */
+ if (!(l->type == MDL_SHARED && l->state == MDL_PENDING))
+ {
+ release_lock(l);
+ l->state= MDL_PENDING;
+#ifndef DBUG_OFF
+ l->lock_data= 0;
+#endif
+ }
+ /*
+ We will return lock request to its initial state only in
+ mdl_remove_all_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);
+ pthread_mutex_unlock(&LOCK_mdl);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all exclusive locks associated with context.
+ Removes the locks from the context.
+
+ @param context Context with exclusive locks.
+
+ @note Shared locks are left intact.
+ @note Resets lock requests for locks released back to their
+ initial state (i.e.sets type and priority to MDL_SHARED
+ and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ {
+ if (l->type == MDL_EXCLUSIVE)
+ {
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ release_lock(l);
+#ifndef DBUG_OFF
+ l->ctx= 0;
+ l->lock_data= 0;
+#endif
+ l->state= MDL_PENDING;
+ /* Return lock request to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
+ context->locks.remove(l);
+ }
+ }
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Release a lock.
+ Removes the lock from the context.
+
+ @param context Context containing lock in question
+ @param lock Lock to be released
+
+ @note Resets lock request for lock released back to its initial state
+ (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr)
+{
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ release_lock(lr);
+#ifndef DBUG_OFF
+ lr->ctx= 0;
+ lr->lock_data= 0;
+#endif
+ lr->state= MDL_PENDING;
+ /* 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);
+}
+
+
+/**
+ Downgrade all exclusive locks in the context to
+ shared.
+
+ @param context A context with exclusive locks.
+*/
+
+void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ if (l->type == MDL_EXCLUSIVE)
+ {
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ if (!l->upgradable)
+ global_intention_exclusive_locks_acquired--;
+ lock_data= l->lock_data;
+ lock_data->active_exclusive.remove(l);
+ l->type= MDL_SHARED;
+ lock_data->active_shared.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);
+}
+
+
+/**
+ Auxiliary function which allows to check if we have exclusive lock
+ on the object.
+
+ @param context Current context
+ @param type Id of object type
+ @param db Name of the database
+ @param name Name of the object
+
+ @return TRUE if current context contains exclusive lock for the object,
+ FALSE otherwise.
+*/
+
+bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type,
+ const char *db, const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ while ((l= it++) && (l->key_length != key_length || memcmp(l->key, key, key_length)))
+ continue;
+ return (l && l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED);
+}
+
+
+/**
+ Auxiliary function which allows to check if we some kind of lock on
+ the object.
+
+ @param context Current context
+ @param type Id of object type
+ @param db Name of the database
+ @param name Name of the object
+
+ @return TRUE if current context contains satisfied lock for the object,
+ FALSE otherwise.
+*/
+
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+ const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ while ((l= it++) && (l->key_length != key_length ||
+ memcmp(l->key, key, key_length) ||
+ l->state == MDL_PENDING))
+ continue;
+
+ return l;
+}
+
+
+/**
+ Check if we have any pending exclusive locks which conflict with
+ existing shared lock.
+
+ @param l Shared lock against which check should be performed.
+
+ @return TRUE if there are any conflicting locks, FALSE otherwise.
+*/
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK *l)
+{
+ bool result;
+
+ DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_ACQUIRED);
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ result= !(l->lock_data->waiting_exclusive.is_empty() &&
+ l->lock_data->active_shared_waiting_upgrade.is_empty());
+ pthread_mutex_unlock(&LOCK_mdl);
+ return result;
+}
+
+
+/**
+ Associate pointer to an opaque object with a lock.
+
+ @param l Lock request for the lock with which the
+ object should be associated.
+ @param cached_object Pointer to the object
+ @param release_hook Cleanup function to be called when MDL subsystem
+ decides to remove lock or associate another object.
+
+ This is used to cache a pointer to TABLE_SHARE in the lock
+ structure. Such caching can save one acquisition of LOCK_open
+ and one table definition cache lookup for every table.
+
+ Since the pointer may be stored only inside an acquired lock,
+ the caching is only effective when there is more than one lock
+ granted on a given table.
+
+ This function has the following usage pattern:
+ - try to acquire an MDL lock
+ - when done, call for mdl_get_cached_object(). If it returns NULL, our
+ thread has the only lock on this table.
+ - look up TABLE_SHARE in the table definition cache
+ - call mdl_set_cache_object() to assign the share to the opaque pointer.
+
+ The release hook is invoked when the last shared metadata
+ lock on this name is released.
+*/
+
+void mdl_set_cached_object(MDL_LOCK *l, void *cached_object,
+ mdl_cached_object_release_hook release_hook)
+{
+ DBUG_ENTER("mdl_set_cached_object");
+ DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", l->key + 4,
+ l->key + 4 + strlen(l->key + 4) + 1,
+ cached_object));
+
+ DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE);
+
+ /*
+ TODO: This assumption works now since we do mdl_get_cached_object()
+ and mdl_set_cached_object() in the same critical section. Once
+ this becomes false we will have to call release_hook here and
+ use additional mutex protecting 'cached_object' member.
+ */
+ DBUG_ASSERT(!l->lock_data->cached_object);
+
+ l->lock_data->cached_object= cached_object;
+ l->lock_data->cached_object_release_hook= release_hook;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get a pointer to an opaque object that associated with the lock.
+
+ @param l Lock request for the lock with which the object is
+ associated.
+
+ @return Pointer to an opaque object associated with the lock.
+*/
+
+void* mdl_get_cached_object(MDL_LOCK *l)
+{
+ DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE);
+ return l->lock_data->cached_object;
+}
diff --git a/sql/mdl.h b/sql/mdl.h
new file mode 100644
index 00000000000..ca3d8d0a784
--- /dev/null
+++ b/sql/mdl.h
@@ -0,0 +1,260 @@
+#ifndef MDL_H
+#define MDL_H
+/* Copyright (C) 2007-2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include "sql_plist.h"
+#include <my_sys.h>
+#include <m_string.h>
+
+class THD;
+
+struct MDL_LOCK;
+struct MDL_LOCK_DATA;
+struct MDL_CONTEXT;
+
+/** Type of metadata lock request. */
+
+enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE};
+
+
+/** States which metadata lock request can have. */
+
+enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE};
+
+
+/**
+ Priority of metadata lock requests. High priority attribute is
+ applicable only to requests for shared locks and indicates that
+ such request should ignore pending requests for exclusive locks
+ and for upgrading of shared locks to exclusive.
+*/
+
+enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO};
+
+
+/**
+ A pending lock request or a granted metadata lock. A lock is requested
+ or granted based on a fully qualified name and type. E.g. for a table
+ the key consists of <0 (=table)>+<database name>+<table name>.
+ Later in this document this triple will be referred to simply as
+ "key" or "name".
+*/
+
+struct MDL_LOCK
+{
+ char *key;
+ uint key_length;
+ 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:
+ /**
+ Pointers for participating in the list of lock requests for this context.
+ */
+ MDL_LOCK *next_context;
+ MDL_LOCK **prev_context;
+ /**
+ Pointers for participating in the list of satisfied/pending requests
+ for the lock.
+ */
+ MDL_LOCK *next_lock;
+ MDL_LOCK **prev_lock;
+
+ friend struct MDL_LOCK_context;
+ friend struct MDL_LOCK_lock;
+
+public:
+ /*
+ Pointer to the lock object for this lock request. Valid only if this lock
+ request is satisified or is present in the list of pending lock requests
+ for particular lock.
+ */
+ MDL_LOCK_DATA *lock_data;
+ MDL_CONTEXT *ctx;
+};
+
+
+/**
+ Helper class which specifies which members of MDL_LOCK are used for
+ participation in the list lock requests belonging to one context.
+*/
+
+struct MDL_LOCK_context
+{
+ static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+ {
+ return &l->next_context;
+ }
+ static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l)
+ {
+ return &l->prev_context;
+ }
+};
+
+
+/**
+ Helper class which specifies which members of MDL_LOCK are used for
+ participation in the list of satisfied/pending requests for the lock.
+*/
+
+struct MDL_LOCK_lock
+{
+ static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+ {
+ return &l->next_lock;
+ }
+ static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l)
+ {
+ return &l->prev_lock;
+ }
+};
+
+
+/**
+ Context of the owner of metadata locks. I.e. each server
+ connection has such a context.
+*/
+
+struct MDL_CONTEXT
+{
+ I_P_List <MDL_LOCK, MDL_LOCK_context> locks;
+ bool has_global_shared_lock;
+ THD *thd;
+};
+
+
+void mdl_init();
+void mdl_destroy();
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd);
+void mdl_context_destroy(MDL_CONTEXT *context);
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source);
+
+void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db,
+ const char *name);
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+ MEM_ROOT *root);
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+void mdl_remove_all_locks(MDL_CONTEXT *context);
+
+/**
+ Set type of lock request. Can be only applied to pending locks.
+*/
+
+inline void mdl_set_lock_type(MDL_LOCK *lock, enum_mdl_type lock_type)
+{
+ DBUG_ASSERT(lock->state == MDL_PENDING);
+ lock->type= lock_type;
+}
+
+/**
+ Set priority for lock request. High priority can be only set
+ for shared locks.
+*/
+
+inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio)
+{
+ DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+ lock->prio= prio;
+}
+
+/**
+ Mark request for shared lock as upgradable. Can be only applied
+ to pending locks.
+*/
+
+inline void mdl_set_upgradable(MDL_LOCK *lock)
+{
+ DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+ lock->upgradable= TRUE;
+}
+
+bool mdl_acquire_shared_lock(MDL_LOCK *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_LOCK *lock);
+bool mdl_acquire_global_shared_lock(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);
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *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);
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+ const char *name);
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK *l);
+
+inline bool mdl_has_locks(MDL_CONTEXT *context)
+{
+ return !context->locks.is_empty();
+}
+
+
+/**
+ Get iterator for walking through all lock requests in the context.
+*/
+
+inline I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> mdl_get_locks(MDL_CONTEXT *ctx)
+{
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> result(ctx->locks);
+ return result;
+}
+
+/**
+ Give metadata lock request object for the table get table definition
+ cache key corresponding to it.
+
+ @param l [in] Lock request object for the table.
+ @param key [out] LEX_STRING object where table definition cache key
+ should be put.
+
+ @note This key will have the same life-time as this lock request object.
+
+ @note This is yet another place where border between MDL subsystem and the
+ rest of the server is broken. OTOH it allows to save some CPU cycles
+ and memory by avoiding generating these TDC keys from table list.
+*/
+
+inline void mdl_get_tdc_key(MDL_LOCK *l, LEX_STRING *key)
+{
+ key->str= l->key + 4;
+ key->length= l->key_length - 4;
+}
+
+
+typedef void (* mdl_cached_object_release_hook)(void *);
+void* mdl_get_cached_object(MDL_LOCK *l);
+void mdl_set_cached_object(MDL_LOCK *l, void *cached_object,
+ mdl_cached_object_release_hook release_hook);
+
+#endif
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index c6454679e28..be03759b383 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -805,7 +805,7 @@ extern my_decimal decimal_zero;
void free_items(Item *item);
void cleanup_items(Item *item);
class THD;
-void close_thread_tables(THD *thd);
+void close_thread_tables(THD *thd, bool skip_mdl= 0);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables);
@@ -993,7 +993,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
bool drop_temporary, bool drop_view, bool log_query);
bool quick_rm_table(handlerton *base,const char *db,
const char *table_name, uint flags);
-void close_cached_table(THD *thd, TABLE *table);
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
char *new_table_name, char *new_table_alias,
@@ -1034,10 +1033,8 @@ bool check_dup(const char *db, const char *name, TABLE_LIST *tables);
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_cache_init(void);
-void table_cache_free(void);
bool table_def_init(void);
void table_def_free(void);
void assign_new_table_id(TABLE_SHARE *share);
@@ -1223,28 +1220,30 @@ void release_table_share(TABLE_SHARE *share, enum release_type type);
TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name);
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
uint lock_flags);
+enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY,
+ OT_DISCOVER, OT_REPAIR};
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
- bool *refresh, uint flags);
+ enum_open_table_action *action, uint flags);
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags);
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in);
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
- uint key_length);
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name, TABLE **table);
-TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list);
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
+TABLE *find_write_locked_table(TABLE *list, const char *db,
+ const char *table_name);
void detach_merge_children(TABLE *table, bool clear_refs);
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);
thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table);
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);
bool wait_for_tables(THD *thd);
bool table_is_used(TABLE *table, bool wait_for_name_lock);
-TABLE *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 unlock_locked_tables(THD *thd);
void execute_init_command(THD *thd, sys_var_str *init_command_var,
rw_lock_t *var_mutex);
extern Field *not_found_field;
@@ -1364,7 +1363,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables);
bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows);
void mysql_ha_flush(THD *thd);
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked);
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
void mysql_ha_cleanup(THD *thd);
/* sql_base.cc */
@@ -1501,7 +1500,7 @@ void free_io_cache(TABLE *entry);
void intern_close_table(TABLE *entry);
bool close_thread_table(THD *thd, TABLE **table_ptr);
void close_temporary_tables(THD *thd);
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables);
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl);
TABLE_LIST *find_table_in_list(TABLE_LIST *table,
TABLE_LIST *TABLE_LIST::*link,
const char *db_name,
@@ -1550,6 +1549,9 @@ char *generate_partition_syntax(partition_info *part_info,
#define RTFC_CHECK_KILLED_FLAG 0x0004
bool remove_table_from_cache(THD *thd, const char *db, const char *table,
uint flags);
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use);
+void expel_table_from_cache(THD *leave_thd, const char *db,
+ const char *table_name);
#define NORMAL_PART_NAME 0
#define TEMP_PART_NAME 1
@@ -1681,7 +1683,7 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
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);
@@ -2001,8 +2003,9 @@ extern const char *opt_date_time_formats[];
extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[];
extern String null_string;
-extern HASH open_cache, lock_db_cache;
+extern HASH table_def_cache, lock_db_cache;
extern TABLE *unused_tables;
+extern uint table_cache_count;
extern const char* any_db;
extern struct my_option my_long_options[];
extern const LEX_STRING view_type;
@@ -2070,18 +2073,8 @@ void unset_protect_against_global_read_lock(void);
void broadcast_refresh(void);
/* Lock based on name */
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list);
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use);
-void unlock_table_name(THD *thd, TABLE_LIST *table_list);
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list);
bool lock_table_names(THD *thd, TABLE_LIST *table_list);
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
- TABLE_LIST *last_table);
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd,
- TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
- int key_length);
+void unlock_table_names(THD *thd);
/* old unireg functions */
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 1cab245b317..90924b5b662 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -1301,9 +1301,9 @@ void clean_up(bool print_message)
grant_free();
#endif
query_cache_destroy();
- table_cache_free();
table_def_free();
hostname_cache_free();
+ mdl_destroy();
item_user_lock_free();
lex_free(); /* Free some memory */
item_create_cleanup();
@@ -3755,7 +3755,8 @@ static int init_server_components()
We need to call each of these following functions to ensure that
all things are initialized so that unireg_abort() doesn't fail
*/
- if (table_cache_init() | table_def_init() | hostname_cache_init())
+ mdl_init();
+ if (table_def_init() | hostname_cache_init())
unireg_abort(1);
query_cache_result_size_limit(query_cache_limit);
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
index ae3cbf789a5..42cd12bd66c 100644
--- a/sql/rpl_rli.cc
+++ b/sql/rpl_rli.cc
@@ -1187,8 +1187,7 @@ void Relay_log_info::cleanup_context(THD *thd, bool error)
end_trans(thd, ROLLBACK); // if a "real transaction"
}
m_table_map.clear_tables();
- close_thread_tables(thd);
- clear_tables_to_lock();
+ slave_close_thread_tables(thd);
clear_flag(IN_STMT);
/*
Cleanup for the flags that have been set at do_apply_event.
@@ -1200,6 +1199,13 @@ void Relay_log_info::cleanup_context(THD *thd, bool error)
void Relay_log_info::clear_tables_to_lock()
{
+ /*
+ Deallocating elements of table list below will also free memory where
+ meta-data locks are stored. So we want to be sure that we don't have
+ any references to this memory left.
+ */
+ DBUG_ASSERT(!mdl_has_locks(&(current_thd->mdl_context)));
+
while (tables_to_lock)
{
uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock);
@@ -1216,4 +1222,15 @@ void Relay_log_info::clear_tables_to_lock()
DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0);
}
+void Relay_log_info::slave_close_thread_tables(THD *thd)
+{
+ /*
+ Since we use same memory chunks for allocation of metadata lock
+ objects for tables as we use for allocating corresponding elements
+ of 'tables_to_lock' list, we have to release metadata locks by
+ closing tables before calling clear_tables_to_lock().
+ */
+ close_thread_tables(thd);
+ clear_tables_to_lock();
+}
#endif
diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h
index fd36d18adae..332bc4e53d0 100644
--- a/sql/rpl_rli.h
+++ b/sql/rpl_rli.h
@@ -346,6 +346,7 @@ public:
bool cached_charset_compare(char *charset) const;
void cleanup_context(THD *, bool);
+ void slave_close_thread_tables(THD *);
void clear_tables_to_lock();
/*
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 1028e5441ae..1490ffd9598 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -4333,7 +4333,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
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 --git a/sql/sp_head.cc b/sql/sp_head.cc
index f90aefc2a3f..14573cd6884 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3984,6 +3984,9 @@ sp_head::add_used_tables_to_table_list(THD *thd,
table->prelocking_placeholder= 1;
table->belong_to_view= belong_to_view;
table->trg_event_map= stab->trg_event_map;
+ table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
/* Everyting else should be zeroed */
@@ -4025,7 +4028,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
table->lock_type= locktype;
table->select_lex= lex->current_select;
table->cacheable_table= 1;
-
+ table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
lex->add_to_query_tables(table);
return table;
}
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
index 641423605aa..b01e7b1049d 100644
--- a/sql/sql_acl.cc
+++ b/sql/sql_acl.cc
@@ -676,12 +676,7 @@ my_bool acl_reload(THD *thd)
my_bool return_val= 1;
DBUG_ENTER("acl_reload");
- if (thd->locked_tables)
- { // Can't have locked tables here
- thd->lock=thd->locked_tables;
- thd->locked_tables=0;
- close_thread_tables(thd);
- }
+ unlock_locked_tables(thd); // Can't have locked tables here
/*
To avoid deadlocks we should obtain table locks before
@@ -697,6 +692,7 @@ my_bool acl_reload(THD *thd)
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ;
tables[0].skip_temporary= tables[1].skip_temporary=
tables[2].skip_temporary= TRUE;
+ alloc_mdl_locks(tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, tables))
{
@@ -1601,6 +1597,7 @@ bool change_password(THD *thd, const char *host, const char *user,
bzero((char*) &tables, sizeof(tables));
tables.alias= tables.table_name= (char*) "user";
tables.db= (char*) "mysql";
+ alloc_mdl_locks(&tables, thd->mem_root);
#ifdef HAVE_REPLICATION
/*
@@ -3053,6 +3050,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
? tables+2 : 0);
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE;
tables[0].db=tables[1].db=tables[2].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3270,6 +3268,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc,
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type=tables[1].lock_type=TL_WRITE;
tables[0].db=tables[1].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3408,6 +3407,7 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list,
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type=tables[1].lock_type=TL_WRITE;
tables[0].db=tables[1].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3741,6 +3741,7 @@ static my_bool grant_reload_procs_priv(THD *thd)
table.db= (char *) "mysql";
table.lock_type= TL_READ;
table.skip_temporary= 1;
+ alloc_mdl_locks(&table, thd->mem_root);
if (simple_open_n_lock_tables(thd, &table))
{
@@ -3807,6 +3808,8 @@ my_bool grant_reload(THD *thd)
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type= tables[1].lock_type= TL_READ;
tables[0].skip_temporary= tables[1].skip_temporary= TRUE;
+ alloc_mdl_locks(tables, thd->mem_root);
+
/*
To avoid deadlocks we should obtain table locks before
obtaining LOCK_grant rwlock.
@@ -5152,6 +5155,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables)
(tables+4)->lock_type= TL_WRITE;
tables->db= (tables+1)->db= (tables+2)->db=
(tables+3)->db= (tables+4)->db= (char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
#ifdef HAVE_REPLICATION
/*
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 8f31ef6999a..de4aaac633e 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -96,17 +96,32 @@ bool Prelock_error_handler::safely_trapped_errors()
@defgroup Data_Dictionary Data Dictionary
@{
*/
-TABLE *unused_tables; /* Used by mysql_test */
-HASH open_cache; /* Used by mysql_test */
-static HASH table_def_cache;
+
+/**
+ Total number of TABLE instances for tables in the table definition cache
+ (both in use by threads and not in use). This value is accessible to user
+ as "Open_tables" status variable.
+*/
+uint table_cache_count= 0;
+/**
+ List that contains all TABLE instances for tables in the table definition
+ cache that are not in use by any thread. Recently used TABLE instances are
+ appended to the end of the list. Thus the beginning of the list contains
+ tables which have been least recently used.
+*/
+TABLE *unused_tables;
+HASH table_def_cache;
static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
static pthread_mutex_t LOCK_table_share;
static bool table_def_inited= 0;
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
- const char *alias,
- char *cache_key, uint cache_key_length,
- MEM_ROOT *mem_root, uint flags);
+static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
+ TABLE_SHARE *table_share);
+static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+ const char *alias, char *cache_key,
+ uint cache_key_length);
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
static void free_cache_entry(TABLE *entry);
static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
@@ -114,41 +129,14 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh);
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
-extern "C" uchar *table_cache_key(const uchar *record, size_t *length,
- my_bool not_used __attribute__((unused)))
-{
- TABLE *entry=(TABLE*) record;
- *length= entry->s->table_cache_key.length;
- return (uchar*) entry->s->table_cache_key.str;
-}
-
-
-bool table_cache_init(void)
-{
- return my_hash_init(&open_cache, &my_charset_bin, table_cache_size+16,
- 0, 0, table_cache_key,
- (my_hash_free_key) free_cache_entry, 0) != 0;
-}
-
-void table_cache_free(void)
-{
- DBUG_ENTER("table_cache_free");
- if (table_def_inited)
- {
- close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
- if (!open_cache.records) // Safety first
- my_hash_free(&open_cache);
- }
- DBUG_VOID_RETURN;
-}
-
uint cached_open_tables(void)
{
- return open_cache.records;
+ return table_cache_count;
}
@@ -156,7 +144,8 @@ uint cached_open_tables(void)
static void check_unused(void)
{
uint count= 0, open_files= 0, idx= 0;
- TABLE *cur_link,*start_link;
+ TABLE *cur_link, *start_link, *entry;
+ TABLE_SHARE *share;
if ((start_link=cur_link=unused_tables))
{
@@ -167,45 +156,42 @@ static void check_unused(void)
DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */
return; /* purecov: inspected */
}
- } while (count++ < open_cache.records &&
+ } while (count++ < table_cache_count &&
(cur_link=cur_link->next) != start_link);
if (cur_link != start_link)
{
DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
}
}
- for (idx=0 ; idx < open_cache.records ; idx++)
+ for (idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- if (!entry->in_use)
+ share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ while ((entry= it++))
+ {
+ if (entry->in_use)
+ {
+ DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
+ }
count--;
- if (entry->file)
open_files++;
+ }
+ it.init(share->used_tables);
+ while ((entry= it++))
+ {
+ if (!entry->in_use)
+ {
+ DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
+ }
+ open_files++;
+ }
}
if (count != 0)
{
DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
count)); /* purecov: inspected */
}
-
-#ifdef NOT_SAFE_FOR_REPAIR
- /*
- check that open cache and table definition cache has same number of
- aktive tables
- */
- count= 0;
- for (idx=0 ; idx < table_def_cache.records ; idx++)
- {
- TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx);
- count+= entry->ref_count;
- }
- if (count != open_files)
- {
- DBUG_PRINT("error", ("table_def ref_count: %u open_cache: %u",
- count, open_files));
- DBUG_ASSERT(count == open_files);
- }
-#endif
}
#else
#define check_unused()
@@ -300,8 +286,11 @@ void table_def_free(void)
DBUG_ENTER("table_def_free");
if (table_def_inited)
{
+ /* Free all open TABLEs first. */
+ close_cached_tables(NULL, NULL, FALSE, FALSE);
table_def_inited= 0;
pthread_mutex_destroy(&LOCK_table_share);
+ /* Free table definitions. */
my_hash_free(&table_def_cache);
}
DBUG_VOID_RETURN;
@@ -315,6 +304,128 @@ uint cached_table_definitions(void)
/*
+ Auxiliary routines for manipulating with per-share used/unused and
+ global unused lists of TABLE objects and table_cache_count counter.
+ Responsible for preserving invariants between those lists, counter
+ and TABLE::in_use member.
+ In fact those routines implement sort of implicit table cache as
+ part of table definition cache.
+*/
+
+
+/**
+ Add newly created TABLE object for table share which is going
+ to be used right away.
+*/
+
+static void table_def_add_used_table(THD *thd, TABLE *table)
+{
+ DBUG_ASSERT(table->in_use == thd);
+ table->s->used_tables.push_front(table);
+ table_cache_count++;
+}
+
+
+/**
+ Prepare used or unused TABLE instance for destruction by removing
+ it from share's and global list.
+*/
+
+static void table_def_remove_table(TABLE *table)
+{
+ if (table->in_use)
+ {
+ /* Remove from per-share chain of used TABLE objects. */
+ table->s->used_tables.remove(table);
+ }
+ else
+ {
+ /* Remove from per-share chain of unused TABLE objects. */
+ table->s->free_tables.remove(table);
+
+ /* And global unused chain. */
+ table->next->prev=table->prev;
+ table->prev->next=table->next;
+ if (table == unused_tables)
+ {
+ unused_tables=unused_tables->next;
+ if (table == unused_tables)
+ unused_tables=0;
+ }
+ check_unused();
+ }
+ table_cache_count--;
+}
+
+
+/**
+ Mark already existing TABLE instance as used.
+*/
+
+static void table_def_use_table(THD *thd, TABLE *table)
+{
+ DBUG_ASSERT(!table->in_use);
+
+ /* Unlink table from list of unused tables for this share. */
+ table->s->free_tables.remove(table);
+ /* Unlink able from global unused tables list. */
+ if (table == unused_tables)
+ { // First unused
+ unused_tables=unused_tables->next; // Remove from link
+ if (table == unused_tables)
+ unused_tables=0;
+ }
+ table->prev->next=table->next; /* Remove from unused list */
+ table->next->prev=table->prev;
+ check_unused();
+ /* Add table to list of used tables for this share. */
+ table->s->used_tables.push_front(table);
+ table->in_use= thd;
+}
+
+
+/**
+ Mark already existing used TABLE instance as unused.
+*/
+
+static void table_def_unuse_table(TABLE *table)
+{
+ DBUG_ASSERT(table->in_use);
+
+ table->in_use= 0;
+ /* Remove table from the list of tables used in this share. */
+ table->s->used_tables.remove(table);
+ /* Add table to the list of unused TABLE objects for this share. */
+ table->s->free_tables.push_front(table);
+ /* Also link it last in the global list of unused TABLE objects. */
+ if (unused_tables)
+ {
+ table->next=unused_tables;
+ table->prev=unused_tables->prev;
+ unused_tables->prev=table;
+ table->prev->next=table;
+ }
+ else
+ unused_tables=table->next=table->prev=table;
+ check_unused();
+}
+
+
+/**
+ Bind used TABLE instance to another table share.
+
+ @note Will go away once we refactor code responsible
+ for reopening tables under lock tables.
+*/
+
+static void table_def_change_share(TABLE *table, TABLE_SHARE *new_share)
+{
+ table->s->used_tables.remove(table);
+ new_share->used_tables.push_front(table);
+}
+
+
+/*
Get TABLE_SHARE for a table.
get_table_share()
@@ -347,6 +458,13 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
*error= 0;
+ /*
+ To be able perform any operation on table we should own
+ some kind of metadata lock on it.
+ */
+ DBUG_ASSERT(mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+ table_list->table_name));
+
/* Read table definition from cache */
if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
key_length)))
@@ -569,6 +687,7 @@ void release_table_share(TABLE_SHARE *share, enum release_type type)
safe_mutex_assert_owner(&LOCK_open);
pthread_mutex_lock(&share->mutex);
+ DBUG_ASSERT(share->ref_count);
if (!--share->ref_count)
{
if (share->version != refresh_version)
@@ -629,6 +748,26 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
}
+/**
+ @brief Mark table share as having one more user (increase its reference
+ count).
+
+ @param share Table share for which reference count should be increased.
+*/
+
+static void reference_table_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("reference_table_share");
+ DBUG_ASSERT(share->ref_count);
+ pthread_mutex_lock(&share->mutex);
+ share->ref_count++;
+ pthread_mutex_unlock(&share->mutex);
+ DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
+ (ulong) share, share->ref_count));
+ DBUG_VOID_RETURN;
+}
+
+
/*
Close file handle, but leave the table in the table cache
@@ -680,6 +819,7 @@ void close_handle_and_leave_table_as_lock(TABLE *table)
detach_merge_children(table, FALSE);
table->file->close();
table->db_stat= 0; // Mark file closed
+ table_def_change_share(table, share);
release_table_share(table->s, RELEASE_NORMAL);
table->s= share;
table->file->change_table_ptr(table, table->s);
@@ -719,11 +859,9 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
start_list= &open_list;
open_list=0;
- for (uint idx=0 ; result == 0 && idx < open_cache.records; idx++)
+ for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
{
- OPEN_TABLE_LIST *table;
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- TABLE_SHARE *share= entry->s;
+ TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx);
if (db && my_strcasecmp(system_charset_info, db, share->db.str))
continue;
@@ -737,21 +875,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE))
continue;
- /* need to check if we haven't already listed it */
- for (table= open_list ; table ; table=table->next)
- {
- if (!strcmp(table->table, share->table_name.str) &&
- !strcmp(table->db, share->db.str))
- {
- if (entry->in_use)
- table->in_use++;
- if (entry->locked_by_name)
- table->locked++;
- break;
- }
- }
- if (table)
- continue;
+
if (!(*start_list = (OPEN_TABLE_LIST *)
sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
{
@@ -762,8 +886,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
strmov(((*start_list)->db= (char*) ((*start_list)+1)),
share->db.str)+1,
share->table_name.str);
- (*start_list)->in_use= entry->in_use ? 1 : 0;
- (*start_list)->locked= entry->locked_by_name ? 1 : 0;
+ (*start_list)->in_use= 0;
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ while (it++)
+ ++(*start_list)->in_use;
+ (*start_list)->locked= (share->version == 0) ? 1 : 0;
start_list= &(*start_list)->next;
*start_list=0;
}
@@ -809,19 +936,11 @@ static void free_cache_entry(TABLE *table)
/* Assert that MERGE children are not attached before final close. */
DBUG_ASSERT(!table->is_children_attached());
+ /* This should be done before releasing table share. */
+ table_def_remove_table(table);
+
intern_close_table(table);
- if (!table->in_use)
- {
- table->next->prev=table->prev; /* remove from used chain */
- table->prev->next=table->next;
- if (table == unused_tables)
- {
- unused_tables=unused_tables->next;
- if (table == unused_tables)
- unused_tables=0;
- }
- check_unused(); // consisty check
- }
+
my_free((uchar*) table,MYF(0));
DBUG_VOID_RETURN;
}
@@ -841,6 +960,38 @@ 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)
+{
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ TABLE *tab;
+ while ((tab= it++))
+ {
+ 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
@@ -848,18 +999,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));
@@ -868,165 +1024,181 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
if (!tables)
{
refresh_version++; // Force close of open tables
- while (unused_tables)
- {
-#ifdef EXTRA_DEBUG
- if (my_hash_delete(&open_cache,(uchar*) unused_tables))
- printf("Warning: Couldn't delete open table from hash\n");
-#else
- (void) my_hash_delete(&open_cache,(uchar*) unused_tables);
-#endif
- }
- /* Free table shares */
- while (oldest_unused_share->next)
- {
- pthread_mutex_lock(&oldest_unused_share->mutex);
- (void) my_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 < open_cache.records ; idx++)
- {
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- if (table->in_use)
- 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".
+
+ FIXME: Do not close all unused TABLE instances when flushing
+ particular table.
+ */
+ while (unused_tables)
+ free_cache_entry(unused_tables);
+ /* Free table shares */
+ while (oldest_unused_share->next)
+ {
+ pthread_mutex_lock(&oldest_unused_share->mutex);
+ (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
+ }
+
+ if (!wait_for_refresh)
+ {
+ if (!have_lock)
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(result);
+ }
+
+ DBUG_ASSERT(!have_lock);
+ pthread_mutex_unlock(&LOCK_open);
+
+ if (thd->locked_tables)
{
/*
- If there is any table that has a lower refresh_version, wait until
- this is closed (or this thread is killed) before returning
+ 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.
*/
- thd->mysys_var->current_mutex= &LOCK_open;
- thd->mysys_var->current_cond= &COND_refresh;
- thd_proc_info(thd, "Flushing tables");
+ if (!tables)
+ {
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ /*
+ Checking TABLE::db_stat is essential in case when we have
+ several instances of the table open and locked.
+ */
+ 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;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ close_data_files_and_morph_locks(thd, dbname, tname);
+ 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;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ close_data_files_and_morph_locks(thd, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ }
+ }
+ }
- close_old_data_files(thd,thd->open_tables,1,1);
+ /* Wait until all threads have closed all the tables we are flushing. */
+ DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
+
+ while (found && ! thd->killed)
+ {
+ found= FALSE;
+ /*
+ To avoid self and other kinds of deadlock we have to flush open HANDLERs.
+ */
mysql_ha_flush(thd);
DEBUG_SYNC(thd, "after_flush_unlock");
- 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"));
- while (found && ! thd->killed)
+ pthread_mutex_lock(&LOCK_open);
+
+ thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables");
+
+ if (!tables)
{
- found=0;
- for (uint idx=0 ; idx < open_cache.records ; idx++)
+ for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- /* Avoid a self-deadlock. */
- if (table->in_use == thd)
- continue;
- /*
- Note that we wait here only for tables which are actually open, and
- not for placeholders with TABLE::open_placeholder set. Waiting for
- latter will cause deadlock in the following scenario, for example:
-
- conn1: lock table t1 write;
- conn2: lock table t2 write;
- conn1: flush tables;
- conn2: flush tables;
-
- It also does not make sense to wait for those of placeholders that
- are employed by CREATE TABLE as in this case table simply does not
- exist yet.
- */
- if (table->needs_reopen_or_name_lock() && (table->db_stat ||
- (table->open_placeholder && wait_for_placeholders)))
- {
- found=1;
- DBUG_PRINT("signal", ("Waiting for COND_refresh"));
- pthread_cond_wait(&COND_refresh,&LOCK_open);
- break;
- }
+ TABLE_SHARE *share=(TABLE_SHARE*) my_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)
+ {
+ 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;
- }
- }
- if (!have_lock)
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);
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
}
DBUG_RETURN(result);
}
@@ -1079,7 +1251,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
}
if (tables)
- result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE);
+ result= close_cached_tables(thd, tables, TRUE, FALSE);
if (!have_lock)
pthread_mutex_unlock(&LOCK_open);
@@ -1208,9 +1380,8 @@ static void close_open_tables(THD *thd)
thd->some_tables_deleted= 0;
/* Free tables to hold down open files */
- while (open_cache.records > table_cache_size && unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
- check_unused();
+ while (table_cache_count > table_cache_size && unused_tables)
+ free_cache_entry(unused_tables);
if (found_old_table)
{
/* Tell threads waiting for refresh that something has happened */
@@ -1239,7 +1410,8 @@ static void close_open_tables(THD *thd)
leave prelocked mode if needed.
*/
-void close_thread_tables(THD *thd)
+void close_thread_tables(THD *thd,
+ bool skip_mdl)
{
TABLE *table;
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
@@ -1328,6 +1500,10 @@ void close_thread_tables(THD *thd)
if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
DBUG_VOID_RETURN;
+ /*
+ Note that we are leaving prelocked mode so we don't need
+ to care about THD::locked_tables_root.
+ */
thd->lock= thd->locked_tables;
thd->locked_tables= 0;
/* Fallthrough */
@@ -1358,6 +1534,12 @@ void close_thread_tables(THD *thd)
if (thd->open_tables)
close_open_tables(thd);
+ mdl_release_locks(&thd->mdl_context);
+ if (!skip_mdl)
+ {
+ mdl_remove_all_locks(&thd->mdl_context);
+ }
+
if (prelocked_mode == PRELOCKED)
{
/*
@@ -1381,8 +1563,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
DBUG_ENTER("close_thread_table");
DBUG_ASSERT(table->key_read == 0);
DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
- DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
+ safe_mutex_assert_owner(&LOCK_open);
*table_ptr=table->next;
/*
@@ -1392,10 +1573,11 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
+ table->mdl_lock= 0;
if (table->needs_reopen_or_name_lock() ||
thd->version != refresh_version || !table->db_stat)
{
- my_hash_delete(&open_cache,(uchar*) table);
+ free_cache_entry(table);
found_old_table=1;
}
else
@@ -1413,16 +1595,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE);
table->file->ha_reset();
- table->in_use=0;
- if (unused_tables)
- {
- table->next=unused_tables; /* Link in last */
- table->prev=unused_tables->prev;
- unused_tables->prev=table;
- table->prev->next=table;
- }
- else
- unused_tables=table->next=table->prev=table;
+ table_def_unuse_table(table);
}
DBUG_RETURN(found_old_table);
}
@@ -2121,7 +2294,7 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock)
/* Remove table from open_tables list. */
*prev= list->next;
/* Close table. */
- my_hash_delete(&open_cache,(uchar*) list); // Close table
+ free_cache_entry(list);
}
else
{
@@ -2231,43 +2404,34 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
{
+ bool result= TRUE;
+
DBUG_ENTER("name_lock_locked_table");
/* Under LOCK TABLES we must only accept write locked tables. */
- tables->table= find_locked_table(thd, tables->db, tables->table_name);
+ tables->table= find_write_locked_table(thd->open_tables, tables->db,
+ tables->table_name);
- if (!tables->table)
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias);
- else if (tables->table->reginfo.lock_type < TL_WRITE_LOW_PRIORITY)
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->alias);
- else
+ if (tables->table)
{
/*
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);
+ result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
}
- DBUG_RETURN(TRUE);
+ DBUG_RETURN(result);
}
/*
- Open table which is already name-locked by this thread.
+ Open table for which this thread has exclusive meta-data lock.
SYNOPSIS
reopen_name_locked_table()
thd Thread handle
- table_list TABLE_LIST object for table to be open, TABLE_LIST::table
- member should point to TABLE object which was used for
- name-locking.
- link_in TRUE - if TABLE object for table to be opened should be
- linked into THD::open_tables list.
- FALSE - placeholder used for name-locking is already in
- this list so we only need to preserve TABLE::next
- pointer.
+ table_list TABLE_LIST object for table to be open.
NOTE
This function assumes that its caller already acquired LOCK_open mutex.
@@ -2277,33 +2441,32 @@ bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
TRUE - Error
*/
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
{
- TABLE *table= table_list->table;
+ TABLE *table;
TABLE_SHARE *share;
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
char *table_name= table_list->table_name;
- TABLE orig_table;
DBUG_ENTER("reopen_name_locked_table");
- safe_mutex_assert_owner(&LOCK_open);
-
- if (thd->killed || !table)
+ if (thd->killed)
DBUG_RETURN(TRUE);
- orig_table= *table;
+ key_length= create_table_def_key(thd, key, table_list, 0);
+
+ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+ DBUG_RETURN(TRUE);
- if (open_unireg_entry(thd, table, table_list, table_name,
- table->s->table_cache_key.str,
- table->s->table_cache_key.length, thd->mem_root, 0))
+ if (reopen_table_entry(thd, table, table_list, table_name, key, key_length))
{
- intern_close_table(table);
/*
If there was an error during opening of table (for example if it
does not exist) '*table' object can be wiped out. To be able
properly release name-lock in this case we should restore this
object to its original state.
*/
- *table= orig_table;
+ my_free((uchar*)table, MYF(0));
DBUG_RETURN(TRUE);
}
@@ -2318,21 +2481,11 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
*/
share->version=0;
table->in_use = thd;
- check_unused();
- if (link_in)
- {
- table->next= thd->open_tables;
- thd->open_tables= table;
- }
- else
- {
- /*
- TABLE object should be already in THD::open_tables list so we just
- need to set TABLE::next correctly.
- */
- table->next= orig_table.next;
- }
+ table_def_add_used_table(thd, table);
+
+ table->next= thd->open_tables;
+ thd->open_tables= table;
table->tablenr=thd->current_tablenr++;
table->used_fields=0;
@@ -2340,109 +2493,7 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
table->null_row= table->maybe_null= 0;
table->force_index= table->force_index_order= table->force_index_group= 0;
table->status=STATUS_NO_RECORD;
- DBUG_RETURN(FALSE);
-}
-
-
-/**
- Create and insert into table cache placeholder for table
- which will prevent its opening (or creation) (a.k.a lock
- table name).
-
- @param thd Thread context
- @param key Table cache key for name to be locked
- @param key_length Table cache key length
-
- @return Pointer to TABLE object used for name locking or 0 in
- case of failure.
-*/
-
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
- uint key_length)
-{
- TABLE *table;
- TABLE_SHARE *share;
- char *key_buff;
- DBUG_ENTER("table_cache_insert_placeholder");
-
- safe_mutex_assert_owner(&LOCK_open);
-
- /*
- Create a table entry with the right key and with an old refresh version
- Note that we must use my_multi_malloc() here as this is freed by the
- table cache
- */
- if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
- &table, sizeof(*table),
- &share, sizeof(*share),
- &key_buff, key_length,
- NULL))
- DBUG_RETURN(NULL);
-
- table->s= share;
- share->set_table_cache_key(key_buff, key, key_length);
- share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table
- table->in_use= thd;
- table->locked_by_name=1;
-
- if (my_hash_insert(&open_cache, (uchar*)table))
- {
- my_free((uchar*) table, MYF(0));
- DBUG_RETURN(NULL);
- }
-
- DBUG_RETURN(table);
-}
-
-
-/**
- Obtain an exclusive name lock on the table if it is not cached
- in the table cache.
-
- @param thd Thread context
- @param db Name of database
- @param table_name Name of table
- @param[out] table Out parameter which is either:
- - set to NULL if table cache contains record for
- the table or
- - set to point to the TABLE instance used for
- name-locking.
-
- @note This function takes into account all records for table in table
- cache, even placeholders used for name-locking. This means that
- 'table' parameter can be set to NULL for some situations when
- table does not really exist.
-
- @retval TRUE Error occured (OOM)
- @retval FALSE Success. 'table' parameter set according to above rules.
-*/
-
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name, TABLE **table)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
- DBUG_ENTER("lock_table_name_if_not_cached");
-
- key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1;
- pthread_mutex_lock(&LOCK_open);
-
- if (my_hash_search(&open_cache, (uchar *)key, key_length))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_PRINT("info", ("Table is cached, name-lock is not obtained"));
- *table= 0;
- DBUG_RETURN(FALSE);
- }
- if (!(*table= table_cache_insert_placeholder(thd, key, key_length)))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(TRUE);
- }
- (*table)->open_placeholder= 1;
- (*table)->next= thd->open_tables;
- thd->open_tables= *table;
- pthread_mutex_unlock(&LOCK_open);
+ table_list->table= table;
DBUG_RETURN(FALSE);
}
@@ -2511,6 +2562,20 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
}
+/**
+ @brief Helper function used by MDL subsystem for releasing TABLE_SHARE
+ objects in cases when it no longer wants to cache reference to it.
+*/
+
+void table_share_release_hook(void *share)
+{
+ pthread_mutex_lock(&LOCK_open);
+ release_table_share((TABLE_SHARE*)share, RELEASE_NORMAL);
+ broadcast_refresh();
+ pthread_mutex_unlock(&LOCK_open);
+}
+
+
/*
Open a table.
@@ -2518,13 +2583,17 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
open_table()
thd Thread context.
table_list Open first table in list.
- refresh INOUT Pointer to memory that will be set to 1 if
- we need to close all tables and reopen them.
+ action INOUT Pointer to variable of enum_open_table_action type
+ which will be set according to action which is
+ required to remedy problem appeared during attempt
+ to open table.
If this is a NULL pointer, then the table is not
put in the thread-open-list.
flags Bitmap of flags to modify how open works:
MYSQL_LOCK_IGNORE_FLUSH - Open table even if
- someone has done a flush or namelock on it.
+ someone has done a flush or there is a pending
+ exclusive metadata lock requests against it
+ (i.e. request high priority metadata lock).
No version number checking is done.
MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary
table not the base table or view.
@@ -2545,21 +2614,23 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
- bool *refresh, uint flags)
+ enum_open_table_action *action, uint flags)
{
reg1 TABLE *table;
char key[MAX_DBKEY_LENGTH];
uint key_length;
char *alias= table_list->alias;
- HASH_SEARCH_STATE state;
+ MDL_LOCK *mdl_lock;
+ int error;
+ TABLE_SHARE *share;
DBUG_ENTER("open_table");
/* Parsing of partitioning information from .frm needs thd->lex set up. */
DBUG_ASSERT(thd->lex->is_lex_started);
/* find a unused table in the open table cache */
- if (refresh)
- *refresh=0;
+ if (action)
+ *action= OT_NO_ACTION;
/* an open table operation needs a lot of the stack space */
if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
@@ -2685,7 +2756,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
Is this table a view and not a base table?
(it is work around to allow to open view with locked tables,
real fix will be made after definition cache will be made)
+
+ Since opening of view which was not explicitly locked by LOCK
+ TABLES breaks metadata locking protocol (potentially can lead
+ to deadlocks) it should be disallowed.
*/
+ if (mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+ table_list->table_name))
{
char path[FN_REFLEN + 1];
enum legacy_db_type not_used;
@@ -2693,21 +2770,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table_list->db, table_list->table_name, reg_ext, 0);
if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
{
- /*
- Will not be used (because it's VIEW) but has to be passed.
- Also we will not free it (because it is a stack variable).
- */
- TABLE tab;
- table= &tab;
- pthread_mutex_lock(&LOCK_open);
- if (!open_unireg_entry(thd, table, table_list, alias,
- key, key_length, mem_root, 0))
+ if (!tdc_open_view(thd, table_list, alias, key, key_length,
+ mem_root, 0))
{
DBUG_ASSERT(table_list->view != 0);
- pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0); // VIEW
}
- pthread_mutex_unlock(&LOCK_open);
}
}
/*
@@ -2740,6 +2808,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
on disk.
*/
+ mdl_lock= table_list->mdl_lock;
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+
+ if (table_list->open_table_type)
+ {
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ /* TODO: This case can be significantly optimized. */
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ bool retry;
+
+ if (table_list->mdl_upgradable)
+ mdl_set_upgradable(mdl_lock);
+ mdl_set_lock_priority(mdl_lock, (flags & MYSQL_LOCK_IGNORE_FLUSH) ?
+ MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
+ if (mdl_acquire_shared_lock(mdl_lock, &retry))
+ {
+ if (action && retry)
+ *action= OT_BACK_OFF_AND_RETRY;
+ DBUG_RETURN(0);
+ }
+ }
+
pthread_mutex_lock(&LOCK_open);
/*
@@ -2756,222 +2850,210 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
! (flags & MYSQL_LOCK_IGNORE_FLUSH))
{
/* Someone did a refresh while thread was opening tables */
- if (refresh)
- *refresh=1;
+ if (action)
+ *action= OT_BACK_OFF_AND_RETRY;
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
- /*
- In order for the back off and re-start process to work properly,
- handler tables having old versions (due to FLUSH TABLES or pending
- name-lock) MUST be closed. This is specially important if a name-lock
- is pending for any table of the handler_tables list, otherwise a
- deadlock may occur.
- */
- if (thd->handler_tables)
- mysql_ha_flush(thd);
-
- /*
- Actually try to find the table in the open_cache.
- The cache may contain several "TABLE" instances for the same
- physical table. The instances that are currently "in use" by
- some thread have their "in_use" member != NULL.
- There is no good reason for having more than one entry in the
- hash for the same physical table, except that we use this as
- an implicit "pending locks queue" - see
- wait_for_locked_table_names for details.
- */
- for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length,
- &state);
- table && table->in_use ;
- table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length,
- &state))
+ if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE)
{
- DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
- /*
- Here we flush tables marked for flush.
- Normally, table->s->version contains the value of
- refresh_version from the moment when this table was
- (re-)opened and added to the cache.
- If since then we did (or just started) FLUSH TABLES
- statement, refresh_version has been increased.
- For "name-locked" TABLE instances, table->s->version is set
- to 0 (see lock_table_name for details).
- In case there is a pending FLUSH TABLES or a name lock, we
- need to back off and re-start opening tables.
- If we do not back off now, we may dead lock in case of lock
- order mismatch with some other thread:
- c1: name lock t1; -- sort of exclusive lock
- c2: open t2; -- sort of shared lock
- c1: name lock t2; -- blocks
- c2: open t1; -- blocks
- */
- if (table->needs_reopen_or_name_lock())
- {
- DBUG_PRINT("note",
- ("Found table '%s.%s' with different refresh version",
- table_list->db, table_list->table_name));
+ bool exists;
- if (flags & MYSQL_LOCK_IGNORE_FLUSH)
- {
- /* Force close at once after usage */
- thd->version= table->s->version;
- continue;
- }
+ if (check_if_table_exists(thd, table_list, &exists))
+ goto err_unlock2;
- /* Avoid self-deadlocks by detecting self-dependencies. */
- if (table->open_placeholder && table->in_use == thd)
- {
- pthread_mutex_unlock(&LOCK_open);
- my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
- DBUG_RETURN(0);
- }
+ if (!exists)
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ /* Table exists. Let us try to open it. */
+ }
+ else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL)
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock)))
+ {
+ if (!(share= get_table_share_with_create(thd, table_list, key,
+ key_length, OPEN_VIEW,
+ &error)))
+ goto err_unlock2;
+
+ if (share->is_view)
+ {
/*
- Back off, part 1: mark the table as "unused" for the
- purpose of name-locking by setting table->db_stat to 0. Do
- that only for the tables in this thread that have an old
- table->s->version (this is an optimization (?)).
- table->db_stat == 0 signals wait_for_locked_table_names
- that the tables in question are not used any more. See
- table_is_used call for details.
-
- Notice that HANDLER tables were already taken care of by
- the earlier call to mysql_ha_flush() in this same critical
- section.
- */
- close_old_data_files(thd,thd->open_tables,0,0);
- /*
- Back-off part 2: try to avoid "busy waiting" on the table:
- if the table is in use by some other thread, we suspend
- and wait till the operation is complete: when any
- operation that juggles with table->s->version completes,
- it broadcasts COND_refresh condition variable.
- If 'old' table we met is in use by current thread we return
- without waiting since in this situation it's this thread
- which is responsible for broadcasting on COND_refresh
- (and this was done already in close_old_data_files()).
- Good example of such situation is when we have statement
- that needs two instances of table and FLUSH TABLES comes
- after we open first instance but before we open second
- instance.
+ This table is a view. Validate its metadata version: in particular,
+ that it was a view when the statement was prepared.
*/
- if (table->in_use != thd)
+ if (check_and_update_table_version(thd, table_list, share))
+ goto err_unlock;
+ if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
+ goto err_unlock;
+
+ /* Open view */
+ if (open_new_frm(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+ (flags & OPEN_VIEW_NO_PARSE), thd->open_options,
+ 0, table_list, mem_root))
+ goto err_unlock;
+
+ /* TODO: Don't free this */
+ release_table_share(share, RELEASE_NORMAL);
+
+ if (flags & OPEN_VIEW_NO_PARSE)
{
- /* wait_for_conditionwill unlock LOCK_open for us */
- wait_for_condition(thd, &LOCK_open, &COND_refresh);
+ /*
+ VIEW not really opened, only frm were read.
+ Set 1 as a flag here
+ */
+ table_list->view= (LEX*)1;
}
else
{
- pthread_mutex_unlock(&LOCK_open);
+ DBUG_ASSERT(table_list->view);
}
+
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ else if (table_list->view)
+ {
/*
- There is a refresh in progress for this table.
- Signal the caller that it has to try again.
+ We're trying to open a table for what was a view.
+ This can only happen during (re-)execution.
+ At prepared statement prepare the view has been opened and
+ merged into the statement parse tree. After that, someone
+ performed a DDL and replaced the view with a base table.
+ Don't try to open the table inside a prepared statement,
+ invalidate it instead.
+
+ Note, the assert below is known to fail inside stored
+ procedures (Bug#27011).
*/
- if (refresh)
- *refresh=1;
- DBUG_RETURN(0);
+ DBUG_ASSERT(thd->m_reprepare_observer);
+ check_and_update_table_version(thd, table_list, share);
+ /* Always an error. */
+ DBUG_ASSERT(thd->is_error());
+ goto err_unlock;
}
+
+ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ goto err_unlock;
+
+ /*
+ We are going to to store extra reference to the share in MDL-subsystem
+ so we need to increase reference counter;
+ */
+ reference_table_share(share);
+ mdl_set_cached_object(mdl_lock, share, table_share_release_hook);
}
- if (table)
+ else
{
- DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
- /* Unlink the table from "unused_tables" list. */
- if (table == unused_tables)
- { // First unused
- unused_tables=unused_tables->next; // Remove from link
- if (table == unused_tables)
- unused_tables=0;
+ if (table_list->view)
+ {
+ DBUG_ASSERT(thd->m_reprepare_observer);
+ check_and_update_table_version(thd, table_list, share);
+ /* Always an error. */
+ DBUG_ASSERT(thd->is_error());
+ goto err_unlock;
}
- table->prev->next=table->next; /* Remove from unused list */
- table->next->prev=table->prev;
- table->in_use= thd;
+ /* When we have cached TABLE_SHARE we know that is not a view. */
+ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ goto err_unlock;
+
+ /*
+ We are going to use this share for construction of new TABLE object
+ so reference counter should be increased.
+ */
+ reference_table_share(share);
+ }
+
+ if (share->version != refresh_version)
+ {
+ if (!(flags & MYSQL_LOCK_IGNORE_FLUSH))
+ {
+ if (action)
+ *action= OT_BACK_OFF_AND_RETRY;
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ /* Force close at once after usage */
+ thd->version= share->version;
+ }
+
+ if (!share->free_tables.is_empty())
+ {
+ table= share->free_tables.head();
+ table_def_use_table(thd, table);
+ /* We need to release share as we have EXTRA reference to it in our hands. */
+ release_table_share(share, RELEASE_NORMAL);
}
else
{
- /* Insert a new TABLE instance into the open cache */
- int error;
- DBUG_PRINT("tcache", ("opening new table"));
- /* Free cache if too big */
- while (open_cache.records > table_cache_size && unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
+ /* We have too many TABLE instances around let us try to get rid of them. */
+ while (table_cache_count > table_cache_size && unused_tables)
+ free_cache_entry(unused_tables);
- if (table_list->create)
- {
- bool exists;
+ /* make a new table */
+ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+ goto err_unlock;
- if (check_if_table_exists(thd, table_list, &exists))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
- }
+ error= open_table_from_share(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE |
+ HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ (READ_KEYINFO | COMPUTE_TYPES |
+ EXTRA_RECORD),
+ thd->open_options, table, FALSE);
+
+ if (error)
+ {
+ my_free(table, MYF(0));
- if (!exists)
+ if (action)
{
- /*
- Table to be created, so we need to create placeholder in table-cache.
- */
- if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
+ if (error == 7)
{
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ share->version= 0;
+ *action= OT_DISCOVER;
+ }
+ else if (share->crashed)
+ {
+ share->version= 0;
+ *action= OT_REPAIR;
}
- /*
- Link placeholder to the open tables list so it will be automatically
- removed once tables are closed. Also mark it so it won't be ignored
- by other trying to take name-lock.
- */
- table->open_placeholder= 1;
- table->next= thd->open_tables;
- thd->open_tables= table;
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(table);
}
- /* Table exists. Let us try to open it. */
- }
- /* make a new table */
- if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ goto err_unlock;
}
- error= open_unireg_entry(thd, table, table_list, alias, key, key_length,
- mem_root, (flags & OPEN_VIEW_NO_PARSE));
- if (error > 0)
+ if (open_table_entry_fini(thd, share, table))
{
+ closefrm(table, 0);
my_free((uchar*)table, MYF(0));
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ goto err_unlock;
}
- if (table_list->view || error < 0)
- {
- /*
- VIEW not really opened, only frm were read.
- Set 1 as a flag here
- */
- if (error < 0)
- table_list->view= (LEX*)1;
- my_free((uchar*)table, MYF(0));
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(0); // VIEW
- }
- DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
- table->s->db.str, table->s->table_name.str,
- (long) table));
- (void) my_hash_insert(&open_cache,(uchar*) table);
+ /* Add table to the share's used tables list. */
+ table_def_add_used_table(thd, table);
}
- check_unused(); // Debugging call
-
pthread_mutex_unlock(&LOCK_open);
- if (refresh)
+
+ // Table existed
+ if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE)
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
+
+ table->mdl_lock= mdl_lock;
+ if (action)
{
table->next=thd->open_tables; /* Link into simple list */
thd->open_tables=table;
@@ -3013,15 +3095,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table->clear_column_bitmaps();
DBUG_ASSERT(table->key_read == 0);
DBUG_RETURN(table);
+
+err_unlock:
+ release_table_share(share, RELEASE_NORMAL);
+err_unlock2:
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
+ DBUG_RETURN(0);
}
-TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
+/**
+ Find table in the list of open tables.
+
+ @param list List of TABLE objects to be inspected.
+ @param db Database name
+ @param table_name Table name
+
+ @return Pointer to the TABLE object found, 0 if no table found.
+*/
+
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
{
char key[MAX_DBKEY_LENGTH];
uint key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
- for (TABLE *table=thd->open_tables; table ; table=table->next)
+ for (TABLE *table= list; table ; table=table->next)
{
if (table->s->table_cache_key.length == key_length &&
!memcmp(table->s->table_cache_key.str, key, key_length))
@@ -3031,6 +3130,41 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
}
+/**
+ Find write locked instance of table in the list of open tables,
+ emit error if no such instance found.
+
+ @param thd List of TABLE objects to be searched
+ @param db Database name.
+ @param table_name Name of table.
+
+ @return Pointer to write-locked TABLE instance, 0 - otherwise.
+*/
+
+TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name)
+{
+ TABLE *tab= find_locked_table(list, db, table_name);
+
+ if (!tab)
+ {
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+ return 0;
+ }
+ else
+ {
+ while (tab->reginfo.lock_type < TL_WRITE_LOW_PRIORITY &&
+ (tab= find_locked_table(tab->next, db, table_name)))
+ continue;
+ if (!tab)
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+ return 0;
+ }
+ }
+ return tab;
+}
+
+
/*
Reopen an table because the definition has changed.
@@ -3073,14 +3207,10 @@ bool reopen_table(TABLE *table)
table_list.table_name= table->s->table_name.str;
table_list.table= table;
- if (wait_for_locked_table_names(thd, &table_list))
- DBUG_RETURN(1); // Thread was killed
-
- if (open_unireg_entry(thd, &tmp, &table_list,
- table->alias,
- table->s->table_cache_key.str,
- table->s->table_cache_key.length,
- thd->mem_root, 0))
+ if (reopen_table_entry(thd, &tmp, &table_list,
+ table->alias,
+ table->s->table_cache_key.str,
+ table->s->table_cache_key.length))
goto end;
/* This list copies variables set by open_table */
@@ -3112,6 +3242,12 @@ bool reopen_table(TABLE *table)
(void) closefrm(&tmp, 1); // close file, free everything
goto end;
}
+ tmp.mdl_lock= table->mdl_lock;
+
+ table_def_change_share(table, tmp.s);
+ /* Avoid wiping out TABLE's position in new share's used tables list. */
+ tmp.share_next= table->share_next;
+ tmp.share_prev= table->share_prev;
delete table->triggers;
if (table->file)
@@ -3214,6 +3350,7 @@ void close_data_files_and_morph_locks(THD *thd, const char *db,
mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
}
table->open_placeholder= 1;
+ table->s->version= 0;
close_handle_and_leave_table_as_lock(table);
}
}
@@ -3282,8 +3419,6 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p)
@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
@@ -3297,11 +3432,11 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p)
@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
- TABLE *err_tables= NULL;
+ TABLE *err_tables= NULL, *err_tab_tmp;
bool error=0, not_used;
bool merge_table_found= FALSE;
const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
@@ -3356,7 +3491,7 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
*/
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
- my_hash_delete(&open_cache,(uchar*) table);
+ free_cache_entry(table);
error=1;
}
else
@@ -3367,11 +3502,15 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
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));
}
}
}
@@ -3385,8 +3524,9 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
{
while (err_tables)
{
- my_hash_delete(&open_cache, (uchar*) err_tables);
- err_tables= err_tables->next;
+ err_tab_tmp= err_tables->next;
+ free_cache_entry(err_tables);
+ err_tables= err_tab_tmp;
}
}
DBUG_PRINT("tcache", ("open tables to lock: %u",
@@ -3534,41 +3674,24 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock)
{
char *key= table->s->table_cache_key.str;
uint key_length= table->s->table_cache_key.length;
-
- DBUG_PRINT("loop", ("table_name: %s", table->alias));
- HASH_SEARCH_STATE state;
- for (TABLE *search= (TABLE*) my_hash_first(&open_cache, (uchar*) key,
- key_length, &state);
- search ;
- search= (TABLE*) my_hash_next(&open_cache, (uchar*) key,
- key_length, &state))
- {
- DBUG_PRINT("info", ("share: 0x%lx "
- "open_placeholder: %d locked_by_name: %d "
- "db_stat: %u version: %lu",
- (ulong) search->s,
- search->open_placeholder, search->locked_by_name,
- search->db_stat,
- search->s->version));
- if (search->in_use == table->in_use)
- continue; // Name locked by this thread
- /*
- We can't use the table under any of the following conditions:
- - There is an name lock on it (Table is to be deleted or altered)
- - If we are in flush table and we didn't execute the flush
- - If the table engine is open and it's an old version
- (We must wait until all engines are shut down to use the table)
- */
- if ( (search->locked_by_name && wait_for_name_lock) ||
- (search->is_name_opened() && search->needs_reopen_or_name_lock()))
- DBUG_RETURN(1);
- }
+ /* Note that 'table' can use artificial TABLE_SHARE object. */
+ TABLE_SHARE *share= (TABLE_SHARE*)my_hash_search(&table_def_cache,
+ (uchar*) key, key_length);
+ if (share && !share->used_tables.is_empty() &&
+ share->version != refresh_version)
+ DBUG_RETURN(1);
} while ((table=table->next));
DBUG_RETURN(0);
}
-/* Wait until all used tables are refreshed */
+/*
+ Wait until all used tables are refreshed.
+
+ FIXME We should remove this function since for several functions which
+ are invoked by it new scenarios of usage are introduced, while
+ this function implements optimization useful only in rare cases.
+*/
bool wait_for_tables(THD *thd)
{
@@ -3593,7 +3716,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);
@@ -3601,111 +3724,27 @@ bool wait_for_tables(THD *thd)
}
-/*
- drop tables from locked list
-
- SYNOPSIS
- drop_locked_tables()
- thd Thread thandler
- db Database
- table_name Table name
-
- INFORMATION
- This is only called on drop tables
-
- The TABLE object for the dropped table is unlocked but still kept around
- as a name lock, which means that the table will be available for other
- thread as soon as we call unlock_table_names().
- If there is multiple copies of the table locked, all copies except
- the first, which acts as a name lock, is removed.
+/**
+ Unlock and close tables open and locked by LOCK TABLES statement.
- RETURN
- # If table existed, return table
- 0 Table was not locked
+ @param thd Current thread's context.
*/
-
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name)
+void unlock_locked_tables(THD *thd)
{
- TABLE *table,*next,**prev, *found= 0;
- 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);
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
- if (!found)
- {
- found= table;
- /* Close engine table, but keep object around as a name lock */
- if (table->db_stat)
- {
- table->db_stat= 0;
- table->file->close();
- }
- }
- else
- {
- /* We already have a name lock, remove copy */
- my_hash_delete(&open_cache,(uchar*) table);
- }
- }
- else
- {
- *prev=table;
- prev= &table->next;
- }
- }
- *prev=0;
- if (found)
- broadcast_refresh();
- if (thd->locked_tables && thd->locked_tables->table_count == 0)
+ if (thd->locked_tables)
{
- my_free((uchar*) thd->locked_tables,MYF(0));
+ thd->lock= thd->locked_tables;
thd->locked_tables=0;
- }
- DBUG_RETURN(found);
-}
-
-
-/*
- 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;
- }
+ close_thread_tables(thd);
+ /*
+ After closing tables we can free memory used for storing lock
+ request objects for metadata locks
+ */
+ free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE));
}
}
@@ -3812,7 +3851,7 @@ static bool inject_reprepare(THD *thd)
@retval FALSE success, version in TABLE_LIST has been updated
*/
-bool
+static bool
check_and_update_table_version(THD *thd,
TABLE_LIST *tables, TABLE_SHARE *table_share)
{
@@ -3838,41 +3877,98 @@ check_and_update_table_version(THD *thd,
return FALSE;
}
-/*
- Load a table definition from file and open unireg table
- SYNOPSIS
- open_unireg_entry()
- thd Thread handle
- entry Store open table definition here
- table_list TABLE_LIST with db, table_name & belong_to_view
- alias Alias name
- cache_key Key for share_cache
- cache_key_length length of cache_key
- mem_root temporary mem_root for parsing
- flags the OPEN_VIEW_NO_PARSE flag to be passed to
- openfrm()/open_new_frm()
+/**
+ Open view by getting its definition from disk (and table cache in future).
- NOTES
- Extra argument for open is taken from thd->open_options
- One must have a lock on LOCK_open when calling this function
+ @param thd Thread handle
+ @param table_list TABLE_LIST with db, table_name & belong_to_view
+ @param alias Alias name
+ @param cache_key Key for table definition cache
+ @param cache_key_length Length of cache_key
+ @param mem_root Memory to be used for .frm parsing.
+ @param flags Flags which modify how we open the view
- RETURN
- 0 ok
- # Error
+ @todo This function is needed for special handling of views under
+ LOCK TABLES. We probably should get rid of it in long term.
+
+ @return FALSE if success, TRUE - otherwise.
*/
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
- const char *alias,
- char *cache_key, uint cache_key_length,
- MEM_ROOT *mem_root, uint flags)
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags)
+{
+ TABLE not_used;
+ int error;
+ TABLE_SHARE *share;
+
+ pthread_mutex_lock(&LOCK_open);
+
+ if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &error)))
+ goto err;
+
+ if (share->is_view &&
+ !open_new_frm(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+ flags, thd->open_options, &not_used, table_list,
+ mem_root))
+ {
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ return FALSE;
+ }
+
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW");
+ release_table_share(share, RELEASE_NORMAL);
+err:
+ pthread_mutex_unlock(&LOCK_open);
+ return TRUE;
+}
+
+
+/**
+ Load table definition from file and open table while holding exclusive
+ meta-data lock on it.
+
+ @param thd Thread handle
+ @param entry Memory for TABLE object to be created
+ @param table_list TABLE_LIST with db, table_name & belong_to_view
+ @param alias Alias name
+ @param cache_key Key for table definition cache
+ @param cache_key_length Length of cache_key
+
+ @note This auxiliary function is mostly inteded for re-opening table
+ in situations when we hold exclusive meta-data lock. It is not
+ intended for normal case in which we have only shared meta-data
+ lock on the table to be open.
+
+ @note Extra argument for open is taken from thd->open_options.
+
+ @note One must have a lock on LOCK_open as well as exclusive meta-data
+ lock on the table when calling this function.
+
+ @return FALSE in case of success, TRUE otherwise.
+*/
+
+static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+ const char *alias, char *cache_key,
+ uint cache_key_length)
{
int error;
TABLE_SHARE *share;
uint discover_retry_count= 0;
- DBUG_ENTER("open_unireg_entry");
+ DBUG_ENTER("reopen_table_entry");
safe_mutex_assert_owner(&LOCK_open);
+ DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+ table_list->db,
+ table_list->table_name));
+
retry:
if (!(share= get_table_share_with_create(thd, table_list, cache_key,
cache_key_length,
@@ -3901,40 +3997,11 @@ retry:
goto err;
if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
goto err;
-
- /* Open view */
- error= (int) open_new_frm(thd, share, alias,
- (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX | HA_TRY_READ_ONLY),
- READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
- (flags & OPEN_VIEW_NO_PARSE),
- thd->open_options, entry, table_list,
- mem_root);
- if (error)
- goto err;
- /* TODO: Don't free this */
+ /* Attempt to reopen view will bring havoc to upper layers anyway. */
release_table_share(share, RELEASE_NORMAL);
- DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0);
- }
- else if (table_list->view)
- {
- /*
- We're trying to open a table for what was a view.
- This can only happen during (re-)execution.
- At prepared statement prepare the view has been opened and
- merged into the statement parse tree. After that, someone
- performed a DDL and replaced the view with a base table.
- Don't try to open the table inside a prepared statement,
- invalidate it instead.
-
- Note, the assert below is known to fail inside stored
- procedures (Bug#27011).
- */
- DBUG_ASSERT(thd->m_reprepare_observer);
- check_and_update_table_version(thd, table_list, share);
- /* Always an error. */
- DBUG_ASSERT(thd->is_error());
- goto err;
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
+ "BASE TABLE");
+ DBUG_RETURN(1);
}
if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
@@ -3956,89 +4023,67 @@ retry:
goto err;
/*
- TODO:
- Here we should wait until all threads has released the table.
- For now we do one retry. This may cause a deadlock if there
- is other threads waiting for other tables used by this thread.
-
- Proper fix would be to if the second retry failed:
- - Mark that table def changed
- - Return from open table
- - Close all tables used by this thread
- - Start waiting that the share is released
- - Retry by opening all tables again
+ Since we have exclusive metadata lock on the table here the only
+ practical case when share->ref_count != 1 is when we have several
+ instances of the table opened by this thread (i.e we are under LOCK
+ TABLES).
*/
+ if (share->ref_count != 1)
+ goto err;
+
+ release_table_share(share, RELEASE_NORMAL);
+
if (ha_create_table_from_engine(thd, table_list->db,
table_list->table_name))
goto err;
- /*
- TO BE FIXED
- To avoid deadlock, only wait for release if no one else is
- using the share.
- */
- if (share->ref_count != 1)
- goto err;
- /* Free share and wait until it's released by all threads */
- release_table_share(share, RELEASE_WAIT_FOR_DROP);
- if (!thd->killed)
- {
- thd->warning_info->clear_warning_info(thd->query_id);
- thd->clear_error(); // Clear error message
- goto retry;
- }
- DBUG_RETURN(1);
+
+ thd->warning_info->clear_warning_info(thd->query_id);
+ thd->clear_error(); // Clear error message
+ goto retry;
}
if (!entry->s || !entry->s->crashed)
goto err;
- // Code below is for repairing a crashed file
- if ((error= lock_table_name(thd, table_list, TRUE)))
- {
- if (error < 0)
- goto err;
- if (wait_for_locked_table_names(thd, table_list))
- {
- unlock_table_name(thd, table_list);
- goto err;
- }
- }
- pthread_mutex_unlock(&LOCK_open);
- thd->clear_error(); // Clear error message
- error= 0;
- if (open_table_from_share(thd, share, alias,
- (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX |
- HA_TRY_READ_ONLY),
- READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
- ha_open_options | HA_OPEN_FOR_REPAIR,
- entry, FALSE) || ! entry->file ||
- (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd)))
- {
- /* Give right error message */
- thd->clear_error();
- my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
- sql_print_error("Couldn't repair table: %s.%s", share->db.str,
- share->table_name.str);
- if (entry->file)
- closefrm(entry, 0);
- error=1;
- }
- else
- thd->clear_error(); // Clear error message
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
-
- if (error)
- goto err;
- break;
- }
- if (Table_triggers_list::check_n_load(thd, share->db.str,
- share->table_name.str, entry, 0))
+ entry->s->version= 0;
+
+ /* TODO: We don't need to release share here. */
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ error= (int)auto_repair_table(thd, table_list);
+ pthread_mutex_lock(&LOCK_open);
+
+ if (error)
+ goto err;
+
+ goto retry;
+ }
+
+ if (open_table_entry_fini(thd, share, entry))
{
closefrm(entry, 0);
goto err;
}
+ DBUG_RETURN(0);
+
+err:
+ release_table_share(share, RELEASE_NORMAL);
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Auxiliary routine which finalizes process of TABLE object creation
+ by loading triggers and handling implicitly emptied tables.
+*/
+
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
+{
+
+ if (Table_triggers_list::check_n_load(thd, share->db.str,
+ share->table_name.str, entry, 0))
+ return TRUE;
+
/*
If we are here, there was no fatal error (but error may be still
unitialized).
@@ -4070,18 +4115,141 @@ retry:
*/
sql_print_error("When opening HEAP table, could not allocate memory "
"to write 'DELETE FROM `%s`.`%s`' to the binary log",
- table_list->db, table_list->table_name);
+ share->db.str, share->table_name.str);
delete entry->triggers;
- closefrm(entry, 0);
- goto err;
+ return TRUE;
}
}
}
- DBUG_RETURN(0);
+ return FALSE;
+}
-err:
+
+/**
+ Auxiliary routine which is used for performing automatical table repair.
+*/
+
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
+{
+ char cache_key[MAX_DBKEY_LENGTH];
+ uint cache_key_length;
+ TABLE_SHARE *share;
+ TABLE *entry;
+ int not_used;
+ bool result= FALSE;
+
+ cache_key_length= create_table_def_key(thd, cache_key, table_list, 0);
+
+ thd->clear_error();
+
+ pthread_mutex_lock(&LOCK_open);
+
+ if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &not_used)))
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ return TRUE;
+ }
+
+ if (share->is_view)
+ goto end_with_lock_open;
+
+ if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
+ {
+ result= TRUE;
+ goto end_with_lock_open;
+ }
+ share->version= 0;
+ pthread_mutex_unlock(&LOCK_open);
+
+ if (open_table_from_share(thd, share, table_list->alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+ ha_open_options | HA_OPEN_FOR_REPAIR,
+ entry, FALSE) || ! entry->file ||
+ (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd)))
+ {
+ /* Give right error message */
+ thd->clear_error();
+ my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
+ sql_print_error("Couldn't repair table: %s.%s", share->db.str,
+ share->table_name.str);
+ if (entry->file)
+ closefrm(entry, 0);
+ result= TRUE;
+ }
+ else
+ {
+ thd->clear_error(); // Clear error message
+ closefrm(entry, 0);
+ }
+ my_free(entry, MYF(0));
+
+ pthread_mutex_lock(&LOCK_open);
+
+end_with_lock_open:
release_table_share(share, RELEASE_NORMAL);
- DBUG_RETURN(1);
+ pthread_mutex_unlock(&LOCK_open);
+ return result;
+}
+
+
+/**
+ Handle failed attempt ot open table by performing requested action.
+
+ @param thd Thread context
+ @param table Table list element for table that caused problem
+ @param action Type of action requested by failed open_table() call
+
+ @retval FALSE - Success. One should try to open tables once again.
+ @retval TRUE - Error
+*/
+
+static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
+ enum_open_table_action action)
+{
+ bool result= FALSE;
+
+ switch (action)
+ {
+ case OT_BACK_OFF_AND_RETRY:
+ result= (mdl_wait_for_locks(&thd->mdl_context) ||
+ tdc_wait_for_old_versions(thd, &thd->mdl_context));
+ mdl_remove_all_locks(&thd->mdl_context);
+ break;
+ case OT_DISCOVER:
+ mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+ 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);
+ pthread_mutex_unlock(&LOCK_open);
+
+ thd->warning_info->clear_warning_info(thd->query_id);
+ thd->clear_error(); // Clear error message
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ break;
+ case OT_REPAIR:
+ mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+ 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);
+
+ result= auto_repair_table(thd, table);
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return result;
}
@@ -4132,6 +4300,7 @@ static int add_merge_table_list(TABLE_LIST *tlist)
/* Set lock type. */
child_l->lock_type= tlist->lock_type;
+ child_l->mdl_upgradable= tlist->mdl_upgradable;
/* Set parent reference. */
child_l->parent_l= tlist;
@@ -4487,7 +4656,7 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table)
int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
{
TABLE_LIST *tables= NULL;
- bool refresh;
+ enum_open_table_action action;
int result=0;
MEM_ROOT new_frm_mem;
/* Also used for indicating that prelocking is need */
@@ -4508,6 +4677,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
thd_proc_info(thd, "Opening tables");
/*
+ Close HANDLER tables which are marked for flush or against which there
+ are pending exclusive metadata locks. Note that we do this not to avoid
+ deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and
+ tdc_wait_for_old_version() are enough for this) but in order to have
+ a point during statement execution at which such HANDLERs are closed
+ even if they don't create problems for current thread (i.e. to avoid
+ having DDL blocked by HANDLERs opened for long time).
+ */
+ if (thd->handler_tables)
+ mysql_ha_flush(thd);
+
+ /*
If we are not already executing prelocked statement and don't have
statement for which table list for prelocking is already built, let
us cache routines and try to build such table list.
@@ -4556,9 +4737,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
if (tables->derived)
{
- if (tables->view)
- goto process_view_routines;
- continue;
+ if (!tables->view)
+ continue;
+ /*
+ We restore view's name and database wiped out by derived tables
+ processing and fall back to standard open process in order to
+ obtain proper metadata locks and do other necessary steps like
+ stored routine processing.
+ */
+ tables->db= tables->view_db.str;
+ tables->db_length= tables->view_db.length;
+ tables->table_name= tables->view_name.str;
+ tables->table_name_length= tables->view_name.length;
}
/*
If this TABLE_LIST object is a placeholder for an information_schema
@@ -4602,12 +4792,12 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
Prelock_error_handler prelock_handler;
thd->push_internal_handler(& prelock_handler);
- tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
thd->pop_internal_handler();
safe_to_ignore_table= prelock_handler.safely_trapped_errors();
}
else
- tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
}
else
DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
@@ -4657,7 +4847,16 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
parent_l->next_global= *parent_l->table->child_last_l;
}
- if (refresh) // Refresh in progress
+ /*
+ FIXME This is a temporary hack. Actually we need check that will
+ allow us to differentiate between error while opening/creating
+ table and successful table creation.
+ ...
+ */
+ if (tables->open_table_type)
+ continue;
+
+ if (action)
{
/*
We have met name-locked or old version of table. Now we have
@@ -4675,7 +4874,17 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
if (query_tables_last_own)
thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
- close_tables_for_reopen(thd, start);
+ close_tables_for_reopen(thd, start, (action == OT_BACK_OFF_AND_RETRY));
+ /*
+ Here we rely on the fact that 'tables' still points to the valid
+ TABLE_LIST element. Altough currently this assumption is valid
+ it may change in future.
+ */
+ if (handle_failed_open_table_attempt(thd, tables, action))
+ {
+ result= -1;
+ goto err;
+ }
goto restart;
}
@@ -4929,6 +5138,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
uint lock_flags)
{
TABLE *table;
+ enum_open_table_action action;
bool refresh;
DBUG_ENTER("open_ltable");
@@ -4939,9 +5149,20 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
thd->current_tablenr= 0;
/* open_ltable can be used only for BASIC TABLEs */
table_list->required_type= FRMTYPE_TABLE;
- while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0)) &&
- refresh)
- ;
+
+retry:
+ while (!(table= open_table(thd, table_list, thd->mem_root, &action, 0)) &&
+ action)
+ {
+ /*
+ Even altough we have failed to open table we still need to
+ call close_thread_tables() to release metadata locks which
+ might have been acquired successfully.
+ */
+ close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY));
+ if (handle_failed_open_table_attempt(thd, table_list, action))
+ break;
+ }
if (table)
{
@@ -4969,8 +5190,22 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1,
- lock_flags, &refresh)))
- table= 0;
+ (lock_flags |
+ MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN),
+ &refresh)))
+ {
+ /*
+ FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option
+ as all reopening should happen outside of mysql_lock_tables() code.
+ */
+ if (refresh)
+ {
+ close_thread_tables(thd);
+ goto retry;
+ }
+ else
+ table= 0;
+ }
}
}
@@ -5026,7 +5261,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
break;
if (!need_reopen)
DBUG_RETURN(-1);
- close_tables_for_reopen(thd, &tables);
+ close_tables_for_reopen(thd, &tables, FALSE);
}
if (derived &&
(mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
@@ -5383,6 +5618,10 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
table->table->query_id= thd->query_id;
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
{
+ /*
+ This was an attempt to enter prelocked mode so there is no
+ need to care about THD::locked_tables_root here.
+ */
mysql_unlock_tables(thd, thd->locked_tables);
thd->locked_tables= 0;
thd->options&= ~(OPTION_TABLE_LOCK);
@@ -5469,7 +5708,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
*/
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl)
{
/*
If table list consists only from tables from prelocking set, table list
@@ -5481,7 +5720,7 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
sp_remove_not_own_routines(thd->lex);
for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
tmp->table= 0;
- close_thread_tables(thd);
+ close_thread_tables(thd, skip_mdl);
}
@@ -8393,36 +8632,6 @@ my_bool mysql_rm_tmp_tables(void)
*****************************************************************************/
/*
- Invalidate any cache entries that are for some DB
-
- SYNOPSIS
- remove_db_from_cache()
- db Database name. This will be in lower case if
- lower_case_table_name is set
-
- NOTE:
- We can't use hash_delete when looping hash_elements. We mark them first
- and afterwards delete those marked unused.
-*/
-
-void remove_db_from_cache(const char *db)
-{
- for (uint idx=0 ; idx < open_cache.records ; idx++)
- {
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- if (!strcmp(table->s->db.str, db))
- {
- table->s->version= 0L; /* Free when thread is ready */
- if (!table->in_use)
- relink_unused(table);
- }
- }
- while (unused_tables && !unused_tables->s->version)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
-}
-
-
-/*
free all unused tables
NOTE
@@ -8434,7 +8643,7 @@ void flush_tables()
{
(void) pthread_mutex_lock(&LOCK_open);
while (unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
+ free_cache_entry(unused_tables);
(void) pthread_mutex_unlock(&LOCK_open);
}
@@ -8468,90 +8677,82 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
for (;;)
{
- HASH_SEARCH_STATE state;
result= signalled= 0;
- for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length,
- &state);
- table;
- table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length,
- &state))
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key,
+ key_length)))
{
- THD *in_use;
- DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
-
- table->s->version=0L; /* Free when thread is ready */
- if (!(in_use=table->in_use))
- {
- DBUG_PRINT("info",("Table was not in use"));
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ share->version= 0;
+ while ((table= it++))
relink_unused(table);
- }
- else if (in_use != thd)
+
+ it.init(share->used_tables);
+ while ((table= it++))
{
- DBUG_PRINT("info", ("Table was in use by other thread"));
- /*
- Mark that table is going to be deleted from cache. This will
- force threads that are in mysql_lock_tables() (but not yet
- in thr_multi_lock()) to abort it's locks, close all tables and retry
- */
- in_use->some_tables_deleted= 1;
- if (table->is_name_opened())
- {
- DBUG_PRINT("info", ("Found another active instance of the table"));
- result=1;
- }
- /* Kill delayed insert threads */
- if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
- ! in_use->killed)
+ THD *in_use= table->in_use;
+ DBUG_ASSERT(in_use);
+ if (in_use != thd)
{
- 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);
- signalled= 1;
- 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);
+ DBUG_PRINT("info", ("Table was in use by other thread"));
+ /*
+ Mark that table is going to be deleted from cache. This will
+ force threads that are in mysql_lock_tables() (but not yet
+ in thr_multi_lock()) to abort it's locks, close all tables and retry
+ */
+ in_use->some_tables_deleted= 1;
+
+ if (table->is_name_opened())
+ {
+ DBUG_PRINT("info", ("Found another active instance of the table"));
+ result=1;
+ }
+ /* Kill delayed insert threads */
+ 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);
+ signalled= 1;
+ 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);
+ }
+ /*
+ Now we must abort all tables locks used by this thread
+ as the thread may be waiting to get a lock for another table.
+ Note that we need to hold LOCK_open while going through the
+ list. So that the other thread cannot change it. The other
+ thread must also hold LOCK_open whenever changing the
+ open_tables list. Aborting the MERGE lock after a child was
+ closed and before the parent is closed would be fatal.
+ */
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /* Do not handle locks of MERGE children. */
+ if (thd_table->db_stat && !thd_table->parent) // If table is open
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
}
- /*
- Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table.
- Note that we need to hold LOCK_open while going through the
- list. So that the other thread cannot change it. The other
- thread must also hold LOCK_open whenever changing the
- open_tables list. Aborting the MERGE lock after a child was
- closed and before the parent is closed would be fatal.
- */
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
+ else
{
- /* Do not handle locks of MERGE children. */
- if (thd_table->db_stat && !thd_table->parent) // If table is open
- signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
+ table->db_stat));
+ result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
}
}
- else
- {
- DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
- table->db_stat));
- result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
- }
- }
- while (unused_tables && !unused_tables->s->version)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
- DBUG_PRINT("info", ("Removing table from table_def_cache"));
- /* Remove table from table definition cache if it's not in use */
- if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
- key_length)))
- {
+ while (unused_tables && !unused_tables->s->version)
+ free_cache_entry(unused_tables);
+
DBUG_PRINT("info", ("share version: %lu ref_count: %u",
share->version, share->ref_count));
- share->version= 0; // Mark for delete
if (share->ref_count == 0)
{
pthread_mutex_lock(&share->mutex);
@@ -8598,6 +8799,160 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
}
+/**
+ A callback to the server internals that is used to address
+ special cases of the locking protocol.
+ Invoked when acquiring an exclusive lock, for each thread that
+ has a conflicting shared metadata lock.
+
+ This function:
+ - aborts waiting of the thread on a data lock, to make it notice
+ the pending exclusive lock and back off.
+ - if the thread is an INSERT DELAYED thread, sends it a KILL
+ signal to terminate it.
+
+ @note This function does not wait for the thread to give away its
+ locks. Waiting is done outside for all threads at once.
+
+ @param thd Current thread context
+ @param in_use The thread to wake up
+
+ @retval TRUE if the thread was woken up
+ @retval FALSE otherwise (e.g. it was not waiting for a table-level lock).
+
+ @note It is one of two places where border between MDL and the
+ rest of the server is broken.
+*/
+
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use)
+{
+ bool signalled= FALSE;
+ 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_cond_broadcast(in_use->mysys_var->current_cond);
+ pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ signalled= TRUE;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /* TODO With new MDL check for db_stat is probably a legacy */
+ if (thd_table->db_stat)
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
+ pthread_mutex_unlock(&LOCK_open);
+ return signalled;
+}
+
+
+/**
+ Remove all instances of the table from cache assuming that current thread
+ has exclusive meta-data lock on it (optionally leave instances belonging
+ to the current thread in cache).
+
+ @param leave_thd 0 If we should remove all instances
+ non-0 Pointer to current thread context if we should
+ leave instances belonging to this thread.
+ @param db Name of database
+ @param table_name Name of table
+
+ @note Unlike remove_table_from_cache() it assumes that table instances
+ are already not used by any (other) thread (this should be achieved
+ by using meta-data locks).
+*/
+
+void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ TABLE *table;
+ TABLE_SHARE *share;
+
+ key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
+
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
+ key_length)))
+ {
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ share->version= 0;
+
+ while ((table= it++))
+ relink_unused(table);
+ }
+
+ /* This may destroy share so we have to do new look-up later. */
+ while (unused_tables && !unused_tables->s->version)
+ free_cache_entry(unused_tables);
+
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
+ key_length)))
+ {
+ DBUG_ASSERT(leave_thd || share->ref_count == 0);
+ if (share->ref_count == 0)
+ {
+ pthread_mutex_lock(&share->mutex);
+ my_hash_delete(&table_def_cache, (uchar*) share);
+ }
+ }
+}
+
+
+/**
+ Wait until there are no old versions of tables in the table
+ definition cache for the metadata locks that we try to acquire.
+
+ @param thd Thread context
+ @param context Metadata locking context with locks.
+*/
+
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ TABLE_SHARE *share;
+ const char *old_msg;
+ LEX_STRING key;
+
+ while (!thd->killed)
+ {
+ /*
+ Here we have situation as in mdl_wait_for_locks() we need to
+ get rid of offending HANDLERs to avoid deadlock.
+ TODO: We should also investigate in which situations we have
+ to broadcast on COND_refresh because of this.
+ */
+ mysql_ha_flush(thd);
+ pthread_mutex_lock(&LOCK_open);
+
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it= mdl_get_locks(context);
+ while ((l= it++))
+ {
+ mdl_get_tdc_key(l, &key);
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key.str,
+ key.length)) &&
+ share->version != refresh_version &&
+ !share->used_tables.is_empty())
+ break;
+ }
+ if (!l)
+ {
+ 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);
+ /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */
+ thd->exit_cond(old_msg);
+ }
+ return thd->killed;
+}
+
+
int setup_ftfuncs(SELECT_LEX *select_lex)
{
List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)),
@@ -8695,7 +9050,6 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
}
err:
- bzero(outparam, sizeof(TABLE)); // do not run repair
DBUG_RETURN(1);
}
@@ -8728,15 +9082,23 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b)
int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt)
{
- uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG;
DBUG_ENTER("abort_and_upgrade_locks");
lpt->old_lock_type= lpt->table->reginfo.lock_type;
- pthread_mutex_lock(&LOCK_open);
/* If MERGE child, forward lock handling to parent. */
mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
lpt->table, TRUE);
- (void) remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags);
+ 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);
+ }
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
@@ -8771,106 +9133,12 @@ void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt)
/*
- SYNOPSIS
- mysql_wait_completed_table()
- lpt Parameter passing struct
- my_table My table object
- All parameters passed through the ALTER_PARTITION_PARAM object
- RETURN VALUES
- TRUE Failure
- FALSE Success
- DESCRIPTION
- We have changed the frm file and now we want to wait for all users of
- the old frm to complete before proceeding to ensure that no one
- remains that uses the old frm definition.
- Start by ensuring that all users of the table will be removed from cache
- once they are done. Then abort all that have stumbled on locks and
- haven't been started yet.
-
- thd Thread object
- table Table object
- db Database name
- table_name Table name
-*/
+ Tells if two (or more) tables have auto_increment columns and we want to
+ lock those tables with a write lock.
-void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
- TABLE *table;
- DBUG_ENTER("mysql_wait_completed_table");
-
- key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1;
- pthread_mutex_lock(&LOCK_open);
- HASH_SEARCH_STATE state;
- for (table= (TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length,
- &state) ;
- table;
- table= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length,
- &state))
- {
- THD *in_use= table->in_use;
- table->s->version= 0L;
- if (!in_use)
- {
- relink_unused(table);
- }
- else
- {
- /* Kill delayed insert threads */
- 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);
- }
- /*
- Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table.
- Note that we need to hold LOCK_open while going through the
- list. So that the other thread cannot change it. The other
- thread must also hold LOCK_open whenever changing the
- open_tables list. Aborting the MERGE lock after a child was
- closed and before the parent is closed would be fatal.
- */
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
- {
- /* Do not handle locks of MERGE children. */
- if (thd_table->db_stat && !thd_table->parent) // If table is open
- mysql_lock_abort_for_thread(lpt->thd, thd_table);
- }
- }
- }
- /*
- We start by removing all unused objects from the cache and marking
- those in use for removal after completion. Now we also need to abort
- all that are locked and are not progressing due to being locked
- by our lock. We don't upgrade our lock here.
- If MERGE child, forward lock handling to parent.
- */
- mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table,
- FALSE);
- pthread_mutex_unlock(&LOCK_open);
- DBUG_VOID_RETURN;
-}
-
-
-/*
- Check if one (or more) write tables have auto_increment columns.
-
- @param[in] tables Table list
-
- @retval 0 if at least one write tables has an auto_increment column
- @retval 1 otherwise
+ SYNOPSIS
+ has_two_write_locked_tables_with_auto_increment
+ tables Table list
NOTES:
Call this function only when you have established the list of all tables
@@ -8924,10 +9192,13 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
{
DBUG_ENTER("open_system_tables_for_read");
+ alloc_mdl_locks(table_list, thd->mem_root);
+
thd->reset_n_backup_open_tables_state(backup);
uint count= 0;
- bool not_used;
+ enum_open_table_action not_used;
+ bool not_used_2;
for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
{
TABLE *table= open_table(thd, tables, thd->mem_root, &not_used,
@@ -8950,7 +9221,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
*(ptr++)= tables->table;
thd->lock= mysql_lock_tables(thd, list, count,
- MYSQL_LOCK_IGNORE_FLUSH, &not_used);
+ MYSQL_LOCK_IGNORE_FLUSH, &not_used_2);
}
if (thd->lock)
DBUG_RETURN(FALSE);
@@ -9002,6 +9273,8 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table)
{
DBUG_ENTER("open_system_table_for_update");
+ alloc_mdl_locks(one_table, thd->mem_root);
+
TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0);
if (table)
{
@@ -9038,6 +9311,7 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
thd->reset_n_backup_open_tables_state(backup);
+ alloc_mdl_locks(one_table, thd->mem_root);
if ((table= open_ltable(thd, one_table, one_table->lock_type, flags)))
{
DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE);
@@ -9116,6 +9390,9 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup)
pthread_mutex_unlock(&LOCK_open);
+ mdl_release_locks(&thd->mdl_context);
+ mdl_remove_all_locks(&thd->mdl_context);
+
thd->restore_backup_open_tables_state(backup);
}
diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc
index 58c309ef57b..31d4430cbe6 100644
--- a/sql/sql_binlog.cc
+++ b/sql/sql_binlog.cc
@@ -241,7 +241,7 @@ void mysql_client_binlog_statement(THD* thd)
my_ok(thd);
end:
- rli->clear_tables_to_lock();
+ rli->slave_close_thread_tables(thd);
my_free(buf, MYF(MY_ALLOW_ZERO_PTR));
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index b7d88eca89a..9445f092546 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -202,10 +202,10 @@ bool foreign_key_prefix(Key *a, Key *b)
** Thread specific functions
****************************************************************************/
-Open_tables_state::Open_tables_state(ulong version_arg)
+Open_tables_state::Open_tables_state(THD *thd, ulong version_arg)
:version(version_arg), state_flags(0U)
{
- reset_open_tables_state();
+ reset_open_tables_state(thd);
}
/*
@@ -440,7 +440,7 @@ bool Drop_table_error_handler::handle_condition(THD *thd,
THD::THD()
:Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION,
/* statement id */ 0),
- Open_tables_state(refresh_version), rli_fake(0),
+ Open_tables_state(this, refresh_version), rli_fake(0),
lock_id(&main_lock_id),
user_time(0), in_sub_stmt(0),
sql_log_bin_toplevel(false),
@@ -468,7 +468,8 @@ THD::THD()
#if defined(ENABLED_DEBUG_SYNC)
debug_sync_control(0),
#endif /* defined(ENABLED_DEBUG_SYNC) */
- main_warning_info(0)
+ main_warning_info(0),
+ mdl_el_root(NULL)
{
ulong tmp;
@@ -573,6 +574,8 @@ THD::THD()
thr_lock_owner_init(&main_lock_id, &lock_info);
m_internal_handler= NULL;
+
+ init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
}
@@ -1051,6 +1054,9 @@ THD::~THD()
if (!cleanup_done)
cleanup();
+ mdl_context_destroy(&mdl_context);
+ mdl_context_destroy(&handler_mdl_context);
+
ha_close_connection(this);
plugin_thdvar_cleanup(this);
@@ -1072,6 +1078,7 @@ THD::~THD()
#endif
free_root(&main_mem_root, MYF(0));
+ free_root(&locked_tables_root, MYF(0));
DBUG_VOID_RETURN;
}
@@ -3014,7 +3021,7 @@ void THD::reset_n_backup_open_tables_state(Open_tables_state *backup)
{
DBUG_ENTER("reset_n_backup_open_tables_state");
backup->set_open_tables_state(this);
- reset_open_tables_state();
+ reset_open_tables_state(this);
state_flags|= Open_tables_state::BACKUPS_AVAIL;
DBUG_VOID_RETURN;
}
@@ -3032,6 +3039,9 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup)
lock == 0 && locked_tables == 0 &&
prelocked_mode == NON_PRELOCKED &&
m_reprepare_observer == NULL);
+ mdl_context_destroy(&mdl_context);
+ mdl_context_destroy(&handler_mdl_context);
+
set_open_tables_state(backup);
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 03a92c1a685..0f7d9d9a8d5 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -25,6 +25,7 @@
#include "log.h"
#include "rpl_tblmap.h"
+#include "mdl.h"
class Reprepare_observer;
@@ -976,26 +977,31 @@ public:
*/
uint state_flags;
+ MDL_CONTEXT mdl_context;
+ MDL_CONTEXT handler_mdl_context;
+
/*
This constructor serves for creation of Open_tables_state instances
which are used as backup storage.
*/
Open_tables_state() : state_flags(0U) { }
- Open_tables_state(ulong version_arg);
+ Open_tables_state(THD *thd, ulong version_arg);
void set_open_tables_state(Open_tables_state *state)
{
*this= *state;
}
- void reset_open_tables_state()
+ void reset_open_tables_state(THD *thd)
{
open_tables= temporary_tables= handler_tables= derived_tables= 0;
extra_lock= lock= locked_tables= 0;
prelocked_mode= NON_PRELOCKED;
state_flags= 0U;
m_reprepare_observer= NULL;
+ mdl_context_init(&mdl_context, thd);
+ mdl_context_init(&handler_mdl_context, thd);
}
};
@@ -1809,6 +1815,9 @@ public:
struct st_debug_sync_control *debug_sync_control;
#endif /* defined(ENABLED_DEBUG_SYNC) */
+ MEM_ROOT *mdl_el_root;
+ MEM_ROOT locked_tables_root;
+
THD();
~THD();
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
index 17626f05aa1..44909880da0 100644
--- a/sql/sql_db.cc
+++ b/sql/sql_db.cc
@@ -904,10 +904,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
}
else
{
- pthread_mutex_lock(&LOCK_open);
- remove_db_from_cache(db);
- pthread_mutex_unlock(&LOCK_open);
-
Drop_table_error_handler err_handler(thd->get_internal_handler());
thd->push_internal_handler(&err_handler);
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index d8aa27c9695..fb48f32660b 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -1089,12 +1089,13 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
TABLE *table;
bool error;
uint path_length;
+ MDL_LOCK *mdl_lock= 0;
DBUG_ENTER("mysql_truncate");
bzero((char*) &create_info,sizeof(create_info));
/* Remove tables from the HANDLER's hash. */
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
/* If it is a temporary table, close and regenerate it */
if (!dont_send_ok && (table= find_temporary_table(thd, table_list)))
@@ -1158,8 +1159,20 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION)
goto trunc_by_del;
- if (lock_and_wait_for_table_name(thd, table_list))
+ /*
+ FIXME: Actually code of TRUNCATE breaks meta-data locking protocol since
+ tries to get table enging and therefore accesses table in some way
+ without holding any kind of meta-data lock.
+ */
+ mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
DBUG_RETURN(TRUE);
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table_list->db, table_list->table_name);
+ pthread_mutex_unlock(&LOCK_open);
}
// Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this
@@ -1184,15 +1197,13 @@ end:
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
my_ok(thd); // This should return record count
}
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
}
else if (error)
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
}
DBUG_RETURN(error);
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index da5ee93fcb9..c8a66073a67 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -116,17 +116,16 @@ static void mysql_ha_hash_free(TABLE_LIST *tables)
@param thd Thread identifier.
@param tables A list of tables with the first entry to close.
- @param is_locked If LOCK_open is locked.
@note Though this function takes a list of tables, only the first list entry
will be closed.
@note Broadcasts refresh if it closed a table with old version.
*/
-static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
- bool is_locked)
+static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
{
TABLE **table_ptr;
+ MDL_LOCK *mdl_lock;
/*
Though we could take the table pointer from hash_tables->table,
@@ -142,15 +141,15 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
if (*table_ptr)
{
(*table_ptr)->file->ha_index_or_rnd_end();
- if (! is_locked)
- pthread_mutex_lock(&LOCK_open);
+ mdl_lock= (*table_ptr)->mdl_lock;
+ pthread_mutex_lock(&LOCK_open);
if (close_thread_table(thd, table_ptr))
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
- if (! is_locked)
- pthread_mutex_unlock(&LOCK_open);
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->handler_mdl_context, mdl_lock);
}
else if (tables->table)
{
@@ -190,10 +189,12 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
{
TABLE_LIST *hash_tables = NULL;
- char *db, *name, *alias;
+ MDL_LOCK *mdl_lock;
+ char *db, *name, *alias, *mdlkey;
uint dblen, namelen, aliaslen, counter;
int error;
TABLE *backup_open_tables;
+ MDL_CONTEXT backup_mdl_context;
DBUG_ENTER("mysql_ha_open");
DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
tables->db, tables->table_name, tables->alias,
@@ -216,7 +217,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
HANDLER_TABLES_HASH_SIZE, 0, 0,
(my_hash_get_key) mysql_ha_hash_get_key,
(my_hash_free_key) mysql_ha_hash_free, 0))
- goto err;
+ {
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
}
else if (! reopen) /* Otherwise we have 'tables' already. */
{
@@ -224,10 +228,51 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
strlen(tables->alias) + 1))
{
DBUG_PRINT("info",("duplicate '%s'", tables->alias));
+ DBUG_PRINT("exit",("ERROR"));
my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
- goto err;
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (! reopen)
+ {
+ /* copy the TABLE_LIST struct */
+ dblen= strlen(tables->db) + 1;
+ namelen= strlen(tables->table_name) + 1;
+ aliaslen= strlen(tables->alias) + 1;
+ if (!(my_multi_malloc(MYF(MY_WME),
+ &hash_tables, (uint) sizeof(*hash_tables),
+ &db, (uint) dblen,
+ &name, (uint) namelen,
+ &alias, (uint) aliaslen,
+ &mdl_lock, sizeof(MDL_LOCK),
+ &mdlkey, MAX_DBKEY_LENGTH,
+ NullS)))
+ {
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
+ /* structure copy */
+ *hash_tables= *tables;
+ hash_tables->db= db;
+ hash_tables->table_name= name;
+ hash_tables->alias= alias;
+ memcpy(hash_tables->db, tables->db, dblen);
+ memcpy(hash_tables->table_name, tables->table_name, namelen);
+ memcpy(hash_tables->alias, tables->alias, aliaslen);
+ mdl_init_lock(mdl_lock, mdlkey, 0, db, name);
+ hash_tables->mdl_lock= mdl_lock;
+
+ /* add to hash */
+ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
+ {
+ my_free((char*) hash_tables, MYF(0));
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
}
}
+ else
+ hash_tables= tables;
/*
Save and reset the open_tables list so that open_tables() won't
@@ -243,21 +288,22 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
*/
backup_open_tables= thd->open_tables;
thd->open_tables= NULL;
+ mdl_context_backup_and_reset(&thd->mdl_context, &backup_mdl_context);
/*
- open_tables() will set 'tables->table' if successful.
+ open_tables() will set 'hash_tables->table' if successful.
It must be NULL for a real open when calling open_tables().
*/
- DBUG_ASSERT(! tables->table);
+ DBUG_ASSERT(! hash_tables->table);
/* for now HANDLER can be used only for real TABLES */
- tables->required_type= FRMTYPE_TABLE;
+ hash_tables->required_type= FRMTYPE_TABLE;
/*
We use open_tables() here, rather than, say,
open_ltable() or open_table() because we would like to be able
to open a temporary table.
*/
- error= open_tables(thd, &tables, &counter, 0);
+ error= open_tables(thd, &hash_tables, &counter, 0);
if (thd->open_tables)
{
if (thd->open_tables->next)
@@ -281,52 +327,26 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
thd->handler_tables= thd->open_tables;
}
}
+ mdl_context_merge(&thd->handler_mdl_context, &thd->mdl_context);
- /* Restore the state. */
thd->open_tables= backup_open_tables;
+ mdl_context_restore(&thd->mdl_context, &backup_mdl_context);
if (error)
goto err;
/* There can be only one table in '*tables'. */
- if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
+ if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
{
my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
goto err;
}
- if (! reopen)
- {
- /* copy the TABLE_LIST struct */
- dblen= strlen(tables->db) + 1;
- namelen= strlen(tables->table_name) + 1;
- aliaslen= strlen(tables->alias) + 1;
- if (!(my_multi_malloc(MYF(MY_WME),
- &hash_tables, (uint) sizeof(*hash_tables),
- &db, (uint) dblen,
- &name, (uint) namelen,
- &alias, (uint) aliaslen,
- NullS)))
- goto err;
- /* structure copy */
- *hash_tables= *tables;
- hash_tables->db= db;
- hash_tables->table_name= name;
- hash_tables->alias= alias;
- memcpy(hash_tables->db, tables->db, dblen);
- memcpy(hash_tables->table_name, tables->table_name, namelen);
- memcpy(hash_tables->alias, tables->alias, aliaslen);
-
- /* add to hash */
- if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
- goto err;
- }
-
/*
If it's a temp table, don't reset table->query_id as the table is
being used by this handler. Otherwise, no meaning at all.
*/
- tables->table->open_by_handler= 1;
+ hash_tables->table->open_by_handler= 1;
if (! reopen)
my_ok(thd);
@@ -334,10 +354,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
DBUG_RETURN(FALSE);
err:
- if (hash_tables)
- my_free((char*) hash_tables, MYF(0));
- if (tables->table)
- mysql_ha_close_table(thd, tables, FALSE);
+ if (hash_tables->table)
+ mysql_ha_close_table(thd, hash_tables);
+ if (!reopen)
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
DBUG_PRINT("exit",("ERROR"));
DBUG_RETURN(TRUE);
}
@@ -371,7 +391,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
(uchar*) tables->alias,
strlen(tables->alias) + 1)))
{
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
}
else
@@ -507,7 +527,7 @@ retry:
if (need_reopen)
{
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
/*
The lock might have been aborted, we need to manually reset
thd->some_tables_deleted because handler's tables are closed
@@ -734,12 +754,11 @@ static TABLE_LIST *mysql_ha_find(THD *thd, TABLE_LIST *tables)
@param thd Thread identifier.
@param tables The list of tables to remove.
- @param is_locked If LOCK_open is locked.
@note Broadcasts refresh if it closed a table with old version.
*/
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked)
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables)
{
TABLE_LIST *hash_tables, *next;
DBUG_ENTER("mysql_ha_rm_tables");
@@ -752,7 +771,7 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked)
{
next= hash_tables->next_local;
if (hash_tables->table)
- mysql_ha_close_table(thd, hash_tables, is_locked);
+ mysql_ha_close_table(thd, hash_tables);
my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
hash_tables= next;
}
@@ -775,13 +794,16 @@ void mysql_ha_flush(THD *thd)
TABLE_LIST *hash_tables;
DBUG_ENTER("mysql_ha_flush");
- safe_mutex_assert_owner(&LOCK_open);
+ safe_mutex_assert_not_owner(&LOCK_open);
for (uint i= 0; i < thd->handler_tables_hash.records; i++)
{
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
- if (hash_tables->table && hash_tables->table->needs_reopen_or_name_lock())
- mysql_ha_close_table(thd, hash_tables, TRUE);
+ if (hash_tables->table &&
+ (hash_tables->table->mdl_lock &&
+ mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) ||
+ hash_tables->table->needs_reopen_or_name_lock()))
+ mysql_ha_close_table(thd, hash_tables);
}
DBUG_VOID_RETURN;
@@ -805,7 +827,7 @@ void mysql_ha_cleanup(THD *thd)
{
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
if (hash_tables->table)
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
}
my_hash_free(&thd->handler_tables_hash);
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index efdc8caa3e5..eb4eee9abb5 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -612,7 +612,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
upgrade the lock here instead?
*/
if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables &&
- find_locked_table(thd, table_list->db, table_list->table_name))
+ find_locked_table(thd->open_tables, table_list->db,
+ table_list->table_name))
{
my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
table_list->table_name);
@@ -2351,6 +2352,8 @@ pthread_handler_t handle_delayed_insert(void *arg)
thd->lex->set_stmt_unsafe();
thd->set_current_stmt_binlog_row_based_if_mixed();
+ alloc_mdl_locks(&di->table_list, thd->mem_root);
+
/* Open table */
if (!(di->table= open_n_lock_single_table(thd, &di->table_list,
TL_WRITE_DELAYED)))
@@ -3495,7 +3498,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, create_table, FALSE))
+ if (reopen_name_locked_table(thd, create_table))
{
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name),
@@ -3507,7 +3510,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
}
else
{
- if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+ if (!(table= open_table(thd, create_table, thd->mem_root,
+ (enum_open_table_action*) 0,
MYSQL_OPEN_TEMPORARY_ONLY)) &&
!create_info->table_existed)
{
@@ -3621,8 +3625,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000););
- if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
- (create_table->table && create_table->table->db_stat))
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && create_table->table)
{
/* Table already exists and was open at open_and_lock_tables() stage. */
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 1271c3112ff..2b2c736fd9e 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -47,6 +47,7 @@
"FUNCTION" : "PROCEDURE")
static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables);
const char *any_db="*any*"; // Special symbol for check_access
@@ -143,17 +144,6 @@ static bool xa_trans_rollback(THD *thd)
return status;
}
-static void unlock_locked_tables(THD *thd)
-{
- if (thd->locked_tables)
- {
- thd->lock=thd->locked_tables;
- thd->locked_tables=0; // Will be automatically closed
- close_thread_tables(thd); // Free tables
- }
-}
-
-
bool end_active_trans(THD *thd)
{
int error=0;
@@ -194,12 +184,9 @@ bool begin_trans(THD *thd)
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
return 1;
}
- if (thd->locked_tables)
- {
- thd->lock=thd->locked_tables;
- thd->locked_tables=0; // Will be automatically closed
- close_thread_tables(thd); // Free tables
- }
+
+ unlock_locked_tables(thd);
+
if (end_active_trans(thd))
error= -1;
else
@@ -1342,6 +1329,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
select_lex.table_list.link_in_list((uchar*) &table_list,
(uchar**) &table_list.next_local);
thd->lex->add_to_query_tables(&table_list);
+ alloc_mdl_locks(&table_list, thd->mem_root);
/* switch on VIEW optimisation: do not fill temporary tables */
thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
@@ -2643,7 +2631,7 @@ case SQLCOM_PREPARE:
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
- create_table->create= TRUE;
+ create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
}
if (!(res= open_and_lock_tables(thd, lex->query_tables)))
@@ -3618,6 +3606,9 @@ end_with_restore_list:
goto error;
thd->in_lock_tables=1;
thd->options|= OPTION_TABLE_LOCK;
+ alloc_mdl_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)))
{
@@ -3641,6 +3632,7 @@ end_with_restore_list:
thd->options&= ~(OPTION_TABLE_LOCK);
}
thd->in_lock_tables=0;
+ thd->mdl_el_root= 0;
break;
case SQLCOM_CREATE_DB:
{
@@ -6542,6 +6534,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->next_name_resolution_table= NULL;
/* Link table in global list (all used tables) */
lex->add_to_query_tables(ptr);
+ ptr->mdl_lock= mdl_alloc_lock(0 , ptr->db, ptr->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
DBUG_RETURN(ptr);
}
@@ -7079,23 +7074,15 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
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
@@ -7105,7 +7092,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
if (lock_global_read_lock(thd))
return 1; // Killed
if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, TRUE))
+ FALSE : TRUE))
result= 1;
if (make_global_read_lock_block_commit(thd)) // Killed
@@ -7117,8 +7104,35 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
}
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->open_tables, 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;
+ }
+ }
+ }
+ }
+
if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, FALSE))
+ FALSE : TRUE))
result= 1;
}
my_dbopt_cleanup();
@@ -8092,6 +8106,42 @@ bool parse_sql(THD *thd,
return ret_value;
}
+
+/**
+ 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
+ {
+ /*
+ TODO: To get rid of this loop we need to change our code to do
+ metadata lock upgrade only for those instances of tables
+ which are write locked instead of doing such upgrade for
+ all instances of tables.
+ */
+ 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;
+ }
+ }
+ }
+}
+
/**
@} (end of group Runtime_Environment)
*/
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
index 868dfc3e968..48f33bf3295 100644
--- a/sql/sql_partition.cc
+++ b/sql/sql_partition.cc
@@ -6225,7 +6225,7 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt)
*/
pthread_mutex_lock(&LOCK_open);
lpt->thd->in_lock_tables= 1;
- err= reopen_tables(lpt->thd, 1, 1);
+ err= reopen_tables(lpt->thd, 1);
lpt->thd->in_lock_tables= 0;
if (err)
{
@@ -6564,7 +6564,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
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_4") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_5") ||
@@ -6631,7 +6631,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
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") ||
@@ -6647,7 +6647,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
ERROR_INJECT_CRASH("crash_add_partition_8") ||
(write_log_completed(lpt, FALSE), FALSE) ||
ERROR_INJECT_CRASH("crash_add_partition_9") ||
- (alter_partition_lock_handling(lpt), FALSE))
+ (alter_partition_lock_handling(lpt), FALSE))
{
handle_alter_part_error(lpt, not_completed, FALSE, frm_install);
goto err;
@@ -6721,7 +6721,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
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_5") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_6") ||
diff --git a/sql/sql_plist.h b/sql/sql_plist.h
new file mode 100644
index 00000000000..af2ed227ea1
--- /dev/null
+++ b/sql/sql_plist.h
@@ -0,0 +1,125 @@
+#ifndef SQL_PLIST_H
+#define SQL_PLIST_H
+/* Copyright (C) 2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include <my_global.h>
+
+template <typename T, typename B> class I_P_List_iterator;
+
+
+/**
+ Intrusive parameterized list.
+
+ Unlike I_List does not require its elements to be descendant of ilink
+ class and therefore allows them to participate in several such lists
+ simultaneously.
+
+ Unlike List is doubly-linked list and thus supports efficient deletion
+ of element without iterator.
+
+ @param T Type of elements which will belong to list.
+ @param B Class which via its methods specifies which members
+ of T should be used for participating in this list.
+ Here is typical layout of such class:
+
+ struct B
+ {
+ static inline T **next_ptr(T *el)
+ {
+ return &el->next;
+ }
+ static inline T ***prev_ptr(T *el)
+ {
+ return &el->prev;
+ }
+ };
+*/
+
+template <typename T, typename B>
+class I_P_List
+{
+ T *first;
+
+ /*
+ Do not prohibit copying of I_P_List object to simplify their usage in
+ backup/restore scenarios. Note that performing any operations on such
+ is a bad idea.
+ */
+public:
+ I_P_List() : first(NULL) { };
+ inline void empty() { first= NULL; }
+ inline bool is_empty() { return (first == NULL); }
+ inline void push_front(T* a)
+ {
+ *B::next_ptr(a)= first;
+ if (first)
+ *B::prev_ptr(first)= B::next_ptr(a);
+ first= a;
+ *B::prev_ptr(a)= &first;
+ }
+ inline void remove(T *a)
+ {
+ T *next= *B::next_ptr(a);
+ if (next)
+ *B::prev_ptr(next)= *B::prev_ptr(a);
+ **B::prev_ptr(a)= next;
+ }
+ inline T* head() { return first; }
+ void swap(I_P_List<T,B> &rhs)
+ {
+ swap_variables(T *, first, rhs.first);
+ if (first)
+ *B::prev_ptr(first)= &first;
+ if (rhs.first)
+ *B::prev_ptr(rhs.first)= &rhs.first;
+ }
+#ifndef _lint
+ friend class I_P_List_iterator<T, B>;
+#endif
+};
+
+
+/**
+ Iterator for I_P_List.
+*/
+
+template <typename T, typename B>
+class I_P_List_iterator
+{
+ I_P_List<T, B> *list;
+ T *current;
+public:
+ I_P_List_iterator(I_P_List<T, B> &a) : list(&a), current(a.first) {}
+ inline void init(I_P_List<T, B> &a)
+ {
+ list= &a;
+ current= a.first;
+ }
+ inline T* operator++(int)
+ {
+ T *result= current;
+ if (result)
+ current= *B::next_ptr(current);
+ return result;
+ }
+ inline void rewind()
+ {
+ current= list->first;
+ }
+};
+
+#endif
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc
index 936c9ae8866..e9f4152f861 100644
--- a/sql/sql_plugin.cc
+++ b/sql/sql_plugin.cc
@@ -1366,6 +1366,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv)
tables.alias= tables.table_name= (char*)"plugin";
tables.lock_type= TL_READ;
tables.db= new_thd->db;
+ alloc_mdl_locks(&tables, tmp_root);
#ifdef EMBEDDED_LIBRARY
/*
@@ -1659,6 +1660,8 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl
if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE))
DBUG_RETURN(TRUE);
+ alloc_mdl_locks(&tables, thd->mem_root);
+
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (! (table = open_ltable(thd, &tables, TL_WRITE, 0)))
DBUG_RETURN(TRUE);
@@ -1732,6 +1735,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name)
bzero(&tables, sizeof(tables));
tables.db= (char *)"mysql";
tables.table_name= tables.alias= (char *)"plugin";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index d624c22f43a..582e18a3abf 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
- create_table->create= TRUE;
+ create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
}
if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc
index dac96f2e9c4..857cccde50f 100644
--- a/sql/sql_rename.cc
+++ b/sql/sql_rename.cc
@@ -51,7 +51,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
DBUG_RETURN(1);
}
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
if (wait_if_global_read_lock(thd,0,1))
DBUG_RETURN(1);
@@ -133,12 +133,13 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
}
}
- pthread_mutex_lock(&LOCK_open);
- if (lock_table_names_exclusively(thd, table_list))
- {
- pthread_mutex_unlock(&LOCK_open);
+ if (lock_table_names(thd, table_list))
goto err;
- }
+
+ pthread_mutex_lock(&LOCK_open);
+
+ for (ren_table= table_list; ren_table; ren_table= ren_table->next_local)
+ expel_table_from_cache(0, ren_table->db, ren_table->table_name);
error=0;
if ((ren_table=rename_tables(thd,table_list,0)))
@@ -184,9 +185,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
if (!error)
query_cache_invalidate3(thd, table_list, 0);
- pthread_mutex_lock(&LOCK_open);
- unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
- pthread_mutex_unlock(&LOCK_open);
+ unlock_table_names(thd);
err:
start_waiting_global_read_lock(thd);
diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc
index e5fe06ce39b..5251a50cab9 100644
--- a/sql/sql_servers.cc
+++ b/sql/sql_servers.cc
@@ -224,12 +224,7 @@ bool servers_reload(THD *thd)
bool return_val= TRUE;
DBUG_ENTER("servers_reload");
- if (thd->locked_tables)
- { // Can't have locked tables here
- thd->lock=thd->locked_tables;
- thd->locked_tables=0;
- close_thread_tables(thd);
- }
+ unlock_locked_tables(thd); // Can't have locked tables here
DBUG_PRINT("info", ("locking servers_cache"));
rw_wrlock(&THR_LOCK_servers);
@@ -238,6 +233,7 @@ bool servers_reload(THD *thd)
tables[0].alias= tables[0].table_name= (char*) "servers";
tables[0].db= (char*) "mysql";
tables[0].lock_type= TL_READ;
+ alloc_mdl_locks(tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, tables))
{
@@ -368,6 +364,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* need to open before acquiring THR_LOCK_plugin or it will deadlock */
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
@@ -586,6 +583,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
rw_wrlock(&THR_LOCK_servers);
@@ -710,6 +708,7 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*)"mysql";
tables.alias= tables.table_name= (char*)"servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
if (!(table= open_ltable(thd, &tables, TL_WRITE, 0)))
{
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index babadc34842..c83a6981166 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -2963,7 +2963,7 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables,
table, res, db_name,
table_name));
thd->temporary_tables= 0;
- close_tables_for_reopen(thd, &show_table_list);
+ close_tables_for_reopen(thd, &show_table_list, FALSE);
DBUG_RETURN(error);
}
@@ -3106,6 +3106,9 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
char key[MAX_DBKEY_LENGTH];
uint key_length;
char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1];
+ MDL_LOCK mdl_lock;
+ char mdlkey[MAX_DBKEY_LENGTH];
+ bool retry;
bzero((char*) &table_list, sizeof(TABLE_LIST));
bzero((char*) &tbl, sizeof(TABLE));
@@ -3130,6 +3133,34 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
table_list.db= db_name->str;
}
+ mdl_init_lock(&mdl_lock, mdlkey, 0, db_name->str, table_name->str);
+ table_list.mdl_lock= &mdl_lock;
+ mdl_add_lock(&thd->mdl_context, &mdl_lock);
+ mdl_set_lock_priority(&mdl_lock, MDL_HIGH_PRIO);
+
+ /*
+ TODO: investigate if in this particular situation we can get by
+ simply obtaining internal lock of data-dictionary (ATM it
+ is LOCK_open) instead of obtaning full-blown metadata lock.
+ */
+ while (1)
+ {
+ if (mdl_acquire_shared_lock(&mdl_lock, &retry))
+ {
+ 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;
+ }
+
key_length= create_table_def_key(thd, key, &table_list, 0);
pthread_mutex_lock(&LOCK_open);
share= get_table_share(thd, &table_list, key,
@@ -3137,7 +3168,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
if (!share)
{
res= 0;
- goto err;
+ goto err_unlock;
}
if (share->is_view)
@@ -3146,7 +3177,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
{
/* skip view processing */
res= 0;
- goto err1;
+ goto err_share;
}
else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL)
{
@@ -3155,7 +3186,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
open_normal_and_derived_tables()
*/
res= 1;
- goto err1;
+ goto err_share;
}
}
@@ -3171,14 +3202,17 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
res= schema_table->process_table(thd, &table_list, table,
res, db_name, table_name);
closefrm(&tbl, true);
- goto err;
+ goto err_unlock;
}
-err1:
+err_share:
release_table_share(share, RELEASE_NORMAL);
-err:
+err_unlock:
pthread_mutex_unlock(&LOCK_open);
+
+err:
+ mdl_release_lock(&thd->mdl_context, &mdl_lock);
thd->clear_error();
return res;
}
@@ -3425,7 +3459,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
res= schema_table->process_table(thd, show_table_list, table,
res, &orig_db_name,
&tmp_lex_string);
- close_tables_for_reopen(thd, &show_table_list);
+ close_tables_for_reopen(thd, &show_table_list, FALSE);
}
DBUG_ASSERT(!lex->query_tables_own_last);
if (res)
@@ -7273,6 +7307,8 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name)
uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
+ alloc_mdl_locks(lst, thd->mem_root);
+
if (open_tables(thd, &lst, &num_tables, 0))
{
my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0),
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index dc0c876e882..f91bae8b76c 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -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
@@ -1791,11 +1792,6 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
DBUG_RETURN(TRUE);
}
- /*
- Acquire LOCK_open after wait_if_global_read_lock(). If we would hold
- LOCK_open during wait_if_global_read_lock(), other threads could not
- close their tables. This would make a pretty deadlock.
- */
thd->push_internal_handler(&err_handler);
error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
thd->pop_internal_handler();
@@ -1867,16 +1863,14 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
built_query.append("DROP TABLE ");
}
- mysql_ha_rm_tables(thd, tables, FALSE);
-
- pthread_mutex_lock(&LOCK_open);
+ mysql_ha_rm_tables(thd, tables);
/*
If we have the table in the definition cache, we don't have to check the
.frm file to find if the table is a normal table (not view) and what
engine to use.
*/
-
+ pthread_mutex_lock(&LOCK_open);
for (table= tables; table; table= table->next_local)
{
TABLE_SHARE *share;
@@ -1889,16 +1883,32 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
check_if_log_table(table->db_length, table->db,
table->table_name_length, table->table_name, 1))
{
- my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
pthread_mutex_unlock(&LOCK_open);
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
DBUG_RETURN(1);
}
}
+ pthread_mutex_unlock(&LOCK_open);
- if (!drop_temporary && lock_table_names_exclusively(thd, tables))
+ if (!drop_temporary)
{
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(1);
+ if (!thd->locked_tables)
+ {
+ if (lock_table_names(thd, tables))
+ DBUG_RETURN(1);
+ pthread_mutex_lock(&LOCK_open);
+ for (table= tables; table; table= table->next_local)
+ expel_table_from_cache(0, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ else if (thd->locked_tables)
+ {
+ for (table= tables; table; table= table->next_local)
+ if (!find_temporary_table(thd, table->db, table->table_name) &&
+ !find_write_locked_table(thd->open_tables, table->db,
+ table->table_name))
+ DBUG_RETURN(1);
+ }
}
for (table= tables; table; table= table->next_local)
@@ -1973,17 +1983,21 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table_type= table->db_type;
if (!drop_temporary)
{
- TABLE *locked_table;
- abort_locked_tables(thd, db, table->table_name);
- remove_table_from_cache(thd, db, table->table_name,
- RTFC_WAIT_OTHER_THREAD_FLAG |
- RTFC_CHECK_KILLED_FLAG);
- /*
- If the table was used in lock tables, remember it so that
- unlock_table_names can free it
- */
- if ((locked_table= drop_locked_tables(thd, db, table->table_name)))
- table->table= locked_table;
+ if (thd->locked_tables)
+ {
+ TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name);
+ if (close_cached_table(thd, tab))
+ {
+ error= -1;
+ goto err_with_placeholders;
+ }
+ /*
+ Leave LOCK TABLES mode if we managed to drop all tables
+ which were locked.
+ */
+ if (thd->locked_tables->table_count == 0)
+ unlock_locked_tables(thd);
+ }
if (thd->killed)
{
@@ -1997,6 +2011,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table->internal_tmp_table ?
FN_IS_TMP : 0);
}
+ /*
+ TODO: Investigate what should be done to remove this lock completely.
+ Is exclusive meta-data lock enough ?
+ */
+ pthread_mutex_lock(&LOCK_open);
if (drop_temporary ||
((table_type == NULL &&
access(path, F_OK) &&
@@ -2049,6 +2068,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
error|= new_error;
}
}
+ pthread_mutex_unlock(&LOCK_open);
if (error)
{
if (wrong_tables.length())
@@ -2064,11 +2084,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table->table_name););
}
- /*
- It's safe to unlock LOCK_open: we have an exclusive lock
- on the table name.
- */
- pthread_mutex_unlock(&LOCK_open);
thd->thread_specific_used|= tmp_table_deleted;
error= 0;
if (wrong_tables.length())
@@ -2151,10 +2166,19 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
*/
}
}
- pthread_mutex_lock(&LOCK_open);
err_with_placeholders:
- unlock_table_names(thd, tables, (TABLE_LIST*) 0);
- pthread_mutex_unlock(&LOCK_open);
+ if (!drop_temporary)
+ {
+ /*
+ Under LOCK TABLES we should release meta-data locks on the tables
+ which were dropped. Otherwise we can rely on close_thread_tables()
+ doing this. Unfortunately in this case we are likely to get more
+ false positives in lock_table_name_if_not_cached() function. So
+ it makes sense to remove exclusive meta-data locks in all cases.
+ */
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ }
+
DBUG_RETURN(error);
}
@@ -4005,6 +4029,34 @@ warn:
}
+/**
+ Auxiliary function which obtains exclusive meta-data lock on the
+ table if there are no shared or exclusive on it already.
+
+ See mdl_try_acquire_exclusive_lock() function for more info.
+
+ TODO: This function is here mostly to simplify current patch
+ and probably should be removed.
+ TODO: Investigate if it is kosher to leave lock request in the
+ context in the case when we fail to obtain the lock.
+*/
+
+static bool lock_table_name_if_not_cached(THD *thd, const char *db,
+ const char *table_name,
+ MDL_LOCK **lock)
+{
+ if (!(*lock= mdl_alloc_lock(0, db, table_name, thd->mem_root)))
+ return TRUE;
+ mdl_set_lock_type(*lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, *lock);
+ if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock))
+ {
+ *lock= 0;
+ }
+ return FALSE;
+}
+
+
/*
Database and name-locking aware wrapper for mysql_create_table_no_lock(),
*/
@@ -4015,7 +4067,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
bool internal_tmp_table,
uint select_field_count)
{
- TABLE *name_lock= 0;
+ MDL_LOCK *target_lock= 0;
bool result;
DBUG_ENTER("mysql_create_table");
@@ -4038,12 +4090,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock))
{
result= TRUE;
goto unlock;
}
- if (!name_lock)
+ if (!target_lock)
{
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
@@ -4069,12 +4121,8 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
select_field_count);
unlock:
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
pthread_mutex_lock(&LOCK_lock_db);
if (!--creating_table && creating_database)
pthread_cond_signal(&COND_refresh);
@@ -4212,80 +4260,83 @@ mysql_rename_table(handlerton *base, const char *old_db,
}
-/*
- 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_DROP if table is to be deleted
- HA_EXTRA_FORCE_REOPEN if table is not be used
- HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
- 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_DROP if table is to be deleted
+ HA_EXTRA_FORCE_REOPEN if table is not be used
+ HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
- PREREQUISITES
- Lock on LOCK_open
- Win32 clients must also have a WRITE LOCK on the table !
+ @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));
- safe_mutex_assert_owner(&LOCK_open);
-
(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 */
- /* Wait until all there are no other threads that has this table open */
- remove_table_from_cache(thd, table->s->db.str,
- table->s->table_name.str,
- RTFC_WAIT_OTHER_THREAD_FLAG);
- DBUG_VOID_RETURN;
+ 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);
+ }
+
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
+ pthread_mutex_unlock(&LOCK_open);
+ 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.
- NOTES
- Function ends by signaling threads waiting for the table to try to
- reopen the table.
+ @param thd Thread handler
+ @param table Table to remove from cache
- PREREQUISITES
- Lock on LOCK_open
- Win32 clients must also have a WRITE LOCK on the table !
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
*/
-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_FORCE_REOPEN);
+ /* FIXME: check if we pass proper parameters everywhere. */
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ DBUG_RETURN(TRUE);
+
/* Close lock if this is not got with LOCK TABLES */
if (thd->lock)
{
mysql_unlock_tables(thd, thd->lock);
thd->lock=0; // Start locked threads
}
+
+ pthread_mutex_lock(&LOCK_open);
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
unlink_open_table(thd, table, TRUE);
-
- /* When lock on LOCK_open is freed other threads can continue */
- broadcast_refresh();
- DBUG_VOID_RETURN;
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(FALSE);
}
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
@@ -4308,6 +4359,7 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table,
static int prepare_for_restore(THD* thd, TABLE_LIST* table,
HA_CHECK_OPT *check_opt)
{
+ MDL_LOCK *mdl_lock= 0;
DBUG_ENTER("prepare_for_restore");
if (table->table) // do not overwrite existing tables on restore
@@ -4331,22 +4383,25 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
build_table_filename(dst_path, sizeof(dst_path) - 1,
db, table_name, reg_ext, 0);
- if (lock_and_wait_for_table_name(thd,table))
- DBUG_RETURN(-1);
+ mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(TRUE);
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
if (my_copy(src_path, dst_path, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table);
- pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed copying .frm file"));
}
if (mysql_truncate(thd, table, 1))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table);
- pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed generating table from .frm file"));
}
@@ -4357,10 +4412,11 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
to finish the restore in the handler later on
*/
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table, TRUE))
+ if (reopen_name_locked_table(thd, table))
{
- unlock_table_name(thd, table);
pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed to open partially restored table"));
}
@@ -4380,6 +4436,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
char from[FN_REFLEN],tmp[FN_REFLEN+32];
const char **ext;
MY_STAT stat_info;
+ MDL_LOCK *mdl_lock;
DBUG_ENTER("prepare_for_repair");
if (!(check_opt->sql_flags & TT_USEFRM))
@@ -4391,6 +4448,17 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
uint key_length;
key_length= create_table_def_key(thd, key, table_list, 0);
+ /*
+ TODO: Check that REPAIR's code also conforms to meta-data
+ locking protocol. Fix if it is not.
+ */
+ mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ 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,
&error))))
@@ -4457,41 +4525,29 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx",
from, current_pid, thd->thread_id);
- /* If we could open the table, close it */
if (table_list->table)
{
- pthread_mutex_lock(&LOCK_open);
- close_cached_table(thd, table);
- pthread_mutex_unlock(&LOCK_open);
- }
- if (lock_and_wait_for_table_name(thd,table_list))
- {
- error= -1;
- goto end;
+ /* If we could open the table, close it */
+ if (close_cached_table(thd, table))
+ goto end;
+ table_list->table= 0;
}
+ // After this point we have X mdl lock in both cases
+
if (my_rename(from, tmp, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed renaming data file");
goto end;
}
if (mysql_truncate(thd, table_list, 1))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed generating table from .frm file");
goto end;
}
if (my_rename(tmp, from, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed restoring .MYD file");
goto end;
@@ -4502,9 +4558,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
to finish the repair in the handler later on.
*/
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table_list, TRUE))
+ if (reopen_name_locked_table(thd, table_list))
{
- unlock_table_name(thd, table_list);
pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed to open partially repaired table");
@@ -4519,6 +4574,8 @@ end:
closefrm(table, 1); // Free allocated memory
pthread_mutex_unlock(&LOCK_open);
}
+ if (error)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(error);
}
@@ -4566,7 +4623,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
DBUG_RETURN(TRUE);
- mysql_ha_rm_tables(thd, tables, FALSE);
+ mysql_ha_rm_tables(thd, tables);
for (table= tables; table; table= table->next_local)
{
@@ -5063,6 +5120,7 @@ bool mysql_restore_table(THD* thd, TABLE_LIST* table_list)
bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
{
DBUG_ENTER("mysql_repair_table");
+ set_all_mdl_upgradable(tables);
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
"repair", TL_WRITE, 1,
test(check_opt->sql_flags & TT_USEFRM),
@@ -5250,7 +5308,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table,
bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
HA_CREATE_INFO *create_info)
{
- TABLE *name_lock= 0;
+ MDL_LOCK *target_lock= 0;
char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1];
uint dst_path_length;
char *db= table->db;
@@ -5307,9 +5365,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
}
else
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock))
goto err;
- if (!name_lock)
+ if (!target_lock)
goto table_exists;
dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1,
db, table_name, reg_ext, 0);
@@ -5453,9 +5511,8 @@ binlog:
The table will be closed by unlink_open_table() at the end
of this function.
*/
- table->table= name_lock;
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table, FALSE))
+ if (reopen_name_locked_table(thd, table))
{
pthread_mutex_unlock(&LOCK_open);
goto err;
@@ -5468,6 +5525,10 @@ binlog:
DBUG_ASSERT(result == 0); // store_create_info() always return 0
write_bin_log(thd, TRUE, query.ptr(), query.length());
+
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, table->table, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
}
else // Case 1
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
@@ -5482,12 +5543,8 @@ binlog:
res= FALSE;
err:
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(res);
}
@@ -5570,7 +5627,7 @@ mysql_discard_or_import_tablespace(THD *thd,
err:
ha_autocommit_or_rollback(thd, error);
thd->tablespace_op=FALSE;
-
+
if (error == 0)
{
my_ok(thd);
@@ -5578,7 +5635,7 @@ err:
}
table->file->print_error(error, MYF(0));
-
+
DBUG_RETURN(-1);
}
@@ -6424,7 +6481,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
Alter_info *alter_info,
uint order_num, ORDER *order, bool ignore)
{
- TABLE *table, *new_table= 0, *name_lock= 0;
+ TABLE *table, *new_table= 0;
+ MDL_LOCK *target_lock= 0;
int error= 0;
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -6507,7 +6565,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0);
build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
/* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */
if (alter_info->tablespace_op != NO_TABLESPACE_OP)
@@ -6563,13 +6621,14 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (wait_if_global_read_lock(thd,0,1))
DBUG_RETURN(TRUE);
- pthread_mutex_lock(&LOCK_open);
if (lock_table_names(thd, table_list))
{
error= 1;
goto view_err;
}
-
+
+ pthread_mutex_lock(&LOCK_open);
+
if (!do_rename(thd, table_list, new_db, new_name, new_name, 1))
{
if (mysql_bin_log.is_open())
@@ -6581,15 +6640,17 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
}
my_ok(thd);
}
+ pthread_mutex_unlock(&LOCK_open);
- unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
+ unlock_table_names(thd);
view_err:
- pthread_mutex_unlock(&LOCK_open);
start_waiting_global_read_lock(thd);
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();
@@ -6644,9 +6705,9 @@ view_err:
}
else
{
- if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, new_db, new_name, &target_lock))
DBUG_RETURN(TRUE);
- if (!name_lock)
+ if (!target_lock)
{
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
DBUG_RETURN(TRUE);
@@ -6737,26 +6798,15 @@ view_err:
case LEAVE_AS_IS:
break;
case ENABLE:
- /*
- wait_while_table_is_used() ensures that table being altered is
- opened only by this thread and that TABLE::TABLE_SHARE::version
- of TABLE object corresponding to this table is 0.
- The latter guarantees that no DML statement will open this table
- until ALTER TABLE finishes (i.e. until close_thread_tables())
- while the fact that the table is still open gives us protection
- from concurrent DDL statements.
- */
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ 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:
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ 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;
@@ -6773,24 +6823,19 @@ view_err:
table->alias);
}
- pthread_mutex_lock(&LOCK_open);
- /*
- Unlike to the above case close_cached_table() below will remove ALL
- instances of TABLE from table cache (it will also remove table lock
- held by this thread). So to make actual table renaming and writing
- to binlog atomic we have to put them into the same critical section
- protected by LOCK_open mutex. This also removes gap for races between
- access() and mysql_rename_table() calls.
- */
-
if (!error && (new_name != table_name || new_db != db))
{
thd_proc_info(thd, "rename");
/*
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
@@ -6807,6 +6852,7 @@ view_err:
else
{
*fn_ext(new_name)=0;
+ pthread_mutex_lock(&LOCK_open);
if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0))
error= -1;
else if (Table_triggers_list::change_table_name(thd, db, table_name,
@@ -6816,6 +6862,7 @@ view_err:
table_name, 0);
error= -1;
}
+ pthread_mutex_unlock(&LOCK_open);
}
}
@@ -6837,11 +6884,24 @@ view_err:
table->file->print_error(error, MYF(0));
error= -1;
}
- if (name_lock)
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
table_list->table= NULL; // For query cache
query_cache_invalidate3(thd, table_list, 0);
+
+ if (thd->locked_tables)
+ {
+ /*
+ Under LOCK TABLES we should adjust meta-data locks before finishing
+ statement. Otherwise we can rely on close_thread_tables() releasing
+ them.
+
+ TODO: Investigate what should be done with upgraded table-level
+ lock here...
+ */
+ if (new_name != table_name || new_db != db)
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ else
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
+ }
DBUG_RETURN(error);
}
@@ -7073,7 +7133,7 @@ view_err:
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (fast_alter_partition)
{
- DBUG_ASSERT(!name_lock);
+ DBUG_ASSERT(!target_lock);
DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
create_info, table_list,
db, table_name,
@@ -7158,7 +7218,7 @@ view_err:
tbl.db= new_db;
tbl.table_name= tbl.alias= tmp_name;
/* Table is in thd->temporary_tables */
- new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0,
+ new_table= open_table(thd, &tbl, thd->mem_root, (enum_open_table_action*) 0,
MYSQL_LOCK_IGNORE_FLUSH);
}
else
@@ -7168,10 +7228,10 @@ view_err:
build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "",
FN_IS_TMP);
/* Open our intermediate table */
- new_table=open_temporary_table(thd, path, new_db, tmp_name,0);
+ new_table= open_temporary_table(thd, path, new_db, tmp_name, 1);
}
if (!new_table)
- goto err1;
+ goto err_new_table_cleanup;
/*
Note: In case of MERGE table, we do not attach children. We do not
copy data for MERGE tables. Only the children have data.
@@ -7200,9 +7260,8 @@ view_err:
}
else
{
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err_new_table_cleanup;
thd_proc_info(thd, "manage keys");
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
alter_info->keys_onoff);
@@ -7253,7 +7312,7 @@ view_err:
table->key_info= key_info;
table->file->print_error(error, MYF(0));
table->key_info= save_key_info;
- goto err1;
+ goto err_new_table_cleanup;
}
}
/*end of if (index_add_count)*/
@@ -7276,14 +7335,14 @@ view_err:
index_drop_count)))
{
table->file->print_error(error, MYF(0));
- goto err1;
+ goto err_new_table_cleanup;
}
/* Tell the handler to finally drop the indexes. */
if ((error= table->file->final_drop_index(table)))
{
table->file->print_error(error, MYF(0));
- goto err1;
+ goto err_new_table_cleanup;
}
}
/*end of if (index_drop_count)*/
@@ -7296,16 +7355,16 @@ view_err:
/* Need to commit before a table is unlocked (NDB requirement). */
DBUG_PRINT("info", ("Committing before unlocking table"));
if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd))
- goto err1;
+ goto err_new_table_cleanup;
committed= 1;
}
/*end of if (! new_table) for add/drop index*/
+ if (error)
+ goto err_new_table_cleanup;
+
if (table->s->tmp_table != NO_TMP_TABLE)
{
- /* We changed a temporary table */
- if (error)
- goto err1;
/* Close lock if this is a transactional table */
if (thd->lock)
{
@@ -7316,28 +7375,22 @@ view_err:
close_temporary_table(thd, table, 1, 1);
/* Should pass the 'new_name' as we store table name in the cache */
if (rename_temporary_table(thd, new_table, new_db, new_name))
- goto err1;
+ goto err_new_table_cleanup;
/* We don't replicate alter table statement on temporary tables */
if (!thd->current_stmt_binlog_row_based)
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
goto end_temporary;
}
+ /*
+ Close the intermediate table that will be the new table, but do
+ not delete it! Even altough MERGE tables do not have their children
+ attached here it is safe to call close_temporary_table().
+ */
if (new_table)
{
- /*
- Close the intermediate table that will be the new table.
- Note that MERGE tables do not have their children attached here.
- */
- intern_close_table(new_table);
- my_free(new_table,MYF(0));
- }
- pthread_mutex_lock(&LOCK_open);
- if (error)
- {
- (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP);
- pthread_mutex_unlock(&LOCK_open);
- goto err;
+ close_temporary_table(thd, new_table, 1, 0);
+ new_table= 0;
}
/*
@@ -7362,7 +7415,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_RENAME);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto err_new_table_cleanup;
+
+ pthread_mutex_lock(&LOCK_open);
+
close_data_files_and_morph_locks(thd, db, table_name);
error=0;
@@ -7431,8 +7488,7 @@ view_err:
table_list->table_name_length= strlen(new_name);
table_list->db= new_db;
table_list->db_length= strlen(new_db);
- table_list->table= name_lock;
- if (reopen_name_locked_table(thd, table_list, FALSE))
+ if (reopen_name_locked_table(thd, table_list))
goto err_with_placeholders;
t_table= table_list->table;
}
@@ -7446,16 +7502,24 @@ view_err:
if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG,
create_info))
goto err_with_placeholders;
- if (thd->locked_tables && new_name == table_name && new_db == db)
+ if (thd->locked_tables)
{
- /*
- We are going to reopen table down on the road, so we have to restore
- state of the TABLE object which we used for obtaining of handler
- object to make it suitable for reopening.
- */
- DBUG_ASSERT(t_table == table);
- table->open_placeholder= 1;
- close_handle_and_leave_table_as_lock(table);
+ if (new_name == table_name && new_db == db)
+ {
+ /*
+ We are going to reopen table down on the road, so we have to restore
+ state of the TABLE object which we used for obtaining of handler
+ object to make it suitable for reopening.
+ */
+ DBUG_ASSERT(t_table == table);
+ table->open_placeholder= 1;
+ close_handle_and_leave_table_as_lock(table);
+ }
+ else
+ {
+ /* Unlink the new name from the list of locked tables. */
+ unlink_open_table(thd, t_table, FALSE);
+ }
}
}
@@ -7464,7 +7528,7 @@ view_err:
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;
@@ -7508,18 +7572,17 @@ view_err:
table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0);
- if (thd->locked_tables && (new_name != table_name || new_db != db))
+ if (thd->locked_tables)
{
- /*
- If are we under LOCK TABLES and did ALTER TABLE with RENAME we need
- to remove placeholders for the old table and for the target table
- from the list of open tables and table cache. If we are not under
- LOCK TABLES we can rely on close_thread_tables() doing this job.
- */
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, table, FALSE);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
+ if ((new_name != table_name || new_db != db))
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, table, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ }
+ else
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
}
end_temporary:
@@ -7530,7 +7593,7 @@ end_temporary:
thd->some_tables_deleted=0;
DBUG_RETURN(FALSE);
-err1:
+err_new_table_cleanup:
if (new_table)
{
/* close_temporary_table() frees the new_table pointer. */
@@ -7574,12 +7637,8 @@ err:
alter_info->datetime_field->field_name);
thd->abort_on_warning= save_abort_on_warning;
}
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(TRUE);
err_with_placeholders:
@@ -7589,9 +7648,8 @@ err_with_placeholders:
from list of open tables list and table cache.
*/
unlink_open_table(thd, table, FALSE);
- if (name_lock)
- unlink_open_table(thd, name_lock, FALSE);
pthread_mutex_unlock(&LOCK_open);
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(TRUE);
}
/* mysql_alter_table */
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
index d9beb77f546..3907aa6a5ae 100644
--- a/sql/sql_test.cc
+++ b/sql/sql_test.cc
@@ -75,7 +75,8 @@ print_where(COND *cond,const char *info, enum_query_type query_type)
void print_cached_tables(void)
{
uint idx,count,unused;
- TABLE *start_link,*lnk;
+ TABLE_SHARE *share;
+ TABLE *start_link, *lnk, *entry;
compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
@@ -83,16 +84,26 @@ void print_cached_tables(void)
pthread_mutex_lock(&LOCK_open);
puts("DB Table Version Thread Open Lock");
- for (idx=unused=0 ; idx < open_cache.records ; idx++)
+ for (idx=unused=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
- entry->s->db.str, entry->s->table_name.str, entry->s->version,
- entry->in_use ? entry->in_use->thread_id : 0L,
- entry->db_stat ? 1 : 0,
- entry->in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : "Not in use");
- if (!entry->in_use)
+ share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ while ((entry= it++))
+ {
+ printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
+ entry->s->db.str, entry->s->table_name.str, entry->s->version,
+ entry->in_use->thread_id, entry->db_stat ? 1 : 0,
+ lock_descriptions[(int)entry->reginfo.lock_type]);
+ }
+ it.init(share->free_tables);
+ while ((entry= it++))
+ {
unused++;
+ printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
+ entry->s->db.str, entry->s->table_name.str, entry->s->version,
+ 0L, entry->db_stat ? 1 : 0, "Not in use");
+ }
}
count=0;
if ((start_link=lnk=unused_tables))
@@ -104,17 +115,18 @@ void print_cached_tables(void)
printf("unused_links isn't linked properly\n");
return;
}
- } while (count++ < open_cache.records && (lnk=lnk->next) != start_link);
+ } while (count++ < table_cache_count && (lnk=lnk->next) != start_link);
if (lnk != start_link)
{
printf("Unused_links aren't connected\n");
}
}
if (count != unused)
- printf("Unused_links (%d) doesn't match open_cache: %d\n", count,unused);
+ printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
+ unused);
printf("\nCurrent refresh version: %ld\n",refresh_version);
- if (my_hash_check(&open_cache))
- printf("Error: File hash table is corrupted\n");
+ if (my_hash_check(&table_def_cache))
+ printf("Error: Table definition hash table is corrupted\n");
fflush(stdout);
pthread_mutex_unlock(&LOCK_open);
/* purecov: end */
@@ -380,7 +392,7 @@ static void display_table_locks(void)
LIST *list;
DYNAMIC_ARRAY saved_table_locks;
- (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),open_cache.records + 20,50);
+ (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50);
pthread_mutex_lock(&THR_LOCK_lock);
for (list= thr_lock_thread_list; list; list= list_rest(list))
{
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index ecbb6473ec4..a623b3c80f3 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -387,8 +387,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
!(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1)))
DBUG_RETURN(TRUE);
- pthread_mutex_lock(&LOCK_open);
-
if (!create)
{
bool if_exists= thd->lex->drop_if_exists;
@@ -444,26 +442,28 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
tables->required_type= FRMTYPE_TABLE;
/* Keep consistent with respect to other DDL statements */
- mysql_ha_rm_tables(thd, tables, TRUE);
+ mysql_ha_rm_tables(thd, tables);
if (thd->locked_tables)
{
- /* Table must be write locked */
if (name_lock_locked_table(thd, tables))
goto end;
+ pthread_mutex_lock(&LOCK_open);
}
else
{
- /* Grab the name lock and insert the placeholder*/
+ /*
+ Obtain exlusive meta-data lock on the table and remove TABLE
+ instances from cache.
+ */
if (lock_table_names(thd, tables))
goto end;
- /* Convert the placeholder to a real table */
- if (reopen_name_locked_table(thd, tables, TRUE))
- {
- unlock_table_name(thd, tables);
- goto end;
- }
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, tables->db, tables->table_name);
+
+ if (reopen_name_locked_table(thd, tables))
+ goto end_unlock;
}
table= tables->table;
@@ -472,11 +472,11 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (!create)
{
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
- goto end;
+ goto end_unlock;
}
if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
- goto end;
+ goto end_unlock;
}
result= (create ?
@@ -489,7 +489,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
/* 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);
@@ -503,14 +503,22 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
thd->in_lock_tables= 0;
}
-end:
+end_unlock:
+ pthread_mutex_unlock(&LOCK_open);
+end:
if (!result)
{
write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length());
}
- pthread_mutex_unlock(&LOCK_open);
+ /*
+ If we are under LOCK TABLES we should restore original state of meta-data
+ locks. Otherwise call to close_thread_tables() will take care about both
+ TABLE instance created by reopen_name_locked_table() and meta-data lock.
+ */
+ if (thd->locked_tables)
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
if (need_start_waiting)
start_waiting_global_read_lock(thd);
@@ -1879,11 +1887,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db,
In the future, only an exclusive table name lock will be enough.
*/
#ifndef DBUG_OFF
- uchar key[MAX_DBKEY_LENGTH];
- uint key_length= (uint) (strmov(strmov((char*)&key[0], db)+1,
- old_table)-(char*)&key[0])+1;
-
- if (!is_table_name_exclusively_locked_by_this_thread(thd, key, key_length))
+ if (mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, db, old_table))
safe_mutex_assert_owner(&LOCK_open);
#endif
diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc
index a1a0d9633b7..25a0db2fbb8 100644
--- a/sql/sql_udf.cc
+++ b/sql/sql_udf.cc
@@ -142,6 +142,7 @@ void udf_init()
tables.alias= tables.table_name= (char*) "func";
tables.lock_type = TL_READ;
tables.db= db;
+ alloc_mdl_locks(&tables, new_thd->mem_root);
if (simple_open_n_lock_tables(new_thd, &tables))
{
@@ -485,6 +486,7 @@ int mysql_create_function(THD *thd,udf_func *udf)
bzero((char*) &tables,sizeof(tables));
tables.db= (char*) "mysql";
tables.table_name= tables.alias= (char*) "func";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* Allow creation of functions even if we can't open func table */
if (!(table = open_ltable(thd, &tables, TL_WRITE, 0)))
goto err;
@@ -563,6 +565,7 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name)
bzero((char*) &tables,sizeof(tables));
tables.db=(char*) "mysql";
tables.table_name= tables.alias= (char*) "func";
+ alloc_mdl_locks(&tables, thd->mem_root);
if (!(table = open_ltable(thd, &tables, TL_WRITE, 0)))
goto err;
table->use_all_columns();
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index da8b2d046bb..1ad39a2f838 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -226,7 +226,7 @@ int mysql_update(THD *thd,
break;
if (!need_reopen)
DBUG_RETURN(1);
- close_tables_for_reopen(thd, &table_list);
+ close_tables_for_reopen(thd, &table_list, FALSE);
}
if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
@@ -1149,7 +1149,7 @@ reopen_tables:
*/
cleanup_items(thd->free_list);
- close_tables_for_reopen(thd, &table_list);
+ close_tables_for_reopen(thd, &table_list, FALSE);
goto reopen_tables;
}
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 83341a53c3e..a0acde600c7 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -178,40 +178,17 @@ err:
static bool
fill_defined_view_parts (THD *thd, TABLE_LIST *view)
{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
LEX *lex= thd->lex;
- bool not_used;
TABLE_LIST decoy;
memcpy (&decoy, view, sizeof (TABLE_LIST));
+ key_length= create_table_def_key(thd, key, view, 0);
- /*
- Let's reset decoy.view before calling open_table(): when we start
- supporting ALTER VIEW in PS/SP that may save us from a crash.
- */
-
- decoy.view= NULL;
-
- /*
- open_table() will return NULL if 'decoy' is idenitifying a view *and*
- there is no TABLE object for that view in the table cache. However,
- decoy.view will be set to 1.
-
- If there is a TABLE-instance for the oject identified by 'decoy',
- open_table() will return that instance no matter if it is a table or
- a view.
-
- Thus, there is no need to check for the return value of open_table(),
- since the return value itself does not mean anything.
- */
-
- open_table(thd, &decoy, thd->mem_root, &not_used, OPEN_VIEW_NO_PARSE);
-
- if (!decoy.view)
- {
- /* It's a table. */
- my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
+ if (tdc_open_view(thd, &decoy, decoy.alias, key, key_length,
+ thd->mem_root, OPEN_VIEW_NO_PARSE))
return TRUE;
- }
if (!lex->definer)
{
@@ -400,6 +377,35 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
DBUG_ASSERT(!lex->proc_list.first && !lex->result &&
!lex->param_list.elements);
+ /*
+ We can't allow taking exclusive meta-data locks of unlocked view under
+ LOCK TABLES since this might lead to deadlock. Since at the moment we
+ can't really lock view with LOCK TABLES we simply prohibit creation/
+ alteration of views under LOCK TABLES.
+ */
+
+ if (thd->locked_tables)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ res= TRUE;
+ goto err;
+ }
+
+ if ((res= create_view_precheck(thd, tables, view, mode)))
+ goto err;
+
+ lex->link_first_table_back(view, link_to_local);
+ view->open_table_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL;
+
+ if (open_and_lock_tables(thd, lex->query_tables))
+ {
+ view= lex->unlink_first_table(&link_to_local);
+ res= TRUE;
+ goto err;
+ }
+
+ view= lex->unlink_first_table(&link_to_local);
+
if (mode != VIEW_CREATE_NEW)
{
if (mode == VIEW_ALTER &&
@@ -464,16 +470,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
}
}
#endif
-
- if ((res= create_view_precheck(thd, tables, view, mode)))
- goto err;
-
- if (open_and_lock_tables(thd, tables))
- {
- res= TRUE;
- goto err;
- }
-
/*
check that tables are not temporary and this VIEW do not used in query
(it is possible with ALTERing VIEW).
@@ -615,11 +611,13 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
}
#endif
+
if (wait_if_global_read_lock(thd, 0, 0))
{
res= TRUE;
goto err;
}
+
pthread_mutex_lock(&LOCK_open);
res= mysql_register_view(thd, view, mode);
@@ -1331,7 +1329,10 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
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
@@ -1579,6 +1580,21 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
bool something_wrong= FALSE;
DBUG_ENTER("mysql_drop_view");
+ /*
+ We can't allow dropping of unlocked view under LOCK TABLES since this
+ might lead to deadlock. But since we can't really lock view with LOCK
+ TABLES we have to simply prohibit dropping of views.
+ */
+
+ if (thd->locked_tables)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (lock_table_names(thd, views))
+ DBUG_RETURN(TRUE);
+
pthread_mutex_lock(&LOCK_open);
for (view= views; view; view= view->next_local)
{
diff --git a/sql/table.cc b/sql/table.cc
index 7ea04ed3e15..181014df9f4 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -316,6 +316,9 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
share->table_map_id= ~0UL;
share->cached_row_logging_check= -1;
+ share->used_tables.empty();
+ share->free_tables.empty();
+
memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST);
pthread_cond_init(&share->cond, NULL);
@@ -382,6 +385,9 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
*/
share->table_map_id= (ulong) thd->query_id;
+ share->used_tables.empty();
+ share->free_tables.empty();
+
DBUG_VOID_RETURN;
}
@@ -4832,6 +4838,20 @@ size_t max_row_length(TABLE *table, const uchar *data)
return length;
}
+
+/**
+ Helper function which allows to allocate metadata lock request
+ objects for all elements of table list.
+*/
+
+void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root)
+{
+ for ( ; table_list ; table_list= table_list->next_global)
+ table_list->mdl_lock= mdl_alloc_lock(0, table_list->db,
+ table_list->table_name, root);
+}
+
+
/*****************************************************************************
** Instansiate templates
*****************************************************************************/
diff --git a/sql/table.h b/sql/table.h
index 76bebd3fdaa..b0598e07299 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -17,6 +17,8 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+#include "sql_plist.h"
+
/* Structs that defines the TABLE */
class Item; /* Needed by ORDER */
@@ -28,6 +30,7 @@ class st_select_lex;
class partition_info;
class COND_EQUAL;
class Security_context;
+struct MDL_LOCK;
/*************************************************************************/
@@ -288,6 +291,9 @@ typedef enum enum_table_category TABLE_CATEGORY;
TABLE_CATEGORY get_table_category(const LEX_STRING *db,
const LEX_STRING *name);
+
+struct TABLE_share;
+
/*
This structure is shared between different table objects. There is one
instance of table share per one table in the database.
@@ -310,6 +316,13 @@ struct TABLE_SHARE
pthread_cond_t cond; /* To signal that share is ready */
TABLE_SHARE *next, **prev; /* Link to unused shares */
+ /*
+ Doubly-linked (back-linked) lists of used and unused TABLE objects
+ for this share.
+ */
+ I_P_List <TABLE, TABLE_share> used_tables;
+ I_P_List <TABLE, TABLE_share> free_tables;
+
/* The following is copied to each TABLE on OPEN */
Field **field;
Field **found_next_number_field;
@@ -613,6 +626,19 @@ struct TABLE
handler *file;
TABLE *next, *prev;
+private:
+ /**
+ Links for the lists of used/unused TABLE objects for this share.
+ Declared as private to avoid direct manipulation with those objects.
+ One should use methods of I_P_List template instead.
+ */
+ TABLE *share_next, **share_prev;
+
+ friend struct TABLE_share;
+ friend bool reopen_table(TABLE *table);
+
+public:
+
/* For the below MERGE related members see top comment in ha_myisammrg.cc */
TABLE *parent; /* Set in MERGE child. Ptr to parent */
TABLE_LIST *child_l; /* Set in MERGE parent. List of children */
@@ -814,6 +840,7 @@ struct TABLE
partition_info *part_info; /* Partition related information */
bool no_partitions_used; /* If true, all partitions have been pruned away */
#endif
+ MDL_LOCK *mdl_lock;
bool fill_item_list(List<Item> *item_list) const;
void reset_item_list(List<Item> *item_list) const;
@@ -859,6 +886,25 @@ struct TABLE
bool is_children_attached(void);
};
+
+/**
+ Helper class which specifies which members of TABLE are used for
+ participation in the list of used/unused TABLE objects for the share.
+*/
+
+struct TABLE_share
+{
+ static inline TABLE **next_ptr(TABLE *l)
+ {
+ return &l->share_next;
+ }
+ static inline TABLE ***prev_ptr(TABLE *l)
+ {
+ return &l->share_prev;
+ }
+};
+
+
enum enum_schema_table_state
{
NOT_PROCESSED= 0,
@@ -1326,11 +1372,18 @@ struct TABLE_LIST
*/
bool prelocking_placeholder;
/*
- This TABLE_LIST object corresponds to the table to be created
- so it is possible that it does not exist (used in CREATE TABLE
- ... SELECT implementation).
+ This TABLE_LIST object corresponds to the table/view which requires
+ special handling/meta-data locking. For example this is a target
+ table in CREATE TABLE ... SELECT so it is possible that it does not
+ exist and we should take exclusive meta-data lock on it in this
+ 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 create;
+ bool mdl_upgradable;
bool internal_tmp_table;
/** TRUE if an alias for this table was specified in the SQL. */
bool is_alias;
@@ -1379,6 +1432,9 @@ struct TABLE_LIST
bool has_table_lookup_value;
uint table_open_method;
enum enum_schema_table_state schema_table_state;
+
+ MDL_LOCK *mdl_lock;
+
void calc_md5(char *buffer);
void set_underlying_merge();
int view_check_option(THD *thd, bool ignore_failure);
@@ -1386,8 +1442,7 @@ struct TABLE_LIST
void cleanup_items();
bool placeholder()
{
- return derived || view || schema_table || (create && !table->db_stat) ||
- !table;
+ return derived || view || schema_table || !table;
}
void print(THD *thd, String *str, enum_query_type query_type);
bool check_single_table(TABLE_LIST **table, table_map map,
@@ -1746,4 +1801,17 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set,
size_t max_row_length(TABLE *table, const uchar *data);
+
+void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root);
+
+/**
+ Helper function which allows to mark all elements in table list
+ as requiring upgradable metadata locks.
+*/
+
+inline void set_all_mdl_upgradable(TABLE_LIST *tables)
+{
+ for (; tables; tables= tables->next_global)
+ tables->mdl_upgradable= TRUE;
+}
#endif /* TABLE_INCLUDED */