summaryrefslogtreecommitdiff
path: root/sql/table.cc
diff options
context:
space:
mode:
authorGeorgi Kodinov <Georgi.Kodinov@Oracle.com>2010-09-20 17:17:32 +0300
committerGeorgi Kodinov <Georgi.Kodinov@Oracle.com>2010-09-20 17:17:32 +0300
commitdc0b8f7ada9df6ea4b40d28766a672581526d9d8 (patch)
tree4b2b7364d1e622b3b02db3fb9d0c898fb9293a87 /sql/table.cc
parent702d559fd5e19e9d0ef462c50c2da97360fc9def (diff)
parent9141bb587892f3bfb59f0aad5eb8ef38e4310199 (diff)
downloadmariadb-git-dc0b8f7ada9df6ea4b40d28766a672581526d9d8.tar.gz
merge of mysql-5.5 into mysql-5.5-wl1054
Diffstat (limited to 'sql/table.cc')
-rw-r--r--sql/table.cc311
1 files changed, 286 insertions, 25 deletions
diff --git a/sql/table.cc b/sql/table.cc
index 67f49195b73..7d16ad7974e 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -34,6 +34,7 @@
#include <m_ctype.h>
#include "my_md5.h"
#include "sql_select.h"
+#include "mdl.h" // MDL_wait_for_graph_visitor
/* INFORMATION_SCHEMA name */
LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")};
@@ -325,6 +326,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
share->used_tables.empty();
share->free_tables.empty();
+ share->m_flush_tickets.empty();
memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data,
@@ -389,52 +391,92 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
share->used_tables.empty();
share->free_tables.empty();
+ share->m_flush_tickets.empty();
DBUG_VOID_RETURN;
}
+/**
+ Release resources (plugins) used by the share and free its memory.
+ TABLE_SHARE is self-contained -- it's stored in its own MEM_ROOT.
+ Free this MEM_ROOT.
+*/
+
+void TABLE_SHARE::destroy()
+{
+ uint idx;
+ KEY *info_it;
+
+ /* The mutex is initialized only for shares that are part of the TDC */
+ if (tmp_table == NO_TMP_TABLE)
+ mysql_mutex_destroy(&LOCK_ha_data);
+ my_hash_free(&name_hash);
+
+ plugin_unlock(NULL, db_plugin);
+ db_plugin= NULL;
+
+ /* Release fulltext parsers */
+ info_it= key_info;
+ for (idx= keys; idx; idx--, info_it++)
+ {
+ if (info_it->flags & HA_USES_PARSER)
+ {
+ plugin_unlock(NULL, info_it->parser);
+ info_it->flags= 0;
+ }
+ }
+ /*
+ Make a copy since the share is allocated in its own root,
+ and free_root() updates its argument after freeing the memory.
+ */
+ MEM_ROOT own_root= mem_root;
+ free_root(&own_root, MYF(0));
+}
+
/*
Free table share and memory used by it
SYNOPSIS
free_table_share()
share Table share
-
- NOTES
- share->mutex must be locked when we come here if it's not a temp table
*/
void free_table_share(TABLE_SHARE *share)
{
- MEM_ROOT mem_root;
- uint idx;
- KEY *key_info;
DBUG_ENTER("free_table_share");
DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str));
DBUG_ASSERT(share->ref_count == 0);
- /* The mutex is initialized only for shares that are part of the TDC */
- if (share->tmp_table == NO_TMP_TABLE)
- mysql_mutex_destroy(&share->LOCK_ha_data);
- my_hash_free(&share->name_hash);
-
- plugin_unlock(NULL, share->db_plugin);
- share->db_plugin= NULL;
-
- /* Release fulltext parsers */
- key_info= share->key_info;
- for (idx= share->keys; idx; idx--, key_info++)
+ if (share->m_flush_tickets.is_empty())
{
- if (key_info->flags & HA_USES_PARSER)
- {
- plugin_unlock(NULL, key_info->parser);
- key_info->flags= 0;
- }
+ /*
+ No threads are waiting for this share to be flushed (the
+ share is not old, is for a temporary table, or just nobody
+ happens to be waiting for it). Destroy it.
+ */
+ share->destroy();
+ }
+ else
+ {
+ Wait_for_flush_list::Iterator it(share->m_flush_tickets);
+ Wait_for_flush *ticket;
+ /*
+ We're about to iterate over a list that is used
+ concurrently. Make sure this never happens without a lock.
+ */
+ mysql_mutex_assert_owner(&LOCK_open);
+
+ 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.
+ */
}
- /* We must copy mem_root from share because share is allocated through it */
- memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root));
- free_root(&mem_root, MYF(0)); // Free's share
DBUG_VOID_RETURN;
}
@@ -2995,6 +3037,225 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def)
}
+/**
+ Traverse portion of wait-for graph which is reachable through edge
+ represented by this flush ticket in search for deadlocks.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE Success, no deadlocks.
+*/
+
+bool Wait_for_flush::accept_visitor(MDL_wait_for_graph_visitor *gvisitor)
+{
+ return m_share->visit_subgraph(this, gvisitor);
+}
+
+
+uint Wait_for_flush::get_deadlock_weight() const
+{
+ return m_deadlock_weight;
+}
+
+
+/**
+ Traverse portion of wait-for graph which is reachable through this
+ table share in search for deadlocks.
+
+ @param waiting_ticket Ticket representing wait for this share.
+ @param dvisitor Deadlock detection visitor.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE No deadlocks, it's OK to begin wait.
+*/
+
+bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
+ MDL_wait_for_graph_visitor *gvisitor)
+{
+ TABLE *table;
+ MDL_context *src_ctx= wait_for_flush->get_ctx();
+ bool result= TRUE;
+
+ /*
+ To protect used_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
+ holding a write-lock on MDL_lock::m_rwlock.
+ */
+ if (gvisitor->m_lock_open_count++ == 0)
+ {
+ /*
+ To circumvent bug #56405 "Deadlock in the MDL deadlock detector"
+ we don't try to lock LOCK_open mutex if some thread doing
+ deadlock detection already owns it and current search depth is
+ greater than 0. Instead we report a deadlock.
+
+ TODO/FIXME: The proper fix for this bug is to use rwlocks for
+ protection of table shares/instead of LOCK_open.
+ Unfortunately it requires more effort/has significant
+ performance effect.
+ */
+ mysql_mutex_lock(&LOCK_dd_owns_lock_open);
+ if (gvisitor->m_current_search_depth > 0 && dd_owns_lock_open > 0)
+ {
+ mysql_mutex_unlock(&LOCK_dd_owns_lock_open);
+ --gvisitor->m_lock_open_count;
+ gvisitor->abort_traversal(src_ctx);
+ return TRUE;
+ }
+ ++dd_owns_lock_open;
+ mysql_mutex_unlock(&LOCK_dd_owns_lock_open);
+ mysql_mutex_lock(&LOCK_open);
+ }
+
+ I_P_List_iterator <TABLE, TABLE_share> tables_it(used_tables);
+
+ /*
+ In case of multiple searches running in parallel, avoid going
+ over the same loop twice and shortcut the search.
+ Do it after taking the lock to weed out unnecessary races.
+ */
+ if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY)
+ {
+ result= FALSE;
+ goto end;
+ }
+
+ ++gvisitor->m_current_search_depth;
+ if (gvisitor->enter_node(src_ctx))
+ {
+ --gvisitor->m_current_search_depth;
+ goto end;
+ }
+
+ while ((table= tables_it++))
+ {
+ if (gvisitor->inspect_edge(&table->in_use->mdl_context))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ tables_it.rewind();
+ while ((table= tables_it++))
+ {
+ if (table->in_use->mdl_context.visit_subgraph(gvisitor))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ result= FALSE;
+
+end_leave_node:
+ gvisitor->leave_node(src_ctx);
+ --gvisitor->m_current_search_depth;
+
+end:
+ if (gvisitor->m_lock_open_count-- == 1)
+ {
+ mysql_mutex_unlock(&LOCK_open);
+ mysql_mutex_lock(&LOCK_dd_owns_lock_open);
+ --dd_owns_lock_open;
+ mysql_mutex_unlock(&LOCK_dd_owns_lock_open);
+ }
+
+ return result;
+}
+
+
+/**
+ Wait until the subject share is removed from the table
+ definition cache and make sure it's destroyed.
+
+ @param mdl_context MDL context for thread which is going to wait.
+ @param abstime Timeout for waiting as absolute time value.
+ @param deadlock_weight Weight of this wait for deadlock detector.
+
+ @pre LOCK_open is write locked, the share is used (has
+ non-zero reference count), is marked for flush and
+ this connection does not reference the share.
+ LOCK_open will be unlocked temporarily during execution.
+
+ @retval FALSE - Success.
+ @retval TRUE - Error (OOM, deadlock, timeout, etc...).
+*/
+
+bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime,
+ uint deadlock_weight)
+{
+ MDL_context *mdl_context= &thd->mdl_context;
+ Wait_for_flush ticket(mdl_context, this, deadlock_weight);
+ MDL_wait::enum_wait_status wait_status;
+
+ mysql_mutex_assert_owner(&LOCK_open);
+ /*
+ We should enter this method only when share's version is not
+ up to date and the share is referenced. Otherwise our
+ thread will never be woken up from wait.
+ */
+ DBUG_ASSERT(version != refresh_version && ref_count != 0);
+
+ m_flush_tickets.push_front(&ticket);
+
+ mdl_context->m_wait.reset_status();
+
+ mysql_mutex_unlock(&LOCK_open);
+
+ mdl_context->will_wait_for(&ticket);
+
+ mdl_context->find_deadlock();
+
+ wait_status= mdl_context->m_wait.timed_wait(thd, abstime, TRUE,
+ "Waiting for table flush");
+
+ mdl_context->done_waiting_for();
+
+ mysql_mutex_lock(&LOCK_open);
+
+ m_flush_tickets.remove(&ticket);
+
+ if (m_flush_tickets.is_empty() && ref_count == 0)
+ {
+ /*
+ If our thread was the last one using the share,
+ we must destroy it here.
+ */
+ destroy();
+ }
+
+ /*
+ In cases when our wait was aborted by KILL statement,
+ a deadlock or a timeout, the share might still be referenced,
+ so we don't delete it. Note, that we can't determine this
+ condition by checking wait_status alone, since, for example,
+ a timeout can happen after all references to the table share
+ were released, but before the share is removed from the
+ cache and we receive the notification. This is why
+ we first destroy the share, and then look at
+ wait_status.
+ */
+ switch (wait_status)
+ {
+ case MDL_wait::GRANTED:
+ return FALSE;
+ case MDL_wait::VICTIM:
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return TRUE;
+ case MDL_wait::TIMEOUT:
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ return TRUE;
+ case MDL_wait::KILLED:
+ return TRUE;
+ default:
+ DBUG_ASSERT(0);
+ return TRUE;
+ }
+}
+
+
/*
Create Item_field for each column in the table.