diff options
Diffstat (limited to 'sql/lock.cc')
-rw-r--r-- | sql/lock.cc | 594 |
1 files changed, 422 insertions, 172 deletions
diff --git a/sql/lock.cc b/sql/lock.cc index 9b86ba69bf2..feaba4267c1 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -1,5 +1,5 @@ -/* Copyright (c) 2000-2008 MySQL AB, 2009 Sun Microsystems, Inc. - Use is subject to license terms. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. 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 @@ -12,11 +12,15 @@ 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ -/* locking functions for mysql */ -/* +/** + @file + + Locking functions for mysql. + Because of the new concurrent inserts, we must first get external locks before getting internal locks. If we do it in the other order, the status information is not up to date when called from the lock handler. @@ -61,7 +65,12 @@ - When calling UNLOCK TABLES we call mysql_unlock_tables() for all tables used in LOCK TABLES -TODO: + If table_handler->external_lock(thd, locktype) fails, we call + table_handler->external_lock(thd, F_UNLCK) for each table that was locked, + excluding one that caused failure. That means handler must cleanup itself + in case external_lock() fails. + + @todo Change to use my_malloc() ONLY when using LOCK TABLES command or when we are forced to use mysql_lock_merge. */ @@ -70,6 +79,11 @@ TODO: #include <hash.h> #include <assert.h> +/** + @defgroup Locking Locking + @{ +*/ + extern HASH open_cache; /* flags for get_lock_data */ @@ -93,6 +107,7 @@ static void print_lock_error(int error, const char *); count The number of tables to lock. flags Options: MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered or dropped tables by itself, @@ -111,16 +126,90 @@ static void print_lock_error(int error, const char *); static int thr_lock_errno_to_mysql[]= { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; +/** + Perform semantic checks for mysql_lock_tables. + @param thd The current thread + @param tables The tables to lock + @param count The number of tables to lock + @param flags Lock flags + @return 0 if all the check passed, non zero if a check failed. +*/ +int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) +{ + bool log_table_write_query; + uint system_count; + uint i; + + DBUG_ENTER("mysql_lock_tables_check"); + + system_count= 0; + log_table_write_query= (is_log_table_write_query(thd->lex->sql_command) + || ((flags & MYSQL_LOCK_PERF_SCHEMA) != 0)); + + for (i=0 ; i<count; i++) + { + TABLE *t= tables[i]; + + /* Protect against 'fake' partially initialized TABLE_SHARE */ + DBUG_ASSERT(t->s->table_category != TABLE_UNKNOWN_CATEGORY); + + /* + Table I/O to performance schema tables is performed + only internally by the server implementation. + When a user is requesting a lock, the following + constraints are enforced: + */ + if (t->s->require_write_privileges() && + ! log_table_write_query) + { + /* + A user should not be able to prevent writes, + or hold any type of lock in a session, + since this would be a DOS attack. + */ + if ((t->reginfo.lock_type >= TL_READ_NO_INSERT) + || (thd->lex->sql_command == SQLCOM_LOCK_TABLES)) + { + my_error(ER_CANT_LOCK_LOG_TABLE, MYF(0)); + DBUG_RETURN(1); + } + } + + if ((t->s->table_category == TABLE_CATEGORY_SYSTEM) && + (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE)) + { + system_count++; + } + } + + /* + Locking of system tables is restricted: + locking a mix of system and non-system tables in the same lock + is prohibited, to prevent contention. + */ + if ((system_count > 0) && (system_count < count)) + { + my_error(ER_WRONG_LOCK_OF_SYSTEM_TABLE, MYF(0)); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { MYSQL_LOCK *sql_lock; TABLE *write_lock_used; int rc; + DBUG_ENTER("mysql_lock_tables"); *need_reopen= FALSE; + if (mysql_lock_tables_check(thd, tables, count, flags)) + DBUG_RETURN (NULL); + for (;;) { if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS, @@ -138,7 +227,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data(sql_lock); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); sql_lock=0; break; } @@ -146,22 +235,41 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data(sql_lock); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); goto retry; } } + if (!(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) && + write_lock_used && + opt_readonly && + !(thd->security_ctx->master_access & SUPER_ACL) && + !thd->slave_thread) + { + /* + Someone has issued SET GLOBAL READ_ONLY=1 and we want a write lock. + We do not wait for READ_ONLY=0, and fail. + */ + reset_lock_data(sql_lock); + my_free((uchar*) sql_lock, MYF(0)); + sql_lock=0; + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); + break; + } + thd_proc_info(thd, "System lock"); + DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); if (sql_lock->table_count && lock_external(thd, sql_lock->table, sql_lock->table_count)) { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data(sql_lock); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); sql_lock=0; break; } thd_proc_info(thd, "Table lock"); + DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); thd->locked=1; /* Copy the lock data array. thr_multi_lock() reorders its contens. */ memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, @@ -176,7 +284,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, if (sql_lock->table_count) VOID(unlock_external(thd, sql_lock->table, sql_lock->table_count)); my_error(rc, MYF(0)); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); sql_lock= 0; break; } @@ -194,6 +302,10 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, } else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) { + /* + Thread was killed or lock aborted. Let upper level close all + used tables and retry or give error. + */ thd->locked=0; break; } @@ -230,7 +342,7 @@ retry: } } - thd->lock_time(); + thd->set_time_after_lock(); DBUG_RETURN (sql_lock); } @@ -241,6 +353,7 @@ static int lock_external(THD *thd, TABLE **tables, uint count) int lock_type,error; DBUG_ENTER("lock_external"); + DBUG_PRINT("info", ("count %d", count)); for (i=1 ; i <= count ; i++, tables++) { DBUG_ASSERT((*tables)->reginfo.lock_type >= TL_READ); @@ -250,11 +363,12 @@ static int lock_external(THD *thd, TABLE **tables, uint count) (*tables)->reginfo.lock_type <= TL_READ_NO_INSERT)) lock_type=F_RDLCK; - if ((error= (*tables)->file->ha_external_lock(thd,lock_type))) + if ((error=(*tables)->file->ha_external_lock(thd,lock_type))) { print_lock_error(error, (*tables)->file->table_type()); - for (; i-- ; tables--) + while (--i) { + tables--; (*tables)->file->ha_external_lock(thd, F_UNLCK); (*tables)->current_lock=F_UNLCK; } @@ -277,14 +391,15 @@ void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock) thr_multi_unlock(sql_lock->locks,sql_lock->lock_count); if (sql_lock->table_count) VOID(unlock_external(thd,sql_lock->table,sql_lock->table_count)); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); DBUG_VOID_RETURN; } -/* - Unlock some of the tables locked by mysql_lock_tables +/** + Unlock some of the tables locked by mysql_lock_tables. + This will work even if get_lock_data fails (next unlock will free all) - */ +*/ void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count) { @@ -296,8 +411,8 @@ void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count) } -/* -** unlock all tables locked for read. +/** + unlock all tables locked for read. */ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) @@ -370,6 +485,9 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) unlock_external() we call handler::external_lock(F_UNLCK) only if table->current_lock is not F_UNLCK. + @param thd thread context + @param locked list of locked tables + @param table the table to unlock @param always_unlock specify explicitly if the legacy side effect is desired. */ @@ -438,32 +556,51 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, } } -/* abort all other threads waiting to get lock in table */ +/* Downgrade all locks on a table to new WRITE level from WRITE_ONLY */ -void mysql_lock_abort(THD *thd, TABLE *table) +void mysql_lock_downgrade_write(THD *thd, TABLE *table, + thr_lock_type new_lock_type) { MYSQL_LOCK *locked; TABLE *write_lock_used; + if ((locked = get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, + &write_lock_used))) + { + for (uint i=0; i < locked->lock_count; i++) + thr_downgrade_write_lock(locked->locks[i], new_lock_type); + my_free((uchar*) locked,MYF(0)); + } +} + + +/** Abort all other threads waiting to get lock in table. */ + +void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock) +{ + MYSQL_LOCK *locked; + TABLE *write_lock_used; + DBUG_ENTER("mysql_lock_abort"); + if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK, &write_lock_used))) { for (uint i=0; i < locked->lock_count; i++) - thr_abort_locks(locked->locks[i]->lock); - my_free((gptr) locked,MYF(0)); + thr_abort_locks(locked->locks[i]->lock, upgrade_lock); + my_free((uchar*) locked,MYF(0)); } + DBUG_VOID_RETURN; } -/* - Abort one thread / table combination +/** + Abort one thread / table combination. - SYNOPSIS - mysql_lock_abort_for_thread() - thd Thread handler - table Table that should be removed from lock queue + @param thd Thread handler + @param table Table that should be removed from lock queue - RETURN + @retval 0 Table was not locked by another thread + @retval 1 Table was locked by at least one other thread */ @@ -480,10 +617,10 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table) for (uint i=0; i < locked->lock_count; i++) { if (thr_abort_locks_for_thread(locked->locks[i]->lock, - table->in_use->real_id)) + table->in_use->thread_id)) result= TRUE; } - my_free((gptr) locked,MYF(0)); + my_free((uchar*) locked,MYF(0)); } DBUG_RETURN(result); } @@ -525,41 +662,39 @@ MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) } /* Delete old, not needed locks */ - my_free((gptr) a,MYF(0)); - my_free((gptr) b,MYF(0)); + my_free((uchar*) a,MYF(0)); + my_free((uchar*) b,MYF(0)); DBUG_RETURN(sql_lock); } -/* +/** Find duplicate lock in tables. - SYNOPSIS - mysql_lock_have_duplicate() - thd The current thread. - needle The table to check for duplicate lock. - haystack The list of tables to search for the dup lock. + Temporary tables are ignored here like they are ignored in + get_lock_data(). If we allow two opens on temporary tables later, + both functions should be checked. + + @param thd The current thread. + @param needle The table to check for duplicate lock. + @param haystack The list of tables to search for the dup lock. - NOTE + @note This is mainly meant for MERGE tables in INSERT ... SELECT situations. The 'real', underlying tables can be found only after the MERGE tables are opened. This function assumes that the tables are already locked. - Temporary tables are ignored here like they are ignored in - get_lock_data(). If we allow two opens on temporary tables later, - both functions should be checked. - - RETURN - NULL No duplicate lock found. - ! NULL First table from 'haystack' that matches a lock on 'needle'. + @retval + NULL No duplicate lock found. + @retval + !NULL First table from 'haystack' that matches a lock on 'needle'. */ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, TABLE_LIST *haystack) { MYSQL_LOCK *mylock; - TABLE **lock_tables; TABLE *table; TABLE *table2; THR_LOCK_DATA **lock_locks; @@ -588,12 +723,11 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, if (mylock->table_count < 2) goto end; - lock_locks= mylock->locks; - lock_tables= mylock->table; + lock_locks= mylock->locks; /* Prepare table related variables that don't change in loop. */ DBUG_ASSERT((table->lock_position < mylock->table_count) && - (table == lock_tables[table->lock_position])); + (table == mylock->table[table->lock_position])); table_lock_data= lock_locks + table->lock_data_start; end_data= table_lock_data + table->lock_count; @@ -607,7 +741,7 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, /* All tables in list must be in lock. */ DBUG_ASSERT((table2->lock_position < mylock->table_count) && - (table2 == lock_tables[table2->lock_position])); + (table2 == mylock->table[table2->lock_position])); for (lock_data2= lock_locks + table2->lock_data_start, end_data2= lock_data2 + table2->lock_count; @@ -636,7 +770,7 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, } - /* unlock a set of external */ +/** Unlock a set of external. */ static int unlock_external(THD *thd, TABLE **table,uint count) { @@ -649,7 +783,7 @@ static int unlock_external(THD *thd, TABLE **table,uint count) if ((*table)->current_lock != F_UNLCK) { (*table)->current_lock = F_UNLCK; - if ((error= (*table)->file->ha_external_lock(thd, F_UNLCK))) + if ((error=(*table)->file->ha_external_lock(thd, F_UNLCK))) { error_code=error; print_lock_error(error_code, (*table)->file->table_type()); @@ -661,21 +795,17 @@ static int unlock_external(THD *thd, TABLE **table,uint count) } -/* - Get lock structures from table structs and initialize locks - - SYNOPSIS - get_lock_data() - thd Thread handler - table_ptr Pointer to tables that should be locks - flags One of: - GET_LOCK_UNLOCK: If we should send TL_IGNORE to - store lock - GET_LOCK_STORE_LOCKS: Store lock info in TABLE - write_lock_used Store pointer to last table with WRITE_ALLOW_WRITE +/** + Get lock structures from table structs and initialize locks. + + @param thd Thread handler + @param table_ptr Pointer to tables that should be locks + @param flags One of: + - GET_LOCK_UNLOCK : If we should send TL_IGNORE to store lock + - GET_LOCK_STORE_LOCKS : Store lock info in TABLE + @param write_lock_used Store pointer to last table with WRITE_ALLOW_WRITE */ - static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags, TABLE **write_lock_used) { @@ -685,27 +815,19 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, TABLE **to, **table_buf; DBUG_ENTER("get_lock_data"); + DBUG_ASSERT((flags == GET_LOCK_UNLOCK) || (flags == GET_LOCK_STORE_LOCKS)); + + DBUG_PRINT("info", ("count %d", count)); *write_lock_used=0; for (i=tables=lock_count=0 ; i < count ; i++) { - if (table_ptr[i]->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE) + TABLE *t= table_ptr[i]; + + if (t->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE) { - tables+=table_ptr[i]->file->lock_count(); + tables+= t->file->lock_count(); lock_count++; } - /* - To be able to open and lock for reading system tables like 'mysql.proc', - when we already have some tables opened and locked, and avoid deadlocks - we have to disallow write-locking of these tables with any other tables. - */ - if (table_ptr[i]->s->system_table && - table_ptr[i]->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE && - count != 1) - { - my_error(ER_WRONG_LOCK_OF_SYSTEM_TABLE, MYF(0), table_ptr[i]->s->db, - table_ptr[i]->s->table_name); - DBUG_RETURN(0); - } } /* @@ -723,7 +845,6 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, locks= locks_buf= sql_lock->locks= (THR_LOCK_DATA**) (sql_lock + 1); to= table_buf= sql_lock->table= (TABLE**) (locks + tables * 2); sql_lock->table_count=lock_count; - sql_lock->lock_count=tables; for (i=0 ; i < count ; i++) { @@ -733,7 +854,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, if ((table=table_ptr[i])->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE) continue; lock_type= table->reginfo.lock_type; - DBUG_ASSERT (lock_type != TL_WRITE_DEFAULT); + DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT); if (lock_type >= TL_WRITE_ALLOW_WRITE) { *write_lock_used=table; @@ -743,7 +864,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, /* Clear the lock type of the lock data that are stored already. */ sql_lock->lock_count= (uint) (locks - sql_lock->locks); reset_lock_data(sql_lock); - my_free((gptr) sql_lock,MYF(0)); + my_free((uchar*) sql_lock,MYF(0)); DBUG_RETURN(0); } } @@ -763,35 +884,46 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, for ( ; org_locks != locks ; org_locks++) (*org_locks)->debug_print_param= (void *) table; } + /* + We do not use 'tables', because there are cases where store_lock() + returns less locks than lock_count() claimed. This can happen when + a FLUSH TABLES tries to abort locks from a MERGE table of another + thread. When that thread has just opened the table, but not yet + attached its children, it cannot return the locks. lock_count() + always returns the number of locks that an attached table has. + This is done to avoid the reverse situation: If lock_count() would + return 0 for a non-attached MERGE table, and that table becomes + attached between the calls to lock_count() and store_lock(), then + we would have allocated too little memory for the lock data. Now + we may allocate too much, but better safe than memory overrun. + And in the FLUSH case, the memory is released quickly anyway. + */ + sql_lock->lock_count= locks - locks_buf; + DBUG_PRINT("info", ("sql_lock->table_count %d sql_lock->lock_count %d", + sql_lock->table_count, sql_lock->lock_count)); DBUG_RETURN(sql_lock); } -/* +/** Reset lock type in lock data. - SYNOPSIS - reset_lock_data() - sql_lock The MySQL lock. - - DESCRIPTION - - After a locking error we want to quit the locking of the table(s). - The test case in the bug report for Bug #18544 has the following - cases: 1. Locking error in lock_external() due to InnoDB timeout. - 2. Locking error in get_lock_data() due to missing write permission. - 3. Locking error in wait_if_global_read_lock() due to lock conflict. + After a locking error we want to quit the locking of the table(s). + The test case in the bug report for Bug #18544 has the following + cases: + -# Locking error in lock_external() due to InnoDB timeout. + -# Locking error in get_lock_data() due to missing write permission. + -# Locking error in wait_if_global_read_lock() due to lock conflict. - In all these cases we have already set the lock type into the lock - data of the open table(s). If the table(s) are in the open table - cache, they could be reused with the non-zero lock type set. This - could lead to ignoring a different lock type with the next lock. + In all these cases we have already set the lock type into the lock + data of the open table(s). If the table(s) are in the open table + cache, they could be reused with the non-zero lock type set. This + could lead to ignoring a different lock type with the next lock. - Clear the lock type of all lock data. This ensures that the next - lock request will set its lock type properly. + Clear the lock type of all lock data. This ensures that the next + lock request will set its lock type properly. - RETURN - void + @param sql_lock The MySQL lock. */ static void reset_lock_data(MYSQL_LOCK *sql_lock) @@ -814,20 +946,19 @@ static void reset_lock_data(MYSQL_LOCK *sql_lock) This is used when we need total access to a closed, not open table *****************************************************************************/ -/* +/** Lock and wait for the named lock. - SYNOPSIS - lock_and_wait_for_table_name() - thd Thread handler - table_list Lock first table in this list + @param thd Thread handler + @param table_list Lock first table in this list - NOTES + @note Works together with global read lock. - RETURN + @retval 0 ok + @retval 1 error */ @@ -840,7 +971,7 @@ int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list) if (wait_if_global_read_lock(thd, 0, 1)) DBUG_RETURN(1); VOID(pthread_mutex_lock(&LOCK_open)); - if ((lock_retcode = lock_table_name(thd, table_list)) < 0) + if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0) goto end; if (lock_retcode && wait_for_locked_table_names(thd, table_list)) { @@ -856,33 +987,34 @@ end: } -/* +/** Put a not open table with an old refresh version in the table cache. - SYNPOSIS - lock_table_name() - thd Thread handler - table_list Lock first table in this list + @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 - WARNING + @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' - NOTES + @note This will force any other threads that uses the table to release it as soon as possible. - REQUIREMENTS - One must have a lock on LOCK_open ! - - RETURN: + @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) +int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use) { TABLE *table; char key[MAX_DBKEY_LENGTH]; @@ -892,26 +1024,35 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list) DBUG_ENTER("lock_table_name"); DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name)); - safe_mutex_assert_owner(&LOCK_open); - - key_length= (uint)(strmov(strmov(key, db) + 1, table_list->table_name) - - key) + 1; + key_length= create_table_def_key(thd, key, table_list, 0); - /* Only insert the table if we haven't insert it already */ - for (table=(TABLE*) hash_first(&open_cache, (byte*)key, key_length, &state); - table ; - table = (TABLE*) hash_next(&open_cache, (byte*)key, key_length, &state)) - if (table->in_use == thd) - DBUG_RETURN(0); + if (check_in_use) + { + /* Only insert the table if we haven't insert it already */ + for (table=(TABLE*) hash_first(&open_cache, (uchar*)key, + key_length, &state); + table ; + table = (TABLE*) hash_next(&open_cache,(uchar*) key, + key_length, &state)) + { + 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 (!(table= table_cache_insert_placeholder(thd, key, key_length))) DBUG_RETURN(-1); - table_list->table= table; + table_list->table=table; /* Return 1 if table is in use */ DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name, - RTFC_NO_FLAG))); + check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG))); } @@ -919,7 +1060,7 @@ void unlock_table_name(THD *thd, TABLE_LIST *table_list) { if (table_list->table) { - hash_delete(&open_cache, (byte*) table_list->table); + hash_delete(&open_cache, (uchar*) table_list->table); broadcast_refresh(); } } @@ -929,8 +1070,17 @@ static bool locked_named_table(THD *thd, TABLE_LIST *table_list) { for (; table_list ; table_list=table_list->next_local) { - if (table_list->table && table_is_used(table_list->table,0)) - return 1; + 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 } @@ -940,6 +1090,7 @@ 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)) @@ -949,30 +1100,29 @@ bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list) result=1; break; } - wait_for_refresh(thd); + 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 +/** + Lock all tables in list with a name lock. - SYNOPSIS - lock_table_names() - thd Thread handle - table_list Names of tables to 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 - NOTES + @note If you are just locking one table, you should use lock_and_wait_for_table_name(). - REQUIREMENTS - One must have a lock on LOCK_open when calling this - - RETURN + @retval 0 ok + @retval 1 Fatal error (end of memory ?) */ @@ -984,7 +1134,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) 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)) < 0) + 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 @@ -1001,34 +1151,139 @@ end: } -/* - Unlock all tables in list with a name lock +/** + Unlock all tables in list with a name lock. - SYNOPSIS - unlock_table_names() + @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 + + @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*) hash_first(&open_cache, key, + key_length, &state); + table ; + table= (TABLE*) 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; +} + +/** + 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) + (default 0, which will unlock all tables) - NOTES + @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. - RETURN + @retval 0 ok + @retval 1 Fatal error (end of memory ?) */ void unlock_table_names(THD *thd, TABLE_LIST *table_list, TABLE_LIST *last_table) { + 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(); + DBUG_VOID_RETURN; } @@ -1277,6 +1532,7 @@ void start_waiting_global_read_lock(THD *thd) if (unlikely(thd->global_read_lock)) DBUG_VOID_RETURN; (void) pthread_mutex_lock(&LOCK_global_read_lock); + DBUG_ASSERT(protect_against_global_read_lock); tmp= (!--protect_against_global_read_lock && (waiting_for_read_lock || global_read_lock_blocks_commit)); (void) pthread_mutex_unlock(&LOCK_global_read_lock); @@ -1318,14 +1574,9 @@ bool make_global_read_lock_block_commit(THD *thd) } -/* +/** Broadcast COND_refresh and COND_global_read_lock. - SYNOPSIS - broadcast_refresh() - void No parameters. - - DESCRIPTION Due to a bug in a threading library it could happen that a signal did not reach its target. A condition for this was that the same condition variable was used with different mutexes in @@ -1337,12 +1588,9 @@ bool make_global_read_lock_block_commit(THD *thd) in global read lock handling. But now it is necessary to signal both conditions at the same time. - NOTE + @note When signalling COND_global_read_lock within the global read lock handling, it is not necessary to also signal COND_refresh. - - RETURN - void */ void broadcast_refresh(void) @@ -1351,4 +1599,6 @@ void broadcast_refresh(void) VOID(pthread_cond_broadcast(&COND_global_read_lock)); } - +/** + @} (end of group Locking) +*/ |