1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

MDEV-5864 - Reduce usage of LOCK_open: TABLE_SHARE::tdc.free_tables

Let TABLE_SHARE::tdc.free_tables, TABLE_SHARE::tdc.all_tables,
TABLE_SHARE::tdc.flushed and corresponding invariants be protected by
per-share TABLE_SHARE::tdc.LOCK_table_share instead of global LOCK_open.
This commit is contained in:
Sergey Vojtovich
2014-03-20 11:11:13 +04:00
parent 8250824a12
commit e4fde57712
16 changed files with 122 additions and 134 deletions

View File

@ -69,13 +69,6 @@ static bool tdc_inited;
static int32 tc_count; /**< Number of TABLE objects in table cache. */
/**
Protects TABLE_SHARE::tdc.free_tables, TABLE_SHARE::tdc.all_tables.
*/
mysql_mutex_t LOCK_open;
/**
Protects unused shares list.
@ -90,11 +83,9 @@ 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_open, key_LOCK_unused_shares,
key_TABLE_SHARE_LOCK_table_share;
static PSI_mutex_key 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 }
};
@ -170,6 +161,33 @@ static void tc_remove_table(TABLE *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;
}
/**
Free all unused TABLE objects.
@ -193,9 +211,11 @@ void tc_purge(bool mark_flushed)
TABLE_SHARE::TABLE_list purge_tables;
tdc_it.init();
mysql_mutex_lock(&LOCK_open);
while ((share= tdc_it.next()))
{
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()))
@ -203,9 +223,9 @@ void tc_purge(bool mark_flushed)
tc_remove_table(table);
purge_tables.push_front(table);
}
mysql_mutex_unlock(&share->tdc.LOCK_table_share);
}
tdc_it.deinit();
mysql_mutex_unlock(&LOCK_open);
while ((table= purge_tables.pop_front()))
intern_close_table(table);
@ -232,9 +252,10 @@ void tc_add_table(THD *thd, TABLE *table)
{
bool need_purge;
DBUG_ASSERT(table->in_use == thd);
mysql_mutex_lock(&LOCK_open);
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(&LOCK_open);
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);
@ -243,31 +264,48 @@ void tc_add_table(THD *thd, TABLE *table)
if (need_purge)
{
TABLE *purge_table= 0;
TABLE_SHARE *purge_share= 0;
TABLE_SHARE *share;
TABLE *entry;
ulonglong purge_time;
TDC_iterator tdc_it;
tdc_it.init();
mysql_mutex_lock(&LOCK_open);
while ((share= tdc_it.next()))
{
TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables);
TABLE *entry;
while ((entry= it++))
if (!purge_table || entry->tc_time < purge_table->tc_time)
purge_table= entry;
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);
}
tdc_it.deinit();
if (purge_table)
if (purge_share)
{
purge_table->s->tdc.free_tables.remove(purge_table);
tc_remove_table(purge_table);
mysql_mutex_unlock(&LOCK_open);
intern_close_table(purge_table);
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)
{
entry->s->tdc.free_tables.remove(entry);
tc_remove_table(entry);
mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share);
intern_close_table(entry);
}
else
mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share);
}
else
mysql_mutex_unlock(&LOCK_open);
tdc_it.deinit();
}
}
@ -292,9 +330,9 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share)
{
TABLE *table;
mysql_mutex_lock(&LOCK_open);
mysql_mutex_lock(&share->tdc.LOCK_table_share);
table= share->tdc.free_tables.pop_front();
mysql_mutex_unlock(&LOCK_open);
mysql_mutex_unlock(&share->tdc.LOCK_table_share);
if (table)
{
@ -328,7 +366,7 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share)
@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.
which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share.
@return
@retval true object purged
@ -342,17 +380,17 @@ bool tc_release_table(TABLE *table)
if (table->needs_reopen() || tc_records() > tc_size)
{
mysql_mutex_lock(&LOCK_open);
mysql_mutex_lock(&table->s->tdc.LOCK_table_share);
goto purge;
}
table->tc_time= my_interval_timer();
mysql_mutex_lock(&LOCK_open);
mysql_mutex_lock(&table->s->tdc.LOCK_table_share);
if (table->s->tdc.flushed)
goto purge;
/*
in_use doesn't really need protection of LOCK_open, but must be reset after
in_use doesn't really need mutex protection, but must be reset after
checking tdc.flushed and before this table appears in free_tables.
Resetting in_use is needed only for print_cached_tables() and
list_open_tables().
@ -360,12 +398,13 @@ 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(&LOCK_open);
mysql_mutex_unlock(&table->s->tdc.LOCK_table_share);
return false;
purge:
tc_wait_for_mdl_deadlock_detector(table->s);
tc_remove_table(table);
mysql_mutex_unlock(&LOCK_open);
mysql_mutex_unlock(&table->s->tdc.LOCK_table_share);
table->in_use= 0;
intern_close_table(table);
return true;
@ -446,13 +485,6 @@ int tdc_init(void)
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);
@ -505,7 +537,6 @@ void tdc_deinit(void)
my_atomic_rwlock_destroy(&LOCK_tdc_atomics);
mysql_rwlock_destroy(&LOCK_tdc);
mysql_mutex_destroy(&LOCK_unused_shares);
mysql_mutex_destroy(&LOCK_open);
}
DBUG_VOID_RETURN;
}
@ -575,6 +606,7 @@ void tdc_init_share(TABLE_SHARE *share)
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;
}
@ -590,6 +622,7 @@ void tdc_deinit_share(TABLE_SHARE *share)
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;
@ -826,7 +859,8 @@ void tdc_release_share(TABLE_SHARE *share)
if (share->tdc.ref_count > 1)
{
share->tdc.ref_count--;
mysql_cond_broadcast(&share->tdc.COND_release);
if (!share->is_view)
mysql_cond_broadcast(&share->tdc.COND_release);
mysql_mutex_unlock(&share->tdc.LOCK_table_share);
DBUG_VOID_RETURN;
}
@ -954,13 +988,14 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
I_P_List <TABLE, TABLE_share> purge_tables;
uint my_refs= 1;
mysql_mutex_lock(&LOCK_open);
mysql_mutex_lock(&share->tdc.LOCK_table_share);
tc_wait_for_mdl_deadlock_detector(share);
/*
Set share's version to zero in order to ensure that it gets
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
incrementing of refresh_version is followed by purge of unused table
marking share flushed is followed by purge of unused table
shares.
*/
if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
@ -985,7 +1020,7 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
}
}
DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL);
mysql_mutex_unlock(&LOCK_open);
mysql_mutex_unlock(&share->tdc.LOCK_table_share);
while ((table= purge_tables.pop_front()))
intern_close_table(table);