Commit 07eaf6ea authored by unknown's avatar unknown

MDEV-5636: Deadlock in RESET MASTER

The problem is a deadlock between MYSQL_BIN_LOG::reset_logs() and
MYSQL_BIN_LOG::mark_xid_done(). The former takes LOCK_log and waits for the
latter to complete. But the latter also tries to take LOCK_log; this can lead
to a deadlock.

There was already code that tries to deal with this, with the flag
reset_master_pending. However, there was still a small opportunity for
deadlock, when an previous mark_xid_done() is still running when reset_logs()
is called and is at the precise point where it first releases LOCK_xid_list
and then re-aquires both LOCK_log and LOCK_xid_list.

Solve by setting reset_master_pending in reset_logs() before taking
LOCK_log. And also count how many invocations of LOCK_xid_list are in the
progress of releasing and re-aquiring locks, and in reset_logs() wait for that
number to drop to zero after setting reset_master_pending and before taking
LOCK_log.
parent 76e929a9
...@@ -2933,7 +2933,7 @@ const char *MYSQL_LOG::generate_name(const char *log_name, ...@@ -2933,7 +2933,7 @@ const char *MYSQL_LOG::generate_name(const char *log_name,
MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period) MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period)
:reset_master_pending(false), :reset_master_pending(false), mark_xid_done_waiting(0),
bytes_written(0), file_id(1), open_count(1), bytes_written(0), file_id(1), open_count(1),
group_commit_queue(0), group_commit_queue_busy(FALSE), group_commit_queue(0), group_commit_queue_busy(FALSE),
num_commits(0), num_group_commits(0), num_commits(0), num_group_commits(0),
...@@ -3749,22 +3749,11 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log, ...@@ -3749,22 +3749,11 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log,
const char* save_name; const char* save_name;
DBUG_ENTER("reset_logs"); DBUG_ENTER("reset_logs");
if (thd)
ha_reset_logs(thd);
/*
We need to get both locks to be sure that no one is trying to
write to the index log file.
*/
mysql_mutex_lock(&LOCK_log);
mysql_mutex_lock(&LOCK_index);
if (!is_relay_log) if (!is_relay_log)
{ {
if (init_state && !is_empty_state()) if (init_state && !is_empty_state())
{ {
my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0)); my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0));
mysql_mutex_unlock(&LOCK_index);
mysql_mutex_unlock(&LOCK_log);
DBUG_RETURN(1); DBUG_RETURN(1);
} }
...@@ -3773,11 +3762,29 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log, ...@@ -3773,11 +3762,29 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log,
This ensures that a binlog checkpoint will not try to write binlog This ensures that a binlog checkpoint will not try to write binlog
checkpoint events, which would be useless (as we are deleting the binlog checkpoint events, which would be useless (as we are deleting the binlog
anyway) and could deadlock, as we are holding LOCK_log. anyway) and could deadlock, as we are holding LOCK_log.
Wait for any mark_xid_done() calls that might be already running to
complete (mark_xid_done_waiting counter to drop to zero); we need to
do this before we take the LOCK_log to not deadlock.
*/ */
mysql_mutex_lock(&LOCK_xid_list); mysql_mutex_lock(&LOCK_xid_list);
reset_master_pending= true; reset_master_pending= true;
while (mark_xid_done_waiting > 0)
mysql_cond_wait(&COND_xid_list, &LOCK_xid_list);
mysql_mutex_unlock(&LOCK_xid_list); mysql_mutex_unlock(&LOCK_xid_list);
}
if (thd)
ha_reset_logs(thd);
/*
We need to get both locks to be sure that no one is trying to
write to the index log file.
*/
mysql_mutex_lock(&LOCK_log);
mysql_mutex_lock(&LOCK_index);
if (!is_relay_log)
{
/* /*
We are going to nuke all binary log files. We are going to nuke all binary log files.
Without binlog, we cannot XA recover prepared-but-not-committed Without binlog, we cannot XA recover prepared-but-not-committed
...@@ -8834,9 +8841,13 @@ TC_LOG_BINLOG::mark_xid_done(ulong binlog_id, bool write_checkpoint) ...@@ -8834,9 +8841,13 @@ TC_LOG_BINLOG::mark_xid_done(ulong binlog_id, bool write_checkpoint)
locks in the opposite order. locks in the opposite order.
*/ */
++mark_xid_done_waiting;
mysql_mutex_unlock(&LOCK_xid_list); mysql_mutex_unlock(&LOCK_xid_list);
mysql_mutex_lock(&LOCK_log); mysql_mutex_lock(&LOCK_log);
mysql_mutex_lock(&LOCK_xid_list); mysql_mutex_lock(&LOCK_xid_list);
--mark_xid_done_waiting;
if (unlikely(reset_master_pending))
mysql_cond_signal(&COND_xid_list);
/* We need to reload current_binlog_id due to release/re-take of lock. */ /* We need to reload current_binlog_id due to release/re-take of lock. */
current= current_binlog_id; current= current_binlog_id;
......
...@@ -471,6 +471,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG ...@@ -471,6 +471,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
checkpoint arrives - when all have arrived, RESET MASTER will complete. checkpoint arrives - when all have arrived, RESET MASTER will complete.
*/ */
bool reset_master_pending; bool reset_master_pending;
ulong mark_xid_done_waiting;
/* LOCK_log and LOCK_index are inited by init_pthread_objects() */ /* LOCK_log and LOCK_index are inited by init_pthread_objects() */
mysql_mutex_t LOCK_index; mysql_mutex_t LOCK_index;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment