summaryrefslogtreecommitdiff
path: root/sql/sql_base.cc
diff options
context:
space:
mode:
authorKonstantin Osipov <kostja@sun.com>2010-06-18 20:14:10 +0400
committerKonstantin Osipov <kostja@sun.com>2010-06-18 20:14:10 +0400
commit429454f76ecfbc107fafeb25a33ef4d90f221fc0 (patch)
tree9a1f962e174210ed2a3074e059869d3cc7bd8a6a /sql/sql_base.cc
parentdc00b5dbb221cbe743169d8a952183e2d36c2cea (diff)
downloadmariadb-git-429454f76ecfbc107fafeb25a33ef4d90f221fc0.tar.gz
A new implementation for the TABLE_SHARE cache in MDL
subsystem. Fix a number of caveates that the previous implementation suffered from, including unprotected access to shared data and lax resource accounting (share->ref_count) that could lead to deadlocks. The new implementation still suffers from a number of potential deadlocks in some edge cases, and this is still not enabled by default. Especially since performance testing has shown that it gives only marginable (not even exceeding measuring accuracy) improvements. @todo: - Remove calls to close_cached_tables() with REFRESH_FAST, and have_lock, because they break the MDL cache. - rework FLUSH TABLES <list> to not use close_cached_tables() - make sure that whenever we set TABLE_SHARE::version to 0 we free MDL cache references to it. sql/mdl.cc: We may cache references to TABLE_SHARE objects in MDL_lock objects for tables. Create a separate MDL_lock class to represent a table. sql/mdl.h: Adjust the MDL caching API to avoid races. sql/sql_base.cc: Move all caching functionality close together. Implement a solution for deadlocks caused by close_cached_tables() when MDL cache is enabled (incomplete). sql/sql_yacc.yy: Adjust FLUSH rule to do the necessary initialization of TABLE_LIST elements used in for FLUSH TABLES <list>, and thus work OK with flush_mdl_cache() function.
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r--sql/sql_base.cc209
1 files changed, 163 insertions, 46 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 78862985e97..bd73dd57367 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -755,27 +755,6 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
}
-#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
-/**
- @brief Mark table share as having one more user (increase its reference
- count).
-
- @param share Table share for which reference count should be increased.
-*/
-
-static void reference_table_share(TABLE_SHARE *share)
-{
- DBUG_ENTER("reference_table_share");
- DBUG_ASSERT(share->ref_count);
- mysql_mutex_assert_owner(&LOCK_open);
- share->ref_count++;
- DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
- (ulong) share, share->ref_count));
- DBUG_VOID_RETURN;
-}
-#endif
-
-
/*
Create a list for all open tables matching SQL expression
@@ -941,6 +920,141 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share)
}
+#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
+/**
+ Flush MDL cached objects.
+
+ How MDL table share cache works
+ -------------------------------
+ Since we take a table share from the table definition
+ cache only after taking an MDL lock, the MDL lock
+ object is a convenient place to cache a pointer
+ to the table share. However, not all SQL in MySQL
+ takes an MDL lock prior to working with the TDC,
+ various forms of FLUSH TABLES (including SET GLOBAL
+ read_only) being the one and only exception.
+
+ To make FLUSH TABLES work, and avoid having dangling
+ references to TABLE_SHARE objects in MDL subsystem
+ after a flush, we make sure that all references
+ to table shares are released whenever a flush comes.
+ This is done in this function.
+
+ To sum up, the following invariants are held:
+ - no statement can work with a TABLE_SHARE without
+ a metadata lock. The only exception is FLUSH TABLES.
+ - a metadata lock object can be used to store
+ a cached reference (pointer) to the corresponding
+ TABLE_SHARE, if and only if this TABLE_SHARE is
+ not stale (version == refresh_version). In other words,
+ checking TABLE_SHARE version and setting the reference
+ must happen only in the same critical section protected
+ by LOCK_open.
+ - FLUSH will mark all subject TABLE_SHARE objects
+ as stale, and then will manually release all TABLE_SHARE
+ references in MDL cache. Since marking TABLE_SHARE
+ objects is done inside a critical section protected
+ by LOCK_open and prior to calling flush_mdl_cache(),
+ it's guaranteed that a flush will take place before
+ a new reference to the table share is established
+ in some other connection.
+*/
+
+bool flush_mdl_cache(THD *thd, TABLE_LIST *table_list)
+{
+ MDL_request_list mdl_requests;
+ MDL_request *mdl_request;
+
+ DBUG_ENTER("flush_mdl_cache");
+
+ if (table_list == NULL)
+ {
+ mysql_mutex_lock(&LOCK_open);
+ for (uint idx= 0 ; idx < table_def_cache.records; idx++)
+ {
+ TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache,
+ idx);
+ if (share->needs_reopen())
+ {
+ mdl_request= MDL_request::create(MDL_key::TABLE,
+ share->db.str,
+ share->table_name.str,
+ MDL_SHARED_HIGH_PRIO,
+ thd->mem_root);
+ if (! mdl_request)
+ {
+ mysql_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(TRUE);
+ }
+ mdl_requests.push_front(mdl_request);
+ }
+ }
+ mysql_mutex_unlock(&LOCK_open);
+ }
+ else
+ {
+ for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
+ {
+ DBUG_ASSERT(tables->mdl_request.type == MDL_SHARED_HIGH_PRIO);
+ mdl_requests.push_front(&tables->mdl_request);
+ }
+ }
+
+ for (MDL_request_list::Iterator it(mdl_requests);
+ (mdl_request= it++); )
+ {
+ if (thd->mdl_context.try_acquire_lock(mdl_request))
+ DBUG_RETURN(TRUE);
+ if (mdl_request->ticket)
+ {
+ mdl_request->ticket->clear_cached_object();
+ thd->mdl_context.release_lock(mdl_request->ticket);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Helper function used by MDL subsystem for releasing TABLE_SHARE
+ objects in cases when it no longer wants to cache reference to it.
+*/
+
+void tdc_release_cached_share(void *ptr)
+{
+ TABLE_SHARE **share= (TABLE_SHARE **) ptr;
+ mysql_mutex_lock(&LOCK_open);
+ if (*share)
+ {
+ release_table_share(*share);
+ *share= NULL;
+ broadcast_refresh();
+ }
+ mysql_mutex_unlock(&LOCK_open);
+}
+
+
+/**
+ @brief Mark table share as having one more user (increase its reference
+ count).
+
+ @param share Table share for which reference count should be increased.
+*/
+
+static void tdc_reference_table_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("tdc_reference_table_share");
+ DBUG_ASSERT(share->ref_count);
+ mysql_mutex_assert_owner(&LOCK_open);
+ share->ref_count++;
+ DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
+ (ulong) share, share->ref_count));
+ DBUG_VOID_RETURN;
+}
+
+
+#endif // DISABLED_UNTIL_GRL_IS_MADE_PART_OF
+
/*
Close all tables which aren't in use by any thread
@@ -1052,6 +1166,15 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
/* Wait until all threads have closed all the tables we are flushing. */
DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
+#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF
+ /*
+ @todo We need to do this for fast refresh as well, otherwise
+ deadlocks are possible.
+ */
+ if (flush_mdl_cache(thd, tables))
+ goto err_with_reopen;
+#endif // DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
+
while (found && ! thd->killed)
{
found= FALSE;
@@ -2362,20 +2485,6 @@ end:
/**
- @brief Helper function used by MDL subsystem for releasing TABLE_SHARE
- objects in cases when it no longer wants to cache reference to it.
-*/
-
-void table_share_release_hook(void *share)
-{
- mysql_mutex_lock(&LOCK_open);
- release_table_share((TABLE_SHARE*) share);
- broadcast_refresh();
- mysql_mutex_unlock(&LOCK_open);
-}
-
-
-/**
An error handler which converts, if possible, ER_LOCK_DEADLOCK error
that can occur when we are trying to acquire a metadata lock to
a request for back-off and re-start of open_tables() process.
@@ -2869,8 +2978,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
mysql_mutex_lock(&LOCK_open);
#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
if (!(share= (TABLE_SHARE *) mdl_ticket->get_cached_object()))
-#endif
{
+#endif // DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
if (!(share= get_table_share_with_create(thd, table_list, key,
key_length, OPEN_VIEW,
&error,
@@ -2927,14 +3036,15 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
/*
- We are going to to store extra reference to the share in MDL-subsystem
- so we need to increase reference counter;
+ We are going to to store extra reference to the share
+ in MDL-subsystem so we need to increase reference counter.
*/
- reference_table_share(share);
- mdl_ticket->set_cached_object(share, table_share_release_hook);
-#endif
+ if (! share->needs_reopen())
+ {
+ mdl_ticket->set_cached_object(share);
+ tdc_reference_table_share(share);
+ }
}
-#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
else
{
if (table_list->view)
@@ -2943,19 +3053,19 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
check_and_update_table_version(thd, table_list, share);
/* Always an error. */
DBUG_ASSERT(thd->is_error());
- goto err_unlock;
+ goto err_unlock2;
}
/* When we have cached TABLE_SHARE we know that is not a view. */
if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
- goto err_unlock;
+ goto err_unlock2;
/*
We are going to use this share for construction of new TABLE object
so reference counter should be increased.
*/
- reference_table_share(share);
+ tdc_reference_table_share(share);
}
-#endif
+#endif // DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
/*
@@ -8755,6 +8865,13 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
automatically deleted once it is no longer referenced.
*/
share->version= 0;
+#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL
+ /*
+ If lock type is not EXCLUSIVE, we must call
+ MDL_ticket::release_cached_object() here to make sure there
+ is no self-reference left on the share in MDL_lock.
+ */
+#endif
while ((table= it++))
free_cache_entry(table);
}