summaryrefslogtreecommitdiff
path: root/sql/mdl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/mdl.cc')
-rw-r--r--sql/mdl.cc172
1 files changed, 163 insertions, 9 deletions
diff --git a/sql/mdl.cc b/sql/mdl.cc
index 40074879e21..af7f310e598 100644
--- a/sql/mdl.cc
+++ b/sql/mdl.cc
@@ -196,6 +196,7 @@ void MDL_context::init(THD *thd_arg)
to empty the list.
*/
m_tickets.empty();
+ m_is_waiting_in_mdl= FALSE;
}
@@ -803,14 +804,28 @@ MDL_context::clone_ticket(MDL_request *mdl_request)
@retval FALSE Lock is not a shared one or no thread was woken up
*/
-static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket)
+bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket)
{
bool woke= FALSE;
if (conflicting_ticket->is_shared())
{
THD *conflicting_thd= conflicting_ticket->get_ctx()->get_thd();
DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */
- woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd);
+
+ /*
+ If the thread that holds the conflicting lock is waiting
+ on an MDL lock, wake it up by broadcasting on COND_mdl.
+ Otherwise it must be waiting on a table-level lock
+ or some other non-MDL resource, so delegate its waking up
+ to an external call.
+ */
+ if (conflicting_ticket->get_ctx()->is_waiting_in_mdl())
+ {
+ pthread_cond_broadcast(&COND_mdl);
+ woke= TRUE;
+ }
+ else
+ woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd);
}
return woke;
}
@@ -957,7 +972,7 @@ bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests)
to abort this thread once again.
*/
struct timespec abstime;
- set_timespec(abstime, 10);
+ set_timespec(abstime, 1);
pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
}
if (mysys_var->abort)
@@ -1032,6 +1047,7 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
const char *old_msg;
st_my_thread_var *mysys_var= my_thread_var;
THD *thd= m_ctx->get_thd();
+ MDL_ticket *pending_ticket;
DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive");
DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive");
@@ -1045,8 +1061,22 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
/* Only allow upgrades from MDL_SHARED_UPGRADABLE */
DBUG_ASSERT(m_type == MDL_SHARED_UPGRADABLE);
+ /*
+ Create an auxiliary ticket to represent a pending exclusive
+ lock and add it to the 'waiting' queue for the duration
+ of upgrade. During upgrade we abort waits of connections
+ that own conflicting locks. A pending request is used
+ to signal such connections that upon waking up they
+ must back off, rather than fall into sleep again.
+ */
+ if (! (pending_ticket= MDL_ticket::create(m_ctx, MDL_EXCLUSIVE)))
+ DBUG_RETURN(TRUE);
+
pthread_mutex_lock(&LOCK_mdl);
+ pending_ticket->m_lock= m_lock;
+ m_lock->waiting.push_front(pending_ticket);
+
old_msg= MDL_ENTER_COND(thd, mysys_var);
/*
@@ -1088,6 +1118,30 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
MDL_ticket *conflicting_ticket;
MDL_lock::Ticket_iterator it(m_lock->granted);
+ /*
+ A temporary work-around to avoid deadlocks/livelocks in
+ a situation when in one connection ALTER TABLE tries to
+ upgrade its metadata lock and in another connection
+ the active transaction already got this lock in some
+ of its earlier statements.
+ In such case this transaction always succeeds with getting
+ a metadata lock on the table -- it already has one.
+ But later on it may block on the table level lock, since ALTER
+ got TL_WRITE_ALLOW_READ, and subsequently get aborted
+ by notify_shared_lock().
+ An abort will lead to a back off, and a second attempt to
+ get an MDL lock (successful), and a table lock (-> livelock).
+
+ The call below breaks this loop by forcing transactions to call
+ tdc_wait_for_old_versions() (even if the transaction doesn't need
+ any new metadata locks), which in turn will check if someone
+ is waiting on the owned MDL lock, and produce ER_LOCK_DEADLOCK.
+
+ TODO: Long-term such deadlocks/livelock will be resolved within
+ MDL subsystem and thus this call will become unnecessary.
+ */
+ mysql_abort_transactions_with_shared_lock(&m_lock->key);
+
while ((conflicting_ticket= it++))
{
if (conflicting_ticket->m_ctx != m_ctx)
@@ -1108,12 +1162,15 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
to abort this thread once again.
*/
struct timespec abstime;
- set_timespec(abstime, 10);
+ set_timespec(abstime, 1);
DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping"));
pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
}
if (mysys_var->abort)
{
+ /* Remove and destroy the auxiliary pending ticket. */
+ m_lock->waiting.remove(pending_ticket);
+ MDL_ticket::destroy(pending_ticket);
/* Pending requests for shared locks can be satisfied now. */
pthread_cond_broadcast(&COND_mdl);
MDL_EXIT_COND(thd, mysys_var, old_msg);
@@ -1124,6 +1181,11 @@ MDL_ticket::upgrade_shared_lock_to_exclusive()
m_lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE;
/* Set the new type of lock in the ticket. */
m_type= MDL_EXCLUSIVE;
+
+ /* Remove and destroy the auxiliary pending ticket. */
+ m_lock->waiting.remove(pending_ticket);
+ MDL_ticket::destroy(pending_ticket);
+
if (m_lock->cached_object)
(*m_lock->cached_object_release_hook)(m_lock->cached_object);
m_lock->cached_object= 0;
@@ -1240,6 +1302,59 @@ bool MDL_context::acquire_global_shared_lock()
/**
+ Check if there are any pending exclusive locks which conflict
+ with shared locks held by this thread.
+
+ @pre The caller already has acquired LOCK_mdl.
+
+ @return TRUE If there are any pending conflicting locks.
+ FALSE Otherwise.
+*/
+
+bool MDL_context::can_wait_lead_to_deadlock_impl() const
+{
+ Ticket_iterator ticket_it(m_tickets);
+ MDL_ticket *ticket;
+
+ while ((ticket= ticket_it++))
+ {
+ /*
+ In MySQL we never call this method while holding exclusive or
+ upgradeable shared metadata locks.
+ Otherwise we would also have to check for the presence of pending
+ requests for conflicting types of global lock.
+ In addition MDL_ticket::has_pending_conflicting_lock_impl()
+ won't work properly for exclusive type of lock.
+ */
+ DBUG_ASSERT(! ticket->is_upgradable_or_exclusive());
+
+ if (ticket->has_pending_conflicting_lock_impl())
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ Implement a simple deadlock detection heuristic: check if there
+ are any pending exclusive locks which conflict with shared locks
+ held by this thread. In that case waiting can be circular,
+ i.e. lead to a deadlock.
+
+ @return TRUE if there are any conflicting locks, FALSE otherwise.
+*/
+
+bool MDL_context::can_wait_lead_to_deadlock() const
+{
+ bool result;
+ pthread_mutex_lock(&LOCK_mdl);
+ result= can_wait_lead_to_deadlock_impl();
+ pthread_mutex_unlock(&LOCK_mdl);
+ return result;
+}
+
+
+/**
Wait until there will be no locks that conflict with lock requests
in the given list.
@@ -1249,7 +1364,7 @@ bool MDL_context::acquire_global_shared_lock()
Does not acquire the locks!
@retval FALSE Success. One can try to obtain metadata locks.
- @retval TRUE Failure (thread was killed)
+ @retval TRUE Failure (thread was killed or deadlock is possible).
*/
bool
@@ -1278,6 +1393,26 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests)
mysql_ha_flush(m_thd);
pthread_mutex_lock(&LOCK_mdl);
old_msg= MDL_ENTER_COND(m_thd, mysys_var);
+
+ /*
+ In cases when we wait while still holding some metadata
+ locks deadlocks are possible.
+ To avoid them we use the following simple empiric - don't
+ wait for new lock request to be satisfied if for one of the
+ locks which are already held by this connection there is
+ a conflicting request (i.e. this connection should not wait
+ if someone waits for it).
+ This empiric should work well (e.g. give low number of false
+ negatives) in situations when conflicts are rare (in our
+ case this is true since DDL statements should be rare).
+ */
+ if (can_wait_lead_to_deadlock_impl())
+ {
+ MDL_EXIT_COND(m_thd, mysys_var, old_msg);
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return TRUE;
+ }
+
it.rewind();
while ((mdl_request= it++))
{
@@ -1301,7 +1436,9 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests)
MDL_EXIT_COND(m_thd, mysys_var, old_msg);
break;
}
+ m_is_waiting_in_mdl= TRUE;
pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ m_is_waiting_in_mdl= FALSE;
/* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
MDL_EXIT_COND(m_thd, mysys_var, old_msg);
}
@@ -1550,21 +1687,38 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
existing shared lock.
@pre The ticket must match an acquired lock.
+ @pre The caller already has acquired LOCK_mdl.
- @param ticket Shared lock against which check should be performed.
+ @return TRUE if there is a conflicting lock request, FALSE otherwise.
+*/
- @return TRUE if there are any conflicting locks, FALSE otherwise.
+bool MDL_ticket::has_pending_conflicting_lock_impl() const
+{
+ DBUG_ASSERT(is_shared());
+ safe_mutex_assert_owner(&LOCK_mdl);
+
+ return !m_lock->waiting.is_empty();
+}
+
+
+/**
+ Check if we have any pending exclusive locks which conflict with
+ existing shared lock.
+
+ @pre The ticket must match an acquired lock.
+
+ @return TRUE if there is a pending conflicting lock request,
+ FALSE otherwise.
*/
bool MDL_ticket::has_pending_conflicting_lock() const
{
bool result;
- DBUG_ASSERT(is_shared());
safe_mutex_assert_not_owner(&LOCK_open);
pthread_mutex_lock(&LOCK_mdl);
- result= !m_lock->waiting.is_empty();
+ result= has_pending_conflicting_lock_impl();
pthread_mutex_unlock(&LOCK_mdl);
return result;
}