summaryrefslogtreecommitdiff
path: root/sql/table_cache.cc
diff options
context:
space:
mode:
authorSergey Vojtovich <svoj@mariadb.org>2014-12-28 19:42:17 +0400
committerSergey Vojtovich <svoj@mariadb.org>2014-12-28 19:46:18 +0400
commit6dbc48ca79e5fdd8d4022b00b862e08a4198155b (patch)
tree1904e477f09dd958af2b6696d4b7c8dbb0a1772d /sql/table_cache.cc
parent8883c54ac08a555bc7d9b09395f49893ad4d80b5 (diff)
downloadmariadb-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.cc944
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;
}