diff options
Diffstat (limited to 'sql/mdl.cc')
-rw-r--r-- | sql/mdl.cc | 1342 |
1 files changed, 1342 insertions, 0 deletions
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; +} |