summaryrefslogtreecommitdiff
path: root/sql/wsrep_mysqld.cc
diff options
context:
space:
mode:
authorsjaakola <seppo.jaakola@iki.fi>2021-10-21 14:49:51 +0300
committerJan Lindström <jan.lindstrom@mariadb.com>2021-10-29 09:52:52 +0300
commit5c230b21bfa582ac304db526c3638c514cf98b13 (patch)
treefff5719d75159ea15af0c9927f61800822789c4d /sql/wsrep_mysqld.cc
parentaa7ca987db05bae47645578e1233d43874b6a14d (diff)
downloadmariadb-git-5c230b21bfa582ac304db526c3638c514cf98b13.tar.gz
MDEV-23328 Server hang due to Galera lock conflict resolution
Mutex order violation when wsrep bf thread kills a conflicting trx, the stack is wsrep_thd_LOCK() wsrep_kill_victim() lock_rec_other_has_conflicting() lock_clust_rec_read_check_and_lock() row_search_mvcc() ha_innobase::index_read() ha_innobase::rnd_pos() handler::ha_rnd_pos() handler::rnd_pos_by_record() handler::ha_rnd_pos_by_record() Rows_log_event::find_row() Update_rows_log_event::do_exec_row() Rows_log_event::do_apply_event() Log_event::apply_event() wsrep_apply_events() and mutexes are taken in the order lock_sys->mutex -> victim_trx->mutex -> victim_thread->LOCK_thd_data When a normal KILL statement is executed, the stack is innobase_kill_query() kill_handlerton() plugin_foreach_with_mask() ha_kill_query() THD::awake() kill_one_thread() and mutexes are victim_thread->LOCK_thd_data -> lock_sys->mutex -> victim_trx->mutex This patch is the plan D variant for fixing potetial mutex locking order exercised by BF aborting and KILL command execution. In this approach, KILL command is replicated as TOI operation. This guarantees total isolation for the KILL command execution in the first node: there is no concurrent replication applying and no concurrent DDL executing. Therefore there is no risk of BF aborting to happen in parallel with KILL command execution either. Potential mutex deadlocks between the different mutex access paths with KILL command execution and BF aborting cannot therefore happen. TOI replication is used, in this approach, purely as means to provide isolated KILL command execution in the first node. KILL command should not (and must not) be applied in secondary nodes. In this patch, we make this sure by skipping KILL execution in secondary nodes, in applying phase, where we bail out if applier thread is trying to execute KILL command. This is effective, but skipping the applying of KILL command could happen much earlier as well. This also fixed unprotected calls to wsrep_thd_abort that will use wsrep_abort_transaction. This is fixed by holding THD::LOCK_thd_data while we abort transaction. Reviewed-by: Jan Lindström <jan.lindstrom@mariadb.com>
Diffstat (limited to 'sql/wsrep_mysqld.cc')
-rw-r--r--sql/wsrep_mysqld.cc43
1 files changed, 32 insertions, 11 deletions
diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc
index 74015fdee28..3fc03054014 100644
--- a/sql/wsrep_mysqld.cc
+++ b/sql/wsrep_mysqld.cc
@@ -1956,6 +1956,11 @@ static int wsrep_TOI_event_buf(THD* thd, uchar** buf, size_t* buf_len)
case SQLCOM_DROP_TABLE:
err= wsrep_drop_table_query(thd, buf, buf_len);
break;
+ case SQLCOM_KILL:
+ WSREP_DEBUG("KILL as TOI: %s", thd->query());
+ err= wsrep_to_buf_helper(thd, thd->query(), thd->query_length(),
+ buf, buf_len);
+ break;
case SQLCOM_CREATE_ROLE:
if (sp_process_definer(thd))
{
@@ -2319,7 +2324,11 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
request_thd, granted_thd);
ticket->wsrep_report(wsrep_debug);
- mysql_mutex_lock(&granted_thd->LOCK_thd_data);
+ /* Here we will call wsrep_abort_transaction so we should hold
+ THD::LOCK_thd_data to protect victim from concurrent usage
+ and THD::LOCK_thd_kill to protect from disconnect or delete. */
+ wsrep_thd_LOCK(granted_thd);
+
if (wsrep_thd_is_toi(granted_thd) ||
wsrep_thd_is_applying(granted_thd))
{
@@ -2327,21 +2336,22 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
{
WSREP_DEBUG("BF thread waiting for SR in aborting state");
ticket->wsrep_report(wsrep_debug);
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
+ wsrep_thd_UNLOCK(granted_thd);
}
else if (wsrep_thd_is_SR(granted_thd) && !wsrep_thd_is_SR(request_thd))
{
- WSREP_MDL_LOG(INFO, "MDL conflict, DDL vs SR",
+ WSREP_MDL_LOG(INFO, "MDL conflict, DDL vs SR",
schema, schema_len, request_thd, granted_thd);
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
wsrep_abort_thd(request_thd, granted_thd, 1);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_data);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_kill);
}
else
{
WSREP_MDL_LOG(INFO, "MDL BF-BF conflict", schema, schema_len,
request_thd, granted_thd);
ticket->wsrep_report(true);
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
+ wsrep_thd_UNLOCK(granted_thd);
unireg_abort(1);
}
}
@@ -2350,15 +2360,16 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
{
WSREP_DEBUG("BF thread waiting for FLUSH");
ticket->wsrep_report(wsrep_debug);
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
+ wsrep_thd_UNLOCK(granted_thd);
}
else if (request_thd->lex->sql_command == SQLCOM_DROP_TABLE)
{
WSREP_DEBUG("DROP caused BF abort, conf %s",
wsrep_thd_transaction_state_str(granted_thd));
ticket->wsrep_report(wsrep_debug);
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
wsrep_abort_thd(request_thd, granted_thd, 1);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_data);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_kill);
}
else
{
@@ -2367,8 +2378,9 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
ticket->wsrep_report(wsrep_debug);
if (granted_thd->wsrep_trx().active())
{
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
- wsrep_abort_thd(request_thd, granted_thd, 1);
+ wsrep_abort_thd(request_thd, granted_thd, true);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_data);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_kill);
}
else
{
@@ -2376,10 +2388,11 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
Granted_thd is likely executing with wsrep_on=0. If the requesting
thd is BF, BF abort and wait.
*/
- mysql_mutex_unlock(&granted_thd->LOCK_thd_data);
if (wsrep_thd_is_BF(request_thd, FALSE))
{
ha_abort_transaction(request_thd, granted_thd, TRUE);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_data);
+ mysql_mutex_assert_not_owner(&granted_thd->LOCK_thd_kill);
}
else
{
@@ -2401,6 +2414,7 @@ void wsrep_handle_mdl_conflict(MDL_context *requestor_ctx,
static bool abort_replicated(THD *thd)
{
bool ret_code= false;
+ wsrep_thd_LOCK(thd);
if (thd->wsrep_trx().state() == wsrep::transaction::s_committing)
{
WSREP_DEBUG("aborting replicated trx: %llu", (ulonglong)(thd->real_id));
@@ -2408,6 +2422,9 @@ static bool abort_replicated(THD *thd)
(void)wsrep_abort_thd(thd, thd, TRUE);
ret_code= true;
}
+ else
+ wsrep_thd_UNLOCK(thd);
+
return ret_code;
}
@@ -2445,8 +2462,10 @@ static my_bool have_client_connections(THD *thd, void*)
(longlong) thd->thread_id));
if (is_client_connection(thd) && thd->killed == KILL_CONNECTION)
{
+ WSREP_DEBUG("Informing thread %lld that it's time to die",
+ thd->thread_id);
(void)abort_replicated(thd);
- return 1;
+ return true;
}
return 0;
}
@@ -2483,6 +2502,8 @@ static my_bool kill_all_threads(THD *thd, THD *caller_thd)
{
DBUG_PRINT("quit", ("Informing thread %lld that it's time to die",
(longlong) thd->thread_id));
+ WSREP_DEBUG("Informing thread %lld that it's time to die",
+ thd->thread_id);
/* We skip slave threads & scheduler on this first loop through. */
if (is_client_connection(thd) && thd != caller_thd)
{