diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2014-12-28 19:42:17 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2014-12-28 19:46:18 +0400 |
commit | 6dbc48ca79e5fdd8d4022b00b862e08a4198155b (patch) | |
tree | 1904e477f09dd958af2b6696d4b7c8dbb0a1772d /sql/table_cache.cc | |
parent | 8883c54ac08a555bc7d9b09395f49893ad4d80b5 (diff) | |
download | mariadb-git-6dbc48ca79e5fdd8d4022b00b862e08a4198155b.tar.gz |
MDEV-7324 - Lock-free hash for table definition cache
Diffstat (limited to 'sql/table_cache.cc')
-rw-r--r-- | sql/table_cache.cc | 944 |
1 files changed, 444 insertions, 500 deletions
diff --git a/sql/table_cache.cc b/sql/table_cache.cc index 9e2246a5846..f12c031f91a 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -49,18 +49,22 @@ */ #include "my_global.h" -#include "hash.h" +#include "lf.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. */ +static LF_HASH tdc_hash; /**< Collection of TABLE_SHARE objects. */ /** Collection of unused TABLE_SHARE objects. */ -static TABLE_SHARE *oldest_unused_share, end_of_unused_share; +I_P_List <TDC_element, + I_P_List_adapter<TDC_element, &TDC_element::next, &TDC_element::prev>, + I_P_List_null_counter, + I_P_List_fast_push_back<TDC_element> > unused_shares; static int64 tdc_version; /* Increments on each reload */ static int64 last_table_id; @@ -72,32 +76,23 @@ static int32 tc_count; /**< Number of TABLE objects in table cache. */ /** Protects unused shares list. - TABLE_SHARE::tdc.prev - TABLE_SHARE::tdc.next - oldest_unused_share - end_of_unused_share + TDC_element::prev + TDC_element::next + unused_shares */ static mysql_mutex_t LOCK_unused_shares; -static mysql_rwlock_t LOCK_tdc; /**< Protects tdc_hash. */ my_atomic_rwlock_t LOCK_tdc_atomics; /**< Protects tdc_version. */ #ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; +PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; static PSI_mutex_info all_tc_mutexes[]= { { &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; -static PSI_rwlock_info all_tc_rwlocks[]= -{ - { &key_rwlock_LOCK_tdc, "LOCK_tdc", PSI_FLAG_GLOBAL } -}; - - -static PSI_cond_key key_TABLE_SHARE_COND_release; +PSI_cond_key key_TABLE_SHARE_COND_release; static PSI_cond_info all_tc_conds[]= { { &key_TABLE_SHARE_COND_release, "TABLE_SHARE::tdc.COND_release", 0 } @@ -112,15 +107,19 @@ static void init_tc_psi_keys(void) 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); - count= array_elements(all_tc_conds); mysql_cond_register(category, all_tc_conds, count); } #endif +static int fix_thd_pins(THD *thd) +{ + return thd->tdc_hash_pins ? 0 : + (thd->tdc_hash_pins= lf_hash_get_pins(&tdc_hash)) == 0; +} + + /* Auxiliary routines for manipulating with per-share all/unused lists and tc_count counter. @@ -157,34 +156,7 @@ static void tc_remove_table(TABLE *table) my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); my_atomic_add32_explicit(&tc_count, -1, MY_MEMORY_ORDER_RELAXED); my_atomic_rwlock_wrunlock(&LOCK_tdc_atomics); - table->s->tdc.all_tables.remove(table); -} - - -/** - Wait for MDL deadlock detector to complete traversing tdc.all_tables. - - Must be called before updating TABLE_SHARE::tdc.all_tables. -*/ - -static void tc_wait_for_mdl_deadlock_detector(TABLE_SHARE *share) -{ - while (share->tdc.all_tables_refs) - mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share); -} - - -/** - Get last element of tdc.free_tables. -*/ - -static TABLE *tc_free_tables_back(TABLE_SHARE *share) -{ - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); - TABLE *entry, *last= 0; - while ((entry= it++)) - last= entry; - return last; + table->s->tdc->all_tables.remove(table); } @@ -203,31 +175,39 @@ static TABLE *tc_free_tables_back(TABLE_SHARE *share) periodicly flush all not used tables. */ -void tc_purge(bool mark_flushed) +struct tc_purge_arg +{ + TDC_element::TABLE_list purge_tables; + bool mark_flushed; +}; + + +static my_bool tc_purge_callback(TDC_element *element, tc_purge_arg *arg) { - TABLE_SHARE *share; TABLE *table; - TDC_iterator tdc_it; - TABLE_SHARE::TABLE_list purge_tables; - tdc_it.init(); - while ((share= tdc_it.next())) + mysql_mutex_lock(&element->LOCK_table_share); + element->wait_for_mdl_deadlock_detector(); + if (arg->mark_flushed) + element->flushed= true; + while ((table= element->free_tables.pop_front())) { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(share); - - if (mark_flushed) - share->tdc.flushed= true; - while ((table= share->tdc.free_tables.pop_front())) - { - tc_remove_table(table); - purge_tables.push_front(table); - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + tc_remove_table(table); + arg->purge_tables.push_front(table); } - tdc_it.deinit(); + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} - while ((table= purge_tables.pop_front())) + +void tc_purge(bool mark_flushed) +{ + tc_purge_arg argument; + TABLE *table; + + argument.mark_flushed= mark_flushed; + tdc_iterate(0, (my_hash_walk_action) tc_purge_callback, &argument); + while ((table= argument.purge_tables.pop_front())) intern_close_table(table); } @@ -248,14 +228,38 @@ void tc_purge(bool mark_flushed) - free evicted object */ +struct tc_add_table_arg +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + ulonglong purge_time; +}; + + +my_bool tc_add_table_callback(TDC_element *element, tc_add_table_arg *arg) +{ + TABLE *table; + + mysql_mutex_lock(&element->LOCK_table_share); + if ((table= element->free_tables_back()) && table->tc_time < arg->purge_time) + { + memcpy(arg->key, element->m_key, element->m_key_length); + arg->key_length= element->m_key_length; + arg->purge_time= table->tc_time; + } + mysql_mutex_unlock(&element->LOCK_table_share); + return FALSE; +} + + void tc_add_table(THD *thd, TABLE *table) { bool need_purge; DBUG_ASSERT(table->in_use == thd); - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(table->s); - table->s->tdc.all_tables.push_front(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + table->s->tdc->wait_for_mdl_deadlock_detector(); + table->s->tdc->all_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); /* If we have too many TABLE instances around, try to get rid of them */ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); @@ -265,89 +269,45 @@ void tc_add_table(THD *thd, TABLE *table) if (need_purge) { - TABLE_SHARE *purge_share= 0; - TABLE_SHARE *share; - TABLE *entry; - ulonglong UNINIT_VAR(purge_time); - TDC_iterator tdc_it; - - tdc_it.init(); - while ((share= tdc_it.next())) - { - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if ((entry= tc_free_tables_back(share)) && - (!purge_share || entry->tc_time < purge_time)) - { - purge_share= share; - purge_time= entry->tc_time; - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - } + tc_add_table_arg argument; + argument.purge_time= ULONGLONG_MAX; + tdc_iterate(thd, (my_hash_walk_action) tc_add_table_callback, &argument); - if (purge_share) + if (argument.purge_time != ULONGLONG_MAX) { - mysql_mutex_lock(&purge_share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(purge_share); - tdc_it.deinit(); - /* - It may happen that oldest table was acquired meanwhile. In this case - just go ahead, number of objects in table cache will normalize - eventually. - */ - if ((entry= tc_free_tables_back(purge_share)) && - entry->tc_time == purge_time) + TDC_element *element= (TDC_element*) lf_hash_search(&tdc_hash, + thd->tdc_hash_pins, + argument.key, + argument.key_length); + if (element) { - entry->s->tdc.free_tables.remove(entry); - tc_remove_table(entry); - mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); - intern_close_table(entry); + TABLE *entry; + mysql_mutex_lock(&element->LOCK_table_share); + lf_hash_search_unpin(thd->tdc_hash_pins); + element->wait_for_mdl_deadlock_detector(); + + /* + It may happen that oldest table was acquired meanwhile. In this case + just go ahead, number of objects in table cache will normalize + eventually. + */ + if ((entry= element->free_tables_back()) && + entry->tc_time == argument.purge_time) + { + element->free_tables.remove(entry); + tc_remove_table(entry); + mysql_mutex_unlock(&element->LOCK_table_share); + intern_close_table(entry); + } + else + mysql_mutex_unlock(&element->LOCK_table_share); } - else - mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); } - else - tdc_it.deinit(); } } /** - 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 - - While unlocked: - - mark object used by thd - - @return TABLE object, or NULL if no unused objects. -*/ - -static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) -{ - TABLE *table; - - mysql_mutex_lock(&share->tdc.LOCK_table_share); - table= share->tdc.free_tables.pop_front(); - if (table) - { - DBUG_ASSERT(!table->in_use); - table->in_use= thd; - /* 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)); - } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); - return table; -} - - -/** Release TABLE object to table cache. @pre object is used by caller. @@ -380,14 +340,14 @@ bool tc_release_table(TABLE *table) if (table->needs_reopen() || tc_records() > tc_size) { - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); goto purge; } table->tc_time= my_interval_timer(); - mysql_mutex_lock(&table->s->tdc.LOCK_table_share); - if (table->s->tdc.flushed) + mysql_mutex_lock(&table->s->tdc->LOCK_table_share); + if (table->s->tdc->flushed) goto purge; /* in_use doesn't really need mutex protection, but must be reset after @@ -397,88 +357,76 @@ bool tc_release_table(TABLE *table) */ table->in_use= 0; /* Add table to the list of unused TABLE objects for this share. */ - table->s->tdc.free_tables.push_front(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + table->s->tdc->free_tables.push_front(table); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); return false; purge: - tc_wait_for_mdl_deadlock_detector(table->s); + table->s->tdc->wait_for_mdl_deadlock_detector(); tc_remove_table(table); - mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); + mysql_mutex_unlock(&table->s->tdc->LOCK_table_share); table->in_use= 0; intern_close_table(table); return true; } -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) +static void tdc_delete_share_from_hash(TDC_element *element) { + THD *thd= current_thd; + LF_PINS *pins; + 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_cond_broadcast(&share->tdc.COND_release); - 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. */ + + mysql_mutex_assert_owner(&element->LOCK_table_share); + share= element->share; + DBUG_ASSERT(share); + element->share= 0; 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 + if (!element->m_flush_tickets.is_empty()) { - Wait_for_flush_list::Iterator it(share->tdc.m_flush_tickets); + Wait_for_flush_list::Iterator it(element->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); + + do + { + mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); + } while (!element->m_flush_tickets.is_empty()); } - DBUG_RETURN(0); + + mysql_mutex_unlock(&element->LOCK_table_share); + + if (thd) + { + fix_thd_pins(thd); + pins= thd->tdc_hash_pins; + } + else + pins= lf_hash_get_pins(&tdc_hash); + + DBUG_ASSERT(pins); // What can we do about it? + element->assert_clean_share(); + lf_hash_delete(&tdc_hash, pins, element->m_key, element->m_key_length); + if (!thd) + lf_hash_put_pins(pins); + free_table_share(share); + DBUG_VOID_RETURN; } /** Initialize table definition cache. - - @retval 0 Success - @retval !0 Error */ -int tdc_init(void) +void tdc_init(void) { DBUG_ENTER("tdc_init"); #ifdef HAVE_PSI_INTERFACE @@ -487,13 +435,15 @@ int tdc_init(void) tdc_inited= true; mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, MY_MUTEX_INIT_FAST); - mysql_rwlock_init(key_rwlock_LOCK_tdc, &LOCK_tdc); 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)); + lf_hash_init(&tdc_hash, sizeof(TDC_element), LF_HASH_UNIQUE, 0, 0, + (my_hash_get_key) TDC_element::key, + &my_charset_bin); + tdc_hash.alloc.constructor= TDC_element::lf_alloc_constructor; + tdc_hash.alloc.destructor= TDC_element::lf_alloc_destructor; + tdc_hash.element_size= offsetof(TDC_element, version); + DBUG_VOID_RETURN; } @@ -533,9 +483,8 @@ void tdc_deinit(void) if (tdc_inited) { tdc_inited= false; - my_hash_free(&tdc_hash); + lf_hash_destroy(&tdc_hash); my_atomic_rwlock_destroy(&LOCK_tdc_atomics); - mysql_rwlock_destroy(&LOCK_tdc); mysql_mutex_destroy(&LOCK_unused_shares); } DBUG_VOID_RETURN; @@ -550,12 +499,7 @@ void tdc_deinit(void) 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); + return my_atomic_load32_explicit(&tdc_hash.count, MY_MEMORY_ORDER_RELAXED); } @@ -564,72 +508,34 @@ void tdc_purge(bool all) DBUG_ENTER("tdc_purge"); while (all || tdc_records() > tdc_size) { - TABLE_SHARE *share; + TDC_element *element; mysql_mutex_lock(&LOCK_unused_shares); - if (!oldest_unused_share->tdc.next) + if (!(element= unused_shares.pop_front())) { 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); + element->prev= 0; + element->next= 0; + mysql_mutex_lock(&element->LOCK_table_share); + if (element->ref_count) + { + mysql_mutex_unlock(&element->LOCK_table_share); + mysql_mutex_unlock(&LOCK_unused_shares); + continue; + } mysql_mutex_unlock(&LOCK_unused_shares); - tdc_delete_share_from_hash(share); + tdc_delete_share_from_hash(element); } 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); - mysql_cond_init(key_TABLE_SHARE_COND_release, &share->tdc.COND_release, 0); - share->tdc.m_flush_tickets.empty(); - share->tdc.all_tables.empty(); - share->tdc.free_tables.empty(); - tdc_assign_new_table_id(share); - share->tdc.version= tdc_refresh_version(); - share->tdc.flushed= false; - share->tdc.all_tables_refs= 0; - 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.all_tables.is_empty()); - DBUG_ASSERT(share->tdc.free_tables.is_empty()); - DBUG_ASSERT(share->tdc.all_tables_refs == 0); - mysql_cond_destroy(&share->tdc.COND_release); - 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 @@ -641,27 +547,35 @@ void tdc_deinit_share(TABLE_SHARE *share) Caller is expected to unlock table share with tdc_unlock_share(). - @retval 0 Share not found - @retval !0 Pointer to locked table share + @retval 0 Share not found + @retval MY_ERRPTR OOM + @retval ptr Pointer to locked table share */ -TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) +TDC_element *tdc_lock_share(THD *thd, const char *db, const char *table_name) { + TDC_element *element; char key[MAX_DBKEY_LENGTH]; - uint key_length; DBUG_ENTER("tdc_lock_share"); - key_length= tdc_create_key(key, db, table_name); + if (fix_thd_pins(thd)) + DBUG_RETURN((TDC_element*) MY_ERRPTR); - 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); + element= (TDC_element *) lf_hash_search(&tdc_hash, thd->tdc_hash_pins, + (uchar*) key, + tdc_create_key(key, db, table_name)); + if (element) + { + mysql_mutex_lock(&element->LOCK_table_share); + if (!element->share || element->share->error) + { + mysql_mutex_unlock(&element->LOCK_table_share); + element= 0; + } + lf_hash_search_unpin(thd->tdc_hash_pins); + } + + DBUG_RETURN(element); } @@ -669,10 +583,10 @@ TABLE_SHARE *tdc_lock_share(const char *db, const char *table_name) Unlock share locked by tdc_lock_share(). */ -void tdc_unlock_share(TABLE_SHARE *share) +void tdc_unlock_share(TDC_element *element) { DBUG_ENTER("tdc_unlock_share"); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); DBUG_VOID_RETURN; } @@ -702,60 +616,60 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, TABLE **out_table) { TABLE_SHARE *share; + TDC_element *element; bool was_unused; DBUG_ENTER("tdc_acquire_share"); - mysql_rwlock_rdlock(&LOCK_tdc); - share= (TABLE_SHARE*) my_hash_search_using_hash_value(&tdc_hash, hash_value, - (uchar*) key, - key_length); - if (!share) + if (fix_thd_pins(thd)) + DBUG_RETURN(0); + +retry: + while (!(element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, + thd->tdc_hash_pins, hash_value, (uchar*) key, key_length))) { - TABLE_SHARE *new_share; - mysql_rwlock_unlock(&LOCK_tdc); + TDC_element tmp(key, key_length); + int res= lf_hash_insert(&tdc_hash, thd->tdc_hash_pins, (uchar*) &tmp); - if (!(new_share= alloc_table_share(db, table_name, key, key_length))) + if (res == -1) DBUG_RETURN(0); - new_share->error= OPEN_FRM_OPEN_ERROR; + else if (res == 1) + continue; - 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; + element= (TDC_element*) lf_hash_search_using_hash_value(&tdc_hash, + thd->tdc_hash_pins, hash_value, (uchar*) key, key_length); + lf_hash_search_unpin(thd->tdc_hash_pins); + DBUG_ASSERT(element); + element->assert_clean_share(); - 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); + if (!(share= alloc_table_share(db, table_name, key, key_length))) + { + lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); + DBUG_RETURN(0); + } - /* 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); + /* note that tdc_acquire_share() *always* uses discovery */ + open_table_def(thd, share, flags | GTS_USE_DISCOVERY); - 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; + if (share->error) + { + free_table_share(share); + lf_hash_delete(&tdc_hash, thd->tdc_hash_pins, key, key_length); + DBUG_RETURN(0); } - free_table_share(new_share); + + mysql_mutex_lock(&element->LOCK_table_share); + element->share= share; + share->tdc= element; + element->ref_count++; + element->version= tdc_refresh_version(); + element->flushed= false; + mysql_mutex_unlock(&element->LOCK_table_share); + + tdc_purge(false); + if (out_table) + *out_table= 0; + share->m_psi= PSI_CALL_get_table_share(false, share); + goto end; } /* cannot force discovery of a cached share */ @@ -763,18 +677,25 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, if (out_table && (flags & GTS_TABLE)) { - if ((*out_table= tc_acquire_table(thd, share))) + if ((*out_table= element->acquire_table(thd))) { - mysql_rwlock_unlock(&LOCK_tdc); + lf_hash_search_unpin(thd->tdc_hash_pins); DBUG_ASSERT(!(flags & GTS_NOLOCK)); - DBUG_ASSERT(!share->error); - DBUG_ASSERT(!share->is_view); - DBUG_RETURN(share); + DBUG_ASSERT(element->share); + DBUG_ASSERT(!element->share->error); + DBUG_ASSERT(!element->share->is_view); + DBUG_RETURN(element->share); } } - mysql_mutex_lock(&share->tdc.LOCK_table_share); - mysql_rwlock_unlock(&LOCK_tdc); + mysql_mutex_lock(&element->LOCK_table_share); + if (!(share= element->share)) + { + mysql_mutex_unlock(&element->LOCK_table_share); + lf_hash_search_unpin(thd->tdc_hash_pins); + goto retry; + } + lf_hash_search_unpin(thd->tdc_hash_pins); /* We found an existing table definition. Return it if we didn't get @@ -797,30 +718,29 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name, goto err; } - was_unused= !share->tdc.ref_count; - share->tdc.ref_count++; - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + was_unused= !element->ref_count; + element->ref_count++; + mysql_mutex_unlock(&element->LOCK_table_share); if (was_unused) { mysql_mutex_lock(&LOCK_unused_shares); - if (share->tdc.prev) + if (element->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; + unused_shares.remove(element); + element->next= 0; + element->prev= 0; } mysql_mutex_unlock(&LOCK_unused_shares); } end: DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", - (ulong) share, share->tdc.ref_count)); + (ulong) share, share->tdc->ref_count)); if (flags & GTS_NOLOCK) { tdc_release_share(share); @@ -836,7 +756,7 @@ end: DBUG_RETURN(share); err: - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); DBUG_RETURN(0); } @@ -849,87 +769,47 @@ void tdc_release_share(TABLE_SHARE *share) { DBUG_ENTER("tdc_release_share"); - mysql_mutex_lock(&share->tdc.LOCK_table_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->tdc.version)); - DBUG_ASSERT(share->tdc.ref_count); + share->tdc->ref_count, share->tdc->version)); + DBUG_ASSERT(share->tdc->ref_count); - if (share->tdc.ref_count > 1) + if (share->tdc->ref_count > 1) { - share->tdc.ref_count--; + share->tdc->ref_count--; if (!share->is_view) - mysql_cond_broadcast(&share->tdc.COND_release); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_cond_broadcast(&share->tdc->COND_release); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); DBUG_VOID_RETURN; } - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + mysql_mutex_unlock(&share->tdc->LOCK_table_share); mysql_mutex_lock(&LOCK_unused_shares); - mysql_mutex_lock(&share->tdc.LOCK_table_share); - if (share->tdc.flushed) + mysql_mutex_lock(&share->tdc->LOCK_table_share); + if (--share->tdc->ref_count) { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + 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) + if (share->tdc->flushed || tdc_records() > tdc_size) { - mysql_mutex_unlock(&share->tdc.LOCK_table_share); mysql_mutex_unlock(&LOCK_unused_shares); + tdc_delete_share_from_hash(share->tdc); 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); + DBUG_ASSERT(share->tdc->next == 0); + unused_shares.push_back(share->tdc); + 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. @@ -973,9 +853,10 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, const char *db, const char *table_name, bool kill_delayed_threads) { + I_P_List <TABLE, TABLE_share> purge_tables; TABLE *table; - TABLE_SHARE *share; - bool found= false; + TDC_element *element; + uint my_refs= 1; DBUG_ENTER("tdc_remove_table"); DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type)); @@ -983,83 +864,102 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, 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; - uint my_refs= 1; - mysql_mutex_lock(&share->tdc.LOCK_table_share); - tc_wait_for_mdl_deadlock_detector(share); - /* - Mark share flushed in order to ensure that it gets - automatically deleted once it is no longer referenced. + mysql_mutex_lock(&LOCK_unused_shares); + if (!(element= tdc_lock_share(thd, db, table_name))) + { + mysql_mutex_unlock(&LOCK_unused_shares); + DBUG_ASSERT(remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); + DBUG_RETURN(false); + } - Note that code in TABLE_SHARE::wait_for_old_version() assumes that - marking share flushed is followed by purge of unused table - shares. - */ - if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) - share->tdc.flushed= true; + DBUG_ASSERT(element != MY_ERRPTR); // What can we do about it? - while ((table= share->tdc.free_tables.pop_front())) + if (!element->ref_count) + { + if (element->prev) { - tc_remove_table(table); - purge_tables.push_front(table); + unused_shares.remove(element); + element->prev= 0; + element->next= 0; } - if (kill_delayed_threads) - kill_delayed_threads_for_table(share); + mysql_mutex_unlock(&LOCK_unused_shares); - if (remove_type == TDC_RT_REMOVE_NOT_OWN || - remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) - { - TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables); - while ((table= it++)) - { - my_refs++; - DBUG_ASSERT(table->in_use == thd); - } - } - DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + tdc_delete_share_from_hash(element); + DBUG_RETURN(true); + } + mysql_mutex_unlock(&LOCK_unused_shares); - while ((table= purge_tables.pop_front())) - intern_close_table(table); + element->ref_count++; - if (remove_type != TDC_RT_REMOVE_UNUSED) + element->wait_for_mdl_deadlock_detector(); + /* + Mark share flushed 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 + marking share flushed is followed by purge of unused table + shares. + */ + if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + element->flushed= true; + + while ((table= element->free_tables.pop_front())) + { + tc_remove_table(table); + purge_tables.push_front(table); + } + if (kill_delayed_threads) + kill_delayed_threads_for_table(element); + + if (remove_type == TDC_RT_REMOVE_NOT_OWN || + remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) + { + TDC_element::All_share_tables_list::Iterator it(element->all_tables); + while ((table= it++)) { - /* - Even though current thread holds exclusive metadata lock on this share - (asserted above), concurrent FLUSH TABLES threads may be in process of - closing unused table instances belonging to this share. E.g.: - thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front(); - thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table); - thr2 (ALTER TABLE): tdc_remove_table(); - thr1 (FLUSH TABLES): intern_close_table(table); - - Current remove type assumes that all table instances (except for those - that are owned by current thread) must be closed before - thd_remove_table() returns. Wait for such tables now. - - intern_close_table() decrements ref_count and signals COND_release. When - ref_count drops down to number of references owned by current thread - waiting is completed. - - Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here - because it waits for all table instances, whereas we have to wait only - for those that are not owned by current thread. - */ - mysql_mutex_lock(&share->tdc.LOCK_table_share); - while (share->tdc.ref_count > my_refs) - mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share); - mysql_mutex_unlock(&share->tdc.LOCK_table_share); + my_refs++; + DBUG_ASSERT(table->in_use == thd); } + } + DBUG_ASSERT(element->all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); + mysql_mutex_unlock(&element->LOCK_table_share); - tdc_release_share(share); + while ((table= purge_tables.pop_front())) + intern_close_table(table); - found= true; + if (remove_type != TDC_RT_REMOVE_UNUSED) + { + /* + Even though current thread holds exclusive metadata lock on this share + (asserted above), concurrent FLUSH TABLES threads may be in process of + closing unused table instances belonging to this share. E.g.: + thr1 (FLUSH TABLES): table= share->tdc.free_tables.pop_front(); + thr1 (FLUSH TABLES): share->tdc.all_tables.remove(table); + thr2 (ALTER TABLE): tdc_remove_table(); + thr1 (FLUSH TABLES): intern_close_table(table); + + Current remove type assumes that all table instances (except for those + that are owned by current thread) must be closed before + thd_remove_table() returns. Wait for such tables now. + + intern_close_table() decrements ref_count and signals COND_release. When + ref_count drops down to number of references owned by current thread + waiting is completed. + + Unfortunately TABLE_SHARE::wait_for_old_version() cannot be used here + because it waits for all table instances, whereas we have to wait only + for those that are not owned by current thread. + */ + mysql_mutex_lock(&element->LOCK_table_share); + while (element->ref_count > my_refs) + mysql_cond_wait(&element->COND_release, &element->LOCK_table_share); + mysql_mutex_unlock(&element->LOCK_table_share); } - DBUG_ASSERT(found || remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE); - DBUG_RETURN(found); + + tdc_release_share(element->share); + + DBUG_RETURN(true); } @@ -1081,21 +981,20 @@ int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name, ulong wait_timeout, uint deadlock_weight, ulong refresh_version) { - TABLE_SHARE *share; - int res= FALSE; + TDC_element *element; - if ((share= tdc_lock_share(db, table_name))) + if (!(element= tdc_lock_share(thd, db, table_name))) + return FALSE; + else if (element == MY_ERRPTR) + return TRUE; + else if (element->flushed && refresh_version > element->version) { - if (share->tdc.flushed && refresh_version > share->tdc.version) - { - struct timespec abstime; - set_timespec(abstime, wait_timeout); - res= share->wait_for_old_version(thd, &abstime, deadlock_weight); - } - else - tdc_unlock_share(share); + struct timespec abstime; + set_timespec(abstime, wait_timeout); + return element->share->wait_for_old_version(thd, &abstime, deadlock_weight); } - return res; + tdc_unlock_share(element); + return FALSE; } @@ -1119,50 +1018,95 @@ ulong tdc_increment_refresh_version(void) /** - Initialize table definition cache iterator. + Iterate 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. */ -void TDC_iterator::init(void) +struct eliminate_duplicates_arg { - DBUG_ENTER("TDC_iterator::init"); - idx= 0; - mysql_rwlock_rdlock(&LOCK_tdc); - DBUG_VOID_RETURN; -} - + HASH hash; + MEM_ROOT root; + my_hash_walk_action action; + void *argument; +}; -/** - Deinitialize table definition cache iterator. -*/ -void TDC_iterator::deinit(void) +static uchar *eliminate_duplicates_get_key(const uchar *element, size_t *length, + my_bool not_used __attribute__((unused))) { - DBUG_ENTER("TDC_iterator::deinit"); - mysql_rwlock_unlock(&LOCK_tdc); - DBUG_VOID_RETURN; + LEX_STRING *key= (LEX_STRING *) element; + *length= key->length; + return (uchar *) key->str; } -/** - Get next TABLE_SHARE object from table definition cache. +static my_bool eliminate_duplicates(TDC_element *element, + eliminate_duplicates_arg *arg) +{ + LEX_STRING *key= (LEX_STRING *) alloc_root(&arg->root, sizeof(LEX_STRING)); - Object is protected against removal from table definition cache. + if (!key || !(key->str= (char*) memdup_root(&arg->root, element->m_key, + element->m_key_length))) + return TRUE; + + key->length= element->m_key_length; + + if (my_hash_insert(&arg->hash, (uchar *) key)) + return FALSE; + + return arg->action(element, arg->argument); +} - @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) +int tdc_iterate(THD *thd, my_hash_walk_action action, void *argument, + bool no_dups) { - TABLE_SHARE *share= 0; - DBUG_ENTER("TDC_iterator::next"); - if (idx < tdc_hash.records) + eliminate_duplicates_arg no_dups_argument; + LF_PINS *pins; + myf alloc_flags= 0; + uint hash_flags= HASH_UNIQUE; + int res; + + if (thd) { - share= (TABLE_SHARE*) my_hash_element(&tdc_hash, idx); - idx++; + fix_thd_pins(thd); + pins= thd->tdc_hash_pins; + alloc_flags= MY_THREAD_SPECIFIC; + hash_flags|= HASH_THREAD_SPECIFIC; } - DBUG_RETURN(share); + else + pins= lf_hash_get_pins(&tdc_hash); + + if (!pins) + return ER_OUTOFMEMORY; + + if (no_dups) + { + init_alloc_root(&no_dups_argument.root, 4096, 4096, MYF(alloc_flags)); + my_hash_init(&no_dups_argument.hash, &my_charset_bin, tdc_records(), 0, 0, + eliminate_duplicates_get_key, 0, hash_flags); + no_dups_argument.action= action; + no_dups_argument.argument= argument; + action= (my_hash_walk_action) eliminate_duplicates; + argument= &no_dups_argument; + } + + res= lf_hash_iterate(&tdc_hash, pins, action, argument); + + if (!thd) + lf_hash_put_pins(pins); + + if (no_dups) + { + my_hash_free(&no_dups_argument.hash); + free_root(&no_dups_argument.root, MYF(0)); + } + return res; } |