diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2013-08-14 12:48:50 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2013-08-14 12:48:50 +0400 |
commit | b7f9c894236c38cb40b5ca70e36d23497fcc01be (patch) | |
tree | 26b3f7e1e848893059f66e7e6bc7cc5a8c56c036 /sql/table_cache.cc | |
parent | b882a3e83eba1be324f4f898ae24e78244e2378d (diff) | |
download | mariadb-git-b7f9c894236c38cb40b5ca70e36d23497fcc01be.tar.gz |
MDEV-4702 - Reduce usage of LOCK_open
Following variables do not require LOCK_open protection anymore:
- table_def_cache (renamed to tdc_hash) is protected by rw-lock
LOCK_tdc_hash;
- table_def_shutdown_in_progress doesn't need LOCK_open protection;
- last_table_id use atomics;
- TABLE_SHARE::ref_count (renamed to TABLE_SHARE::tdc.ref_count)
is protected by TABLE_SHARE::tdc.LOCK_table_share;
- TABLE_SHARE::next, ::prev (renamed to tdc.next and tdc.prev),
oldest_unused_share, end_of_unused_share are protected by
LOCK_unused_shares;
- TABLE_SHARE::m_flush_tickets (renamed to tdc.m_flush_tickets)
is protected by TABLE_SHARE::tdc.LOCK_table_share;
- refresh_version (renamed to tdc_version) use atomics.
Diffstat (limited to 'sql/table_cache.cc')
-rw-r--r-- | sql/table_cache.cc | 1280 |
1 files changed, 1280 insertions, 0 deletions
diff --git a/sql/table_cache.cc b/sql/table_cache.cc new file mode 100644 index 00000000000..14e735ed3b2 --- /dev/null +++ b/sql/table_cache.cc @@ -0,0 +1,1280 @@ +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. + Copyright (c) 2010, 2011 Monty Program Ab + Copyright (C) 2013 Sergey Vojtovich and MariaDB Foundation + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + Table definition cache and table cache implementation. + + Table definition cache actions: + - add new TABLE_SHARE object to cache (tdc_acquire_share()) + - acquire TABLE_SHARE object from cache (tdc_acquire_share()) + - release TABLE_SHARE object to cache (tdc_release_share()) + - purge unused TABLE_SHARE objects from cache (tdc_purge()) + - remove TABLE_SHARE object from cache (tdc_remove_table()) + - get number of TABLE_SHARE objects in cache (tdc_records()) + + Table cache actions: + - add new TABLE object to cache (tc_add_table()) + - acquire TABLE object from cache (tc_acquire_table()) + - release TABLE object to cache (tc_release_table()) + - purge unused TABLE objects from cache (tc_purge()) + - purge unused TABLE objects of a table from cache (tdc_remove_table()) + - get number of TABLE objects in cache (tc_records()) + + Dependencies: + - intern_close_table(): frees TABLE object + - kill_delayed_threads_for_table() + - close_cached_tables(): flush tables on shutdown + - alloc_table_share() + - free_table_share() + + Table cache invariants: + - TABLE_SHARE::used_tables shall not contain objects with TABLE::in_use == 0 + - TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0 + - unused_tables shall not contain objects with TABLE::in_use != 0 + - cached TABLE object must be either in TABLE_SHARE::used_tables or in + TABLE_SHARE::free_tables +*/ + +#include "my_global.h" +#include "hash.h" +#include "table.h" +#include "sql_base.h" + +/** Configuration. */ +ulong tdc_size; /**< Table definition cache threshold for LRU eviction. */ +ulong tc_size; /**< Table cache threshold for LRU eviction. */ + +/** Data collections. */ +static HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ +/** Collection of unused TABLE_SHARE objects. */ +static TABLE_SHARE *oldest_unused_share, end_of_unused_share; +TABLE *unused_tables; /**< Collection of unused TABLE objects. */ + +static int64 tdc_version; /* Increments on each reload */ +static int64 last_table_id; +static bool tdc_inited; + +static uint tc_count; /**< Number of TABLE objects in table cache. */ + + +/** + Protects used and unused lists in the TABLE_SHARE object, + LRU lists of used TABLEs. + + tc_count + unused_tables + TABLE::next + TABLE::prev + TABLE_SHARE::tdc.free_tables + TABLE_SHARE::tdc.used_tables +*/ + +mysql_mutex_t LOCK_open; + + +/** + Protects unused shares list. + + TABLE_SHARE::tdc.prev + TABLE_SHARE::tdc.next + oldest_unused_share + end_of_unused_share +*/ + +static mysql_mutex_t LOCK_unused_shares; +static mysql_rwlock_t LOCK_tdc; /**< Protects tdc_hash. */ +static mysql_rwlock_t LOCK_flush; /**< Sync tc_purge() and tdc_remove_table(). */ +my_atomic_rwlock_t LOCK_tdc_atomics; /**< Protects tdc_version. */ + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key key_LOCK_open, key_LOCK_unused_shares, + key_TABLE_SHARE_LOCK_table_share; +static PSI_mutex_info all_tc_mutexes[]= +{ + { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }, + { &key_LOCK_unused_shares, "LOCK_unused_shares", PSI_FLAG_GLOBAL }, + { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share", 0 } +}; + +static PSI_rwlock_key key_rwlock_LOCK_tdc, key_rwlock_LOCK_flush; +static PSI_rwlock_info all_tc_rwlocks[]= +{ + { &key_rwlock_LOCK_tdc, "LOCK_tdc", PSI_FLAG_GLOBAL }, + { &key_rwlock_LOCK_flush, "LOCK_flush", PSI_FLAG_GLOBAL } +}; + + +static void init_tc_psi_keys(void) +{ + const char *category= "sql"; + int count; + + count= array_elements(all_tc_mutexes); + mysql_mutex_register(category, all_tc_mutexes, count); + + count= array_elements(all_tc_rwlocks); + mysql_rwlock_register(category, all_tc_rwlocks, count); +} +#endif + + +/* + Auxiliary routines for manipulating with per-share used/unused and + global unused lists of TABLE objects and tc_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. +*/ + + +/** + Get number of TABLE objects (used and unused) in table cache. + + @todo Protect tc_count so it is read atomically. +*/ + +uint tc_records(void) +{ + return tc_count; +} + + +/** + Free all unused TABLE objects. + + While locked: + - remove unused objects from TABLE_SHARE::tdc.free_tables lists + - reset unused_tables + - decrement tc_count + + While unlocked: + - free resources related to unused objects + + @note This is called by 'handle_manager' when one wants to + periodicly flush all not used tables. +*/ + +void tc_purge(void) +{ + mysql_mutex_lock(&LOCK_open); + if (unused_tables) + { + TABLE *table= unused_tables, *next; + unused_tables->prev->next= 0; + do + { + unused_tables->s->tdc.free_tables.remove(unused_tables); + tc_count--; + } while ((unused_tables= unused_tables->next)); + mysql_rwlock_rdlock(&LOCK_flush); + mysql_mutex_unlock(&LOCK_open); + + do + { + next= table->next; + intern_close_table(table); + } while ((table= next)); + mysql_rwlock_unlock(&LOCK_flush); + } + else + mysql_mutex_unlock(&LOCK_open); +} + + +/** + Verify consistency of used/unused lists (for debugging). +*/ + +#ifdef EXTRA_DEBUG +static void check_unused(THD *thd) +{ + uint count= 0, open_files= 0; + TABLE *cur_link, *start_link, *entry; + TABLE_SHARE *share; + TDC_iterator tdc_it; + + tdc_it.init(); + mysql_mutex_lock(&LOCK_open); + if ((start_link=cur_link=unused_tables)) + { + do + { + if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next) + { + DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ + return; /* purecov: inspected */ + } + } while (count++ < tc_count && + (cur_link=cur_link->next) != start_link); + if (cur_link != start_link) + { + DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */ + } + } + while ((share= tdc_it.next())) + { + TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); + while ((entry= it++)) + { + /* + We must not have TABLEs in the free list that have their file closed. + */ + DBUG_ASSERT(entry->db_stat && entry->file); + /* Merge children should be detached from a merge parent */ + if (entry->in_use) + { + DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */ + } + /* extra() may assume that in_use is set */ + entry->in_use= thd; + DBUG_ASSERT(!thd || !entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); + entry->in_use= 0; + + count--; + open_files++; + } + it.init(share->tdc.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++; + } + } + mysql_mutex_unlock(&LOCK_open); + tdc_it.deinit(); + if (count != 0) + { + DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ + count)); /* purecov: inspected */ + } +} +#else +#define check_unused(A) +#endif + + +/** + Remove unused TABLE object from table cache. + + @pre LOCK_open is locked, table is not used. + + While locked: + - remove object from TABLE_SHARE::tdc.free_tables + - remove object from unused_tables + + @note This is helper routine, supposed to be used by table cache + methods only. +*/ + +static void tc_remove_table(TABLE *table) +{ + mysql_mutex_assert_owner(&LOCK_open); + DBUG_ASSERT(!table->in_use); + /* Remove from per-share chain of unused TABLE objects. */ + table->s->tdc.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; + } + tc_count--; +} + + +/** + Add new TABLE object to table cache. + + @pre TABLE object is used by caller. + + Added object cannot be evicted or acquired. + + While locked: + - add object to TABLE_SHARE::tdc.used_tables + - increment tc_count + - evict LRU object from table cache if we reached threshold + + While unlocked: + - free evicted object +*/ + +void tc_add_table(THD *thd, TABLE *table) +{ + DBUG_ASSERT(table->in_use == thd); + mysql_mutex_lock(&LOCK_open); + table->s->tdc.used_tables.push_front(table); + tc_count++; + /* If we have too many TABLE instances around, try to get rid of them */ + if (tc_count > tc_size && unused_tables) + { + TABLE *purge_table= unused_tables; + tc_remove_table(purge_table); + mysql_rwlock_rdlock(&LOCK_flush); + mysql_mutex_unlock(&LOCK_open); + intern_close_table(purge_table); + mysql_rwlock_unlock(&LOCK_flush); + check_unused(thd); + } + else + mysql_mutex_unlock(&LOCK_open); +} + + +/** + Acquire TABLE object from table cache. + + @pre share must be protected against removal. + + Acquired object cannot be evicted or acquired again. + + While locked: + - pop object from TABLE_SHARE::tdc.free_tables() + - remove share protection + - remove object from unused_tables + - add object to TABLE_SHARE::tdc.used_tables() + - mark object used by thd + + @note share protection is kept if there are no unused objects. + + @return TABLE object, or NULL if no unused objects. +*/ + +static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) +{ + TABLE *table; + + mysql_mutex_lock(&LOCK_open); + if (!(table= share->tdc.free_tables.pop_front())) + { + mysql_mutex_unlock(&LOCK_open); + return 0; + } + mysql_rwlock_unlock(&LOCK_tdc); + DBUG_ASSERT(!table->in_use); + + /* Unlink table 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; + table->in_use= thd; + /* Add table to list of used tables for this share. */ + table->s->tdc.used_tables.push_front(table); + mysql_mutex_unlock(&LOCK_open); + + /* The ex-unused table must be fully functional. */ + DBUG_ASSERT(table->db_stat && table->file); + /* The children must be detached from the table. */ + DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); + check_unused(thd); + return table; +} + + +/** + Release TABLE object to table cache. + + @pre object is used by caller. + + Released object may be evicted or acquired again. + + While locked: + - mark object not in use by any thread + - remove object from TABLE_SHARE::tdc.used_tables + - if object is marked for purge, decrement tc_count + - add object to TABLE_SHARE::tdc.free_tables + - add object to unused_tables + - evict LRU object from table cache if we reached threshold + + While unlocked: + - free evicted/purged object + + @note Another thread may mark share for purge any moment (even + after version check). It means to-be-purged object may go to + unused lists. This other thread is expected to call tc_purge(), + which is synchronized with us on LOCK_open. + + @return + @retval true object purged + @retval false object released +*/ + +bool tc_release_table(TABLE *table) +{ + THD *thd __attribute__((unused))= table->in_use; + DBUG_ASSERT(table->in_use); + DBUG_ASSERT(table->file); + + mysql_mutex_lock(&LOCK_open); + /* Remove table from the list of tables used in this share. */ + table->s->tdc.used_tables.remove(table); + table->in_use= 0; + if (table->s->has_old_version() || table->needs_reopen() || !tdc_size) + { + tc_count--; + mysql_rwlock_rdlock(&LOCK_flush); + mysql_mutex_unlock(&LOCK_open); + intern_close_table(table); + mysql_rwlock_unlock(&LOCK_flush); + return true; + } + /* Add table to the list of unused TABLE objects for this share. */ + table->s->tdc.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; + /* + We free the least used table, not the subject table, + to keep the LRU order. + */ + if (tc_count > tc_size) + { + TABLE *purge_table= unused_tables; + tc_remove_table(purge_table); + mysql_rwlock_rdlock(&LOCK_flush); + mysql_mutex_unlock(&LOCK_open); + intern_close_table(purge_table); + mysql_rwlock_unlock(&LOCK_flush); + } + else + mysql_mutex_unlock(&LOCK_open); + check_unused(thd); + return false; +} + + +extern "C" uchar *tdc_key(const uchar *record, size_t *length, + my_bool not_used __attribute__((unused))) +{ + TABLE_SHARE *entry= (TABLE_SHARE*) record; + *length= entry->table_cache_key.length; + return (uchar*) entry->table_cache_key.str; +} + + +/** + Delete share from hash and free share object. + + @return + @retval 0 Success + @retval 1 Share is referenced +*/ + +static int tdc_delete_share_from_hash(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_delete_share_from_hash"); + mysql_rwlock_wrlock(&LOCK_tdc); + mysql_mutex_lock(&share->tdc.LOCK_table_share); + if (--share->tdc.ref_count) + { + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_rwlock_unlock(&LOCK_tdc); + DBUG_RETURN(1); + } + my_hash_delete(&tdc_hash, (uchar*) share); + /* Notify PFS early, while still locked. */ + PSI_CALL_release_table_share(share->m_psi); + share->m_psi= 0; + mysql_rwlock_unlock(&LOCK_tdc); + + if (share->tdc.m_flush_tickets.is_empty()) + { + /* No threads are waiting for this share to be flushed, destroy it. */ + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + free_table_share(share); + } + else + { + Wait_for_flush_list::Iterator it(share->tdc.m_flush_tickets); + Wait_for_flush *ticket; + while ((ticket= it++)) + (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED); + /* + If there are threads waiting for this share to be flushed, + the last one to receive the notification will destroy the + share. At this point the share is removed from the table + definition cache, so is OK to proceed here without waiting + for this thread to do the work. + */ + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + } + DBUG_RETURN(0); +} + + +/** + Initialize table definition cache. + + @retval 0 Success + @retval !0 Error +*/ + +int tdc_init(void) +{ + DBUG_ENTER("tdc_init"); +#ifdef HAVE_PSI_INTERFACE + init_tc_psi_keys(); +#endif + tdc_inited= true; + mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST); + mysql_mutex_record_order(&LOCK_active_mi, &LOCK_open); + /* + We must have LOCK_open before LOCK_global_system_variables because + LOCK_open is held while sql_plugin.cc::intern_sys_var_ptr() is called. + */ + mysql_mutex_record_order(&LOCK_open, &LOCK_global_system_variables); + mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, + MY_MUTEX_INIT_FAST); + mysql_rwlock_init(key_rwlock_LOCK_tdc, &LOCK_tdc); + mysql_rwlock_init(key_rwlock_LOCK_flush, &LOCK_flush); + my_atomic_rwlock_init(&LOCK_tdc_atomics); + oldest_unused_share= &end_of_unused_share; + end_of_unused_share.tdc.prev= &oldest_unused_share; + tdc_version= 1L; /* Increments on each reload */ + DBUG_RETURN(my_hash_init(&tdc_hash, &my_charset_bin, tdc_size, 0, 0, tdc_key, + 0, 0)); +} + + +/** + Notify table definition cache that process of shutting down server + has started so it has to keep number of TABLE and TABLE_SHARE objects + minimal in order to reduce number of references to pluggable engines. +*/ + +void tdc_start_shutdown(void) +{ + DBUG_ENTER("table_def_start_shutdown"); + if (tdc_inited) + { + /* + Ensure that TABLE and TABLE_SHARE objects which are created for + tables that are open during process of plugins' shutdown are + immediately released. This keeps number of references to engine + plugins minimal and allows shutdown to proceed smoothly. + */ + tdc_size= 0; + /* Free all cached but unused TABLEs and TABLE_SHAREs. */ + close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT); + } + DBUG_VOID_RETURN; +} + + +/** + Deinitialize table definition cache. +*/ + +void tdc_deinit(void) +{ + DBUG_ENTER("tdc_deinit"); + if (tdc_inited) + { + tdc_inited= false; + my_hash_free(&tdc_hash); + my_atomic_rwlock_destroy(&LOCK_tdc_atomics); + mysql_rwlock_destroy(&LOCK_flush); + mysql_rwlock_destroy(&LOCK_tdc); + mysql_mutex_destroy(&LOCK_unused_shares); + mysql_mutex_destroy(&LOCK_open); + } + DBUG_VOID_RETURN; +} + + +/** + Get number of cached table definitions. + + @return Number of cached table definitions +*/ + +ulong tdc_records(void) +{ + ulong records; + DBUG_ENTER("tdc_records"); + mysql_rwlock_rdlock(&LOCK_tdc); + records= tdc_hash.records; + mysql_rwlock_unlock(&LOCK_tdc); + DBUG_RETURN(records); +} + + +void tdc_purge(bool all) +{ + DBUG_ENTER("tdc_purge"); + for (;;) + { + TABLE_SHARE *share; + if (!all) + { + mysql_rwlock_rdlock(&LOCK_tdc); + if (tdc_hash.records <= tdc_size) + { + mysql_rwlock_unlock(&LOCK_tdc); + break; + } + mysql_rwlock_unlock(&LOCK_tdc); + } + + mysql_mutex_lock(&LOCK_unused_shares); + if (!oldest_unused_share->tdc.next) + { + mysql_mutex_unlock(&LOCK_unused_shares); + break; + } + + share= oldest_unused_share; + *share->tdc.prev= share->tdc.next; + share->tdc.next->tdc.prev= share->tdc.prev; + /* Concurrent thread may start using share again, reset prev and next. */ + share->tdc.prev= 0; + share->tdc.next= 0; + mysql_mutex_lock(&share->tdc.LOCK_table_share); + share->tdc.ref_count++; + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + + tdc_delete_share_from_hash(share); + } + DBUG_VOID_RETURN; +} + + +/** + Prepeare table share for use with table definition cache. +*/ + +void tdc_init_share(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_init_share"); + mysql_mutex_init(key_TABLE_SHARE_LOCK_table_share, + &share->tdc.LOCK_table_share, MY_MUTEX_INIT_FAST); + share->tdc.m_flush_tickets.empty(); + share->tdc.used_tables.empty(); + share->tdc.free_tables.empty(); + tdc_assign_new_table_id(share); + share->version= tdc_refresh_version(); + DBUG_VOID_RETURN; +} + + +/** + Release table definition cache specific resources of table share. +*/ + +void tdc_deinit_share(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_deinit_share"); + DBUG_ASSERT(share->tdc.ref_count == 0); + DBUG_ASSERT(share->tdc.m_flush_tickets.is_empty()); + DBUG_ASSERT(share->tdc.used_tables.is_empty()); + DBUG_ASSERT(share->tdc.free_tables.is_empty()); + mysql_mutex_destroy(&share->tdc.LOCK_table_share); + DBUG_VOID_RETURN; +} + + +/** + Lock table share. + + Find table share with given db.table_name in table definition cache. Return + locked table share if found. + + Locked table share means: + - table share is protected against removal from table definition cache + - no other thread can acquire/release table share + + Caller is expected to unlock table share with tdc_unlock_share(). + + @retval 0 Share not found + @retval !0 Pointer to locked table share +*/ + +TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + + DBUG_ENTER("tdc_lock_share"); + key_length= tdc_create_key(key, db, table_name); + + mysql_rwlock_rdlock(&LOCK_tdc); + TABLE_SHARE* share= (TABLE_SHARE*) my_hash_search(&tdc_hash, + (uchar*) key, key_length); + if (share && !share->error) + mysql_mutex_lock(&share->tdc.LOCK_table_share); + else + share= 0; + mysql_rwlock_unlock(&LOCK_tdc); + DBUG_RETURN(share); +} + + +/** + Unlock share locked by tdc_lock_share(). +*/ + +void tdc_unlock_share(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_unlock_share"); + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + DBUG_VOID_RETURN; +} + + +/* + Get TABLE_SHARE for a table. + + tdc_acquire_share() + thd Thread handle + table_list Table that should be opened + key Table cache key + key_length Length of key + flags operation: what to open table or view + + IMPLEMENTATION + Get a table definition from the table definition cache. + If it doesn't exist, create a new from the table definition file. + + RETURN + 0 Error + # Share for table +*/ + +TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, + const char *key, uint key_length, uint flags, + TABLE **out_table) +{ + TABLE_SHARE *share; + bool was_unused; + my_hash_value_type hash_value; + DBUG_ENTER("tdc_acquire_share"); + + hash_value= my_calc_hash(&tdc_hash, (uchar*) key, key_length); + + mysql_rwlock_rdlock(&LOCK_tdc); + share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value, + (uchar*) key, + key_length); + if (!share) + { + TABLE_SHARE *new_share; + mysql_rwlock_unlock(&LOCK_tdc); + + if (!(new_share= alloc_table_share(db, table_name, key, key_length))) + DBUG_RETURN(0); + new_share->error= OPEN_FRM_OPEN_ERROR; + + mysql_rwlock_wrlock(&LOCK_tdc); + share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value, + (uchar*) key, + key_length); + if (!share) + { + bool need_purge; + + share= new_share; + mysql_mutex_lock(&share->tdc.LOCK_table_share); + if (my_hash_insert(&tdc_hash, (uchar*) share)) + { + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_rwlock_unlock(&LOCK_tdc); + free_table_share(share); + DBUG_RETURN(0); + } + need_purge= tdc_hash.records > tdc_size; + mysql_rwlock_unlock(&LOCK_tdc); + + /* note that tdc_acquire_share() *always* uses discovery */ + open_table_def(thd, share, flags | GTS_USE_DISCOVERY); + share->tdc.ref_count++; + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + + if (share->error) + { + tdc_delete_share_from_hash(share); + DBUG_RETURN(0); + } + else if (need_purge) + tdc_purge(false); + if (out_table) + *out_table= 0; + share->m_psi= PSI_CALL_get_table_share(false, share); + goto end; + } + free_table_share(new_share); + } + + /* cannot force discovery of a cached share */ + DBUG_ASSERT(!(flags & GTS_FORCE_DISCOVERY)); + + if (out_table && (flags & GTS_TABLE)) + { + if ((*out_table= tc_acquire_table(thd, share))) + { + DBUG_ASSERT(!(flags & GTS_NOLOCK)); + DBUG_ASSERT(!share->tdc.prev && !share->tdc.next); + DBUG_ASSERT(!share->error); + DBUG_ASSERT(!share->is_view); + DBUG_RETURN(share); + } + } + + mysql_mutex_lock(&share->tdc.LOCK_table_share); + mysql_rwlock_unlock(&LOCK_tdc); + + /* + We found an existing table definition. Return it if we didn't get + an error when reading the table definition from file. + */ + if (share->error) + { + open_table_error(share, share->error, share->open_errno); + goto err; + } + + if (share->is_view && !(flags & GTS_VIEW)) + { + open_table_error(share, OPEN_FRM_NOT_A_TABLE, ENOENT); + goto err; + } + if (!share->is_view && !(flags & GTS_TABLE)) + { + open_table_error(share, OPEN_FRM_NOT_A_VIEW, ENOENT); + goto err; + } + + was_unused= !share->tdc.ref_count; + share->tdc.ref_count++; + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + if (was_unused) + { + mysql_mutex_lock(&LOCK_unused_shares); + if (share->tdc.prev) + { + /* + Share was not used before and it was in the old_unused_share list + Unlink share from this list + */ + DBUG_PRINT("info", ("Unlinking from not used list")); + *share->tdc.prev= share->tdc.next; + share->tdc.next->tdc.prev= share->tdc.prev; + share->tdc.next= 0; + share->tdc.prev= 0; + } + mysql_mutex_unlock(&LOCK_unused_shares); + } + DBUG_ASSERT(share->tdc.prev == 0 && share->tdc.next == 0); + +end: + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->tdc.ref_count)); + if (flags & GTS_NOLOCK) + { + tdc_release_share(share); + /* + if GTS_NOLOCK is requested, the returned share pointer cannot be used, + the share it points to may go away any moment. + But perhaps the caller is only interested to know whether a share or + table existed? + Let's return an invalid pointer here to catch dereferencing attempts. + */ + share= (TABLE_SHARE*) 1; + } + DBUG_RETURN(share); + +err: + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + DBUG_RETURN(0); +} + + +/** + Release table share acquired by tdc_acquire_share(). +*/ + +void tdc_release_share(TABLE_SHARE *share) +{ + DBUG_ENTER("tdc_release_share"); + + mysql_mutex_lock(&share->tdc.LOCK_table_share); + DBUG_PRINT("enter", + ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", + (ulong) share, share->db.str, share->table_name.str, + share->tdc.ref_count, share->version)); + DBUG_ASSERT(share->tdc.ref_count); + + if (share->tdc.ref_count > 1) + { + share->tdc.ref_count--; + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + DBUG_VOID_RETURN; + } + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + + mysql_mutex_lock(&LOCK_unused_shares); + mysql_mutex_lock(&share->tdc.LOCK_table_share); + if (share->has_old_version()) + { + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + tdc_delete_share_from_hash(share); + DBUG_VOID_RETURN; + } + if (--share->tdc.ref_count) + { + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + DBUG_VOID_RETURN; + } + /* Link share last in used_table_share list */ + DBUG_PRINT("info", ("moving share to unused list")); + DBUG_ASSERT(share->tdc.next == 0); + share->tdc.prev= end_of_unused_share.tdc.prev; + *end_of_unused_share.tdc.prev= share; + end_of_unused_share.tdc.prev= &share->tdc.next; + share->tdc.next= &end_of_unused_share; + mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + + /* Delete the least used share to preserve LRU order. */ + tdc_purge(false); + DBUG_VOID_RETURN; +} + + +static TABLE_SHARE *tdc_delete_share(const char *db, const char *table_name) +{ + TABLE_SHARE *share; + DBUG_ENTER("tdc_delete_share"); + + while ((share= tdc_lock_share(db, table_name))) + { + share->tdc.ref_count++; + if (share->tdc.ref_count > 1) + { + tdc_unlock_share(share); + DBUG_RETURN(share); + } + tdc_unlock_share(share); + + mysql_mutex_lock(&LOCK_unused_shares); + if (share->tdc.prev) + { + *share->tdc.prev= share->tdc.next; + share->tdc.next->tdc.prev= share->tdc.prev; + /* Concurrent thread may start using share again, reset prev and next. */ + share->tdc.prev= 0; + share->tdc.next= 0; + } + mysql_mutex_unlock(&LOCK_unused_shares); + + if (!tdc_delete_share_from_hash(share)) + break; + } + DBUG_RETURN(0); +} + + +/** + Remove all or some (depending on parameter) instances of TABLE and + TABLE_SHARE from the table definition cache. + + @param thd Thread context + @param remove_type Type of removal: + TDC_RT_REMOVE_ALL - remove all TABLE instances and + TABLE_SHARE instance. There + should be no used TABLE objects + and caller should have exclusive + metadata lock on the table. + TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances + except those that belong to + this thread. There should be + no TABLE objects used by other + threads and caller should have + exclusive metadata lock on the + table. + TDC_RT_REMOVE_UNUSED - remove all unused TABLE + instances (if there are no + used instances will also + remove TABLE_SHARE). + TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE - + remove all TABLE instances + except those that belong to + this thread, but don't mark + TABLE_SHARE as old. There + should be no TABLE objects + used by other threads and + caller should have exclusive + metadata lock on the table. + @param db Name of database + @param table_name Name of table + @param kill_delayed_threads If TRUE, kill INSERT DELAYED threads + + @note It assumes that table instances are already not used by any + (other) thread (this should be achieved by using meta-data locks). +*/ + +bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name, + bool kill_delayed_threads) +{ + TABLE *table; + TABLE_SHARE *share; + bool found= false; + DBUG_ENTER("tdc_remove_table"); + DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); + + DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + MDL_EXCLUSIVE)); + + if ((share= tdc_delete_share(db, table_name))) + { + I_P_List <TABLE, TABLE_share> purge_tables; + purge_tables.empty(); + + mysql_mutex_lock(&LOCK_open); + if (kill_delayed_threads) + kill_delayed_threads_for_table(share); + + TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); +#ifndef DBUG_OFF + if (remove_type == TDC_RT_REMOVE_ALL) + DBUG_ASSERT(share->tdc.used_tables.is_empty()); + else if (remove_type == TDC_RT_REMOVE_NOT_OWN) + { + TABLE_SHARE::TABLE_list::Iterator it2(share->tdc.used_tables); + while ((table= it2++)) + DBUG_ASSERT(table->in_use == thd); + } +#endif + /* + Set share's version to zero in order to ensure that it gets + automatically deleted once it is no longer referenced. + + Note that code in TABLE_SHARE::wait_for_old_version() assumes that + incrementing of refresh_version is followed by purge of unused table + shares. + */ + if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + share->version= 0; + + while ((table= it++)) + { + tc_remove_table(table); + purge_tables.push_front(table); + } + mysql_rwlock_rdlock(&LOCK_flush); + mysql_mutex_unlock(&LOCK_open); + + while ((table= purge_tables.pop_front())) + intern_close_table(table); + mysql_rwlock_unlock(&LOCK_flush); + + check_unused(thd); + tdc_release_share(share); + + /* Wait for concurrent threads to free unused objects. */ + mysql_rwlock_wrlock(&LOCK_flush); + mysql_rwlock_unlock(&LOCK_flush); + + found= true; + } + DBUG_ASSERT(found || remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); + DBUG_RETURN(found); +} + + +/** + Check if table's share is being removed from the table definition + cache and, if yes, wait until the flush is complete. + + @param thd Thread context. + @param table_list Table which share should be checked. + @param timeout Timeout for waiting. + @param deadlock_weight Weight of this wait for deadlock detector. + + @retval 0 Success. Share is up to date or has been flushed. + @retval 1 Error (OOM, was killed, the wait resulted + in a deadlock or timeout). Reported. +*/ + +int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, + ulong wait_timeout, uint deadlock_weight) +{ + TABLE_SHARE *share; + int res= FALSE; + + if ((share= tdc_lock_share(db, table_name))) + { + if (share->has_old_version()) + { + struct timespec abstime; + set_timespec(abstime, wait_timeout); + res= share->wait_for_old_version(thd, &abstime, deadlock_weight); + } + else + tdc_unlock_share(share); + } + return res; +} + + +ulong tdc_refresh_version(void) +{ + my_atomic_rwlock_rdlock(&LOCK_tdc_atomics); + ulong v= my_atomic_load64(&tdc_version); + my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics); + return v; +} + + +void tdc_increment_refresh_version(void) +{ + my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); +#ifndef DBUG_OFF + ulong v= my_atomic_add64(&tdc_version, 1); +#else + my_atomic_add64(&tdc_version, 1); +#endif + my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics); + DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", v)); +} + + +/** + Initialize table definition cache iterator. +*/ + +void TDC_iterator::init(void) +{ + DBUG_ENTER("TDC_iterator::init"); + idx= 0; + mysql_rwlock_rdlock(&LOCK_tdc); + DBUG_VOID_RETURN; +} + + +/** + Deinitialize table definition cache iterator. +*/ + +void TDC_iterator::deinit(void) +{ + DBUG_ENTER("TDC_iterator::deinit"); + mysql_rwlock_unlock(&LOCK_tdc); + DBUG_VOID_RETURN; +} + + +/** + Get next TABLE_SHARE object from table definition cache. + + Object is protected against removal from table definition cache. + + @note Returned TABLE_SHARE is not guaranteed to be fully initialized: + tdc_acquire_share() added new share, but didn't open it yet. If caller + needs fully initializer share, it must lock table share mutex. +*/ + +TABLE_SHARE *TDC_iterator::next(void) +{ + TABLE_SHARE *share= 0; + DBUG_ENTER("TDC_iterator::next"); + if (idx < tdc_hash.records) + { + share= (TABLE_SHARE*) my_hash_element(&tdc_hash, idx); + idx++; + } + DBUG_RETURN(share); +} + + +/* + Function to assign a new table map id to a table share. + + PARAMETERS + + share - Pointer to table share structure + + DESCRIPTION + + We are intentionally not checking that share->mutex is locked + since this function should only be called when opening a table + share and before it is entered into the table definition cache + (meaning that it cannot be fetched by another thread, even + accidentally). + + PRE-CONDITION(S) + + share is non-NULL + last_table_id_lock initialized (tdc_inited) + + POST-CONDITION(S) + + share->table_map_id is given a value that with a high certainty is + not used by any other table (the only case where a table id can be + reused is on wrap-around, which means more than 4 billion table + share opens have been executed while one table was open all the + time). + + share->table_map_id is not ~0UL. +*/ + +void tdc_assign_new_table_id(TABLE_SHARE *share) +{ + ulong tid; + DBUG_ENTER("assign_new_table_id"); + DBUG_ASSERT(share); + DBUG_ASSERT(tdc_inited); + + /* + There is one reserved number that cannot be used. Remember to + change this when 6-byte global table id's are introduced. + */ + do + { + my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); + tid= my_atomic_add64(&last_table_id, 1); + my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics); + } while (unlikely(tid == ~0UL)); + + share->table_map_id= tid; + DBUG_PRINT("info", ("table_id= %lu", share->table_map_id)); + DBUG_VOID_RETURN; +} |