summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/sql_base.cc22
-rw-r--r--sql/sql_handler.cc2
-rw-r--r--sql/sql_insert.cc2
-rw-r--r--sql/sql_test.cc9
-rw-r--r--sql/table.cc10
-rw-r--r--sql/table.h11
-rw-r--r--sql/table_cache.cc140
-rw-r--r--sql/table_cache.h7
8 files changed, 83 insertions, 120 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index f4ff9f2fc75..b0e121018fc 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -385,7 +385,8 @@ void kill_delayed_threads_for_table(TABLE_SHARE *share)
{
THD *in_use= tab->in_use;
- if (in_use && (in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ DBUG_ASSERT(in_use && tab->s->tdc.flushed);
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
! in_use->killed)
{
in_use->killed= KILL_SYSTEM_THREAD;
@@ -426,9 +427,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
{
bool result= FALSE;
struct timespec abstime;
+ ulong refresh_version;
DBUG_ENTER("close_cached_tables");
DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
+ refresh_version= tdc_increment_refresh_version();
+
if (!tables)
{
/*
@@ -438,13 +442,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
incrementing of refresh_version is followed by purge of unused table
shares.
*/
- tdc_increment_refresh_version();
kill_delayed_threads();
/*
Get rid of all unused TABLE and TABLE_SHARE instances. By doing
this we automatically close all tables which were marked as "old".
*/
- tc_purge();
+ tc_purge(true);
/* Free table shares which were not freed implicitly by loop above. */
tdc_purge(true);
}
@@ -526,7 +529,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
while ((share= tdc_it.next()))
{
mysql_mutex_lock(&share->tdc.LOCK_table_share);
- if (share->has_old_version())
+ if (share->tdc.flushed && share->tdc.version < refresh_version)
{
/* wait_for_old_version() will unlock mutex and free share */
found= true;
@@ -554,7 +557,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
if (thd->killed)
break;
if (tdc_wait_for_old_version(thd, table->db, table->table_name, timeout,
- MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL,
+ refresh_version))
{
result= TRUE;
break;
@@ -1754,7 +1758,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table,
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
table->s->table_name.str, (ulong) table->s,
- table->db_stat, table->s->version));
+ table->db_stat, table->s->tdc.version));
if (thd->mdl_context.upgrade_shared_lock(
table->mdl_ticket, MDL_EXCLUSIVE,
@@ -2321,7 +2325,7 @@ retry_share:
/*
Check if this TABLE_SHARE-object corresponds to a view. Note, that there is
- no need to call TABLE_SHARE::has_old_version() as we do for regular tables,
+ no need to check TABLE_SHARE::tdc.flushed as we do for regular tables,
because view shares are always up to date.
*/
if (share->is_view)
@@ -2362,7 +2366,7 @@ retry_share:
if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
- if (share->has_old_version())
+ if (share->tdc.flushed)
{
/*
We already have an MDL lock. But we have encountered an old
@@ -2394,7 +2398,7 @@ retry_share:
goto retry_share;
}
- if (thd->open_tables && thd->open_tables->s->version != share->version)
+ if (thd->open_tables && thd->open_tables->s->tdc.flushed)
{
/*
If the version changes while we're opening the tables,
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index 812a5dc1461..6b961ac2262 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -1135,7 +1135,7 @@ void mysql_ha_flush(THD *thd)
((hash_tables->table->mdl_ticket &&
hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) ||
(!hash_tables->table->s->tmp_table &&
- hash_tables->table->s->has_old_version())))
+ hash_tables->table->s->tdc.flushed)))
mysql_ha_close_table(hash_tables);
}
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 899e0dead6b..dee631edb4b 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -3072,7 +3072,7 @@ bool Delayed_insert::handle_inserts(void)
THD_STAGE_INFO(&thd, stage_insert);
max_rows= delayed_insert_limit;
- if (thd.killed || table->s->has_old_version())
+ if (thd.killed || table->s->tdc.flushed)
{
thd.killed= KILL_SYSTEM_THREAD;
max_rows= ULONG_MAX; // Do as much as possible
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
index 922423bef26..8588d6564ca 100644
--- a/sql/sql_test.cc
+++ b/sql/sql_test.cc
@@ -94,12 +94,13 @@ static void print_cached_tables(void)
TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
while ((entry= it++))
{
+ THD *in_use= entry->in_use;
printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
- entry->s->db.str, entry->s->table_name.str, entry->s->version,
- entry->in_use ? entry->in_use->thread_id : 0,
+ entry->s->db.str, entry->s->table_name.str, entry->s->tdc.version,
+ in_use ? in_use->thread_id : 0,
entry->db_stat ? 1 : 0,
- entry->in_use ? lock_descriptions[(int)entry->reginfo.lock_type] :
- "Not in use");
+ in_use ? lock_descriptions[(int)entry->reginfo.lock_type] :
+ "Not in use");
}
}
mysql_mutex_unlock(&LOCK_open);
diff --git a/sql/table.cc b/sql/table.cc
index 2e1d18b5da1..78d7e7935fc 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -3805,7 +3805,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
bool result= TRUE;
/*
- To protect used_tables list from being concurrently modified
+ To protect all_tables list from being concurrently modified
while we are iterating through it we acquire LOCK_open.
This does not introduce deadlocks in the deadlock detector
because we won't try to acquire LOCK_open while
@@ -3832,7 +3832,8 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
while ((table= tables_it++))
{
- if (table->in_use && gvisitor->inspect_edge(&table->in_use->mdl_context))
+ DBUG_ASSERT(table->in_use && tdc.flushed);
+ if (gvisitor->inspect_edge(&table->in_use->mdl_context))
{
goto end_leave_node;
}
@@ -3841,7 +3842,8 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
tables_it.rewind();
while ((table= tables_it++))
{
- if (table->in_use && table->in_use->mdl_context.visit_subgraph(gvisitor))
+ DBUG_ASSERT(table->in_use && tdc.flushed);
+ if (table->in_use->mdl_context.visit_subgraph(gvisitor))
{
goto end_leave_node;
}
@@ -3890,7 +3892,7 @@ bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime,
MDL_wait::enum_wait_status wait_status;
mysql_mutex_assert_owner(&tdc.LOCK_table_share);
- DBUG_ASSERT(has_old_version());
+ DBUG_ASSERT(tdc.flushed);
tdc.m_flush_tickets.push_front(&ticket);
diff --git a/sql/table.h b/sql/table.h
index 260ff7cf6ba..0cee0a54dff 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -481,8 +481,6 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db,
struct TABLE_share;
struct All_share_tables;
-extern ulong tdc_refresh_version(void);
-
typedef struct st_table_field_type
{
LEX_STRING name;
@@ -623,6 +621,8 @@ struct TABLE_SHARE
*/
All_share_tables_list all_tables;
TABLE_list free_tables;
+ ulong version;
+ bool flushed;
} tdc;
LEX_CUSTRING tabledef_version;
@@ -668,7 +668,6 @@ struct TABLE_SHARE
key_map keys_for_keyread;
ha_rows min_rows, max_rows; /* create information */
ulong avg_row_length; /* create information */
- ulong version;
ulong mysql_version; /* 0 if .frm is created before 5.0 */
ulong reclength; /* Recordlength */
/* Stored record length. No generated-only virtual fields are included */
@@ -847,12 +846,6 @@ struct TABLE_SHARE
return table_map_id;
}
- /** Is this table share being expelled from the table definition cache? */
- inline bool has_old_version() const
- {
- return version != tdc_refresh_version();
- }
-
/**
Convert unrelated members of TABLE_SHARE to one enum
representing its type.
diff --git a/sql/table_cache.cc b/sql/table_cache.cc
index d0840e803c7..b24f286b19e 100644
--- a/sql/table_cache.cc
+++ b/sql/table_cache.cc
@@ -44,6 +44,8 @@
Table cache invariants:
- TABLE_SHARE::free_tables shall not contain objects with TABLE::in_use != 0
+ - TABLE_SHARE::free_tables shall not receive new objects if
+ TABLE_SHARE::tdc.flushed is true
*/
#include "my_global.h"
@@ -68,8 +70,7 @@ static int32 tc_count; /**< Number of TABLE objects in table cache. */
/**
- Protects TABLE_SHARE::tdc.free_tables, TABLE_SHARE::tdc.all_tables,
- TABLE::in_use.
+ Protects TABLE_SHARE::tdc.free_tables, TABLE_SHARE::tdc.all_tables.
*/
mysql_mutex_t LOCK_open;
@@ -176,7 +177,7 @@ static void tc_remove_table(TABLE *table)
periodicly flush all not used tables.
*/
-void tc_purge(void)
+void tc_purge(bool mark_flushed)
{
TABLE_SHARE *share;
TABLE *table;
@@ -187,6 +188,8 @@ void tc_purge(void)
mysql_mutex_lock(&LOCK_open);
while ((share= tdc_it.next()))
{
+ if (mark_flushed)
+ share->tdc.flushed= true;
while ((table= share->tdc.free_tables.pop_front()))
{
tc_remove_table(table);
@@ -204,49 +207,6 @@ void tc_purge(void)
/**
- Verify consistency of used/unused lists (for debugging).
-*/
-
-#ifdef EXTRA_DEBUG
-static void check_unused(THD *thd)
-{
- TABLE *entry;
- TABLE_SHARE *share;
- TDC_iterator tdc_it;
- DBUG_ENTER("check_unused");
-
- tdc_it.init();
- mysql_mutex_lock(&LOCK_open);
- 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;
- }
- }
- mysql_mutex_unlock(&LOCK_open);
- tdc_it.deinit();
- DBUG_VOID_RETURN;
-}
-#else
-#define check_unused(A)
-#endif
-
-
-/**
Add new TABLE object to table cache.
@pre TABLE object is used by caller.
@@ -301,7 +261,6 @@ void tc_add_table(THD *thd, TABLE *table)
mysql_mutex_unlock(&LOCK_open);
intern_close_table(purge_table);
mysql_rwlock_unlock(&LOCK_flush);
- check_unused(thd);
}
else
mysql_mutex_unlock(&LOCK_open);
@@ -317,7 +276,9 @@ void tc_add_table(THD *thd, TABLE *table)
Acquired object cannot be evicted or acquired again.
While locked:
- - pop object from TABLE_SHARE::tdc.free_tables()
+ - pop object from TABLE_SHARE::tdc.free_tables
+
+ While unlocked:
- mark object used by thd
@return TABLE object, or NULL if no unused objects.
@@ -328,19 +289,18 @@ 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;
- }
- DBUG_ASSERT(!table->in_use);
- table->in_use= thd;
+ table= share->tdc.free_tables.pop_front();
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));
+ 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));
+ }
return table;
}
@@ -353,12 +313,12 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share)
Released object may be evicted or acquired again.
While locked:
- - mark object not in use by any thread
- if object is marked for purge, decrement tc_count
- add object to TABLE_SHARE::tdc.free_tables
- evict LRU object from table cache if we reached threshold
While unlocked:
+ - mark object not in use by any thread
- free evicted/purged object
@note Another thread may mark share for purge any moment (even
@@ -373,33 +333,37 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share)
bool tc_release_table(TABLE *table)
{
- THD *thd __attribute__((unused))= table->in_use;
DBUG_ASSERT(table->in_use);
DBUG_ASSERT(table->file);
if (table->needs_reopen() || tc_records() > tc_size)
{
mysql_mutex_lock(&LOCK_open);
- table->in_use= 0;
goto purge;
}
table->tc_time= my_interval_timer();
mysql_mutex_lock(&LOCK_open);
- table->in_use= 0;
- if (table->s->has_old_version())
+ if (table->s->tdc.flushed)
goto purge;
+ /*
+ in_use doesn't really need protection of LOCK_open, 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().
+ */
+ 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);
- check_unused(thd);
return false;
purge:
tc_remove_table(table);
mysql_rwlock_rdlock(&LOCK_flush);
mysql_mutex_unlock(&LOCK_open);
+ table->in_use= 0;
intern_close_table(table);
mysql_rwlock_unlock(&LOCK_flush);
return true;
@@ -607,7 +571,8 @@ void tdc_init_share(TABLE_SHARE *share)
share->tdc.all_tables.empty();
share->tdc.free_tables.empty();
tdc_assign_new_table_id(share);
- share->version= tdc_refresh_version();
+ share->tdc.version= tdc_refresh_version();
+ share->tdc.flushed= false;
DBUG_VOID_RETURN;
}
@@ -767,7 +732,6 @@ TABLE_SHARE *tdc_acquire_share(THD *thd, const char *db, const char *table_name,
if ((*out_table= tc_acquire_table(thd, share)))
{
mysql_rwlock_unlock(&LOCK_tdc);
- check_unused(thd);
DBUG_ASSERT(!(flags & GTS_NOLOCK));
DBUG_ASSERT(!share->error);
DBUG_ASSERT(!share->is_view);
@@ -855,7 +819,7 @@ void tdc_release_share(TABLE_SHARE *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));
+ share->tdc.ref_count, share->tdc.version));
DBUG_ASSERT(share->tdc.ref_count);
if (share->tdc.ref_count > 1)
@@ -868,7 +832,7 @@ void tdc_release_share(TABLE_SHARE *share)
mysql_mutex_lock(&LOCK_unused_shares);
mysql_mutex_lock(&share->tdc.LOCK_table_share);
- if (share->has_old_version())
+ if (share->tdc.flushed)
{
mysql_mutex_unlock(&share->tdc.LOCK_table_share);
mysql_mutex_unlock(&LOCK_unused_shares);
@@ -988,17 +952,6 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
I_P_List <TABLE, TABLE_share> purge_tables;
mysql_mutex_lock(&LOCK_open);
- if (kill_delayed_threads)
- kill_delayed_threads_for_table(share);
-
-#ifndef DBUG_OFF
- if (remove_type == TDC_RT_REMOVE_NOT_OWN)
- {
- TABLE_SHARE::All_share_tables_list::Iterator it2(share->tdc.all_tables);
- while ((table= it2++))
- DBUG_ASSERT(!table->in_use || 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.
@@ -1008,13 +961,25 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
shares.
*/
if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
- share->version= 0;
+ share->tdc.flushed= true;
while ((table= share->tdc.free_tables.pop_front()))
{
tc_remove_table(table);
purge_tables.push_front(table);
}
+ if (kill_delayed_threads)
+ kill_delayed_threads_for_table(share);
+
+#ifndef DBUG_OFF
+ if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+ {
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ while ((table= it++))
+ DBUG_ASSERT(table->in_use == thd);
+ }
+#endif
+
mysql_rwlock_rdlock(&LOCK_flush);
mysql_mutex_unlock(&LOCK_open);
@@ -1022,7 +987,6 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
intern_close_table(table);
mysql_rwlock_unlock(&LOCK_flush);
- check_unused(thd);
DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL);
tdc_release_share(share);
@@ -1052,14 +1016,15 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
*/
int tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
- ulong wait_timeout, uint deadlock_weight)
+ ulong wait_timeout, uint deadlock_weight,
+ ulong refresh_version)
{
TABLE_SHARE *share;
int res= FALSE;
if ((share= tdc_lock_share(db, table_name)))
{
- if (share->has_old_version())
+ if (share->tdc.flushed && refresh_version > share->tdc.version)
{
struct timespec abstime;
set_timespec(abstime, wait_timeout);
@@ -1081,16 +1046,13 @@ ulong tdc_refresh_version(void)
}
-void tdc_increment_refresh_version(void)
+ulong 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));
+ return v + 1;
}
diff --git a/sql/table_cache.h b/sql/table_cache.h
index 7b7fb239131..a30a07b8357 100644
--- a/sql/table_cache.h
+++ b/sql/table_cache.h
@@ -47,13 +47,14 @@ extern bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
bool kill_delayed_threads);
extern int tdc_wait_for_old_version(THD *thd, const char *db,
const char *table_name,
- ulong wait_timeout, uint deadlock_weight);
+ ulong wait_timeout, uint deadlock_weight,
+ ulong refresh_version= ULONG_MAX);
extern ulong tdc_refresh_version(void);
-extern void tdc_increment_refresh_version(void);
+extern ulong tdc_increment_refresh_version(void);
extern void tdc_assign_new_table_id(TABLE_SHARE *share);
extern uint tc_records(void);
-extern void tc_purge(void);
+extern void tc_purge(bool mark_flushed= false);
extern void tc_add_table(THD *thd, TABLE *table);
extern bool tc_release_table(TABLE *table);