mirror of
https://github.com/MariaDB/server.git
synced 2025-01-30 18:41:56 +01:00
Bug#12636001 : deadlock from thd_security_context
PROBLEM: Threads end-up in deadlock due to locks acquired as described below, con1: Run Query on a table. It is important that this SELECT must back-off while trying to open the t1 and enter into wait_for_condition(). The SELECT then is blocked trying to lock mysys_var->mutex which is held by con3. The very significant fact here is that mysys_var->current_mutex will still point to LOCK_open, even if LOCK_open is no longer held by con1 at this point. con2: Try dropping table used in con1 or query some table. It will hold LOCK_open and be blocked trying to lock kernel_mutex held by con4. con3: Try killing the query run by con1. It will hold THD::LOCK_thd_data belonging to con1 while trying to lock mysys_var->current_mutex belonging to con1. But current_mutex will point to LOCK_open which is held by con2. con4: Get innodb engine status It will hold kernel_mutex, trying to lock THD::LOCK_thd_data belonging to con1 which is held by con3. So while technically only con2, con3 and con4 participate in the deadlock, con1's mysys_var->current_mutex pointing to LOCK_open is a vital component of the deadlock. CYCLE = (THD::LOCK_thd_data -> LOCK_open -> kernel_mutex -> THD::LOCK_thd_data) FIX: LOCK_thd_data has responsibility of protecting, 1) thd->query, thd->query_length 2) VIO 3) thd->mysys_var (used by KILL statement and shutdown) 4) THD during thread delete. Among above responsibilities, 1), 2)and (3,4) seems to be three independent group of responsibility. If there is different LOCK owning responsibility of (3,4), the above mentioned deadlock cycle can be avoid. This fix introduces LOCK_thd_kill to handle responsibility (3,4), which eliminates the deadlock issue. Note: The problem is not found in 5.5. Introduction MDL subsystem caused metadata locking responsibility to be moved from TDC/TC to MDL subsystem. Due to this, responsibility of LOCK_open is reduced. As the use of LOCK_open is removed in open_table() and mysql_rm_table() the above mentioned CYCLE does not form. Revision ID for changes, open_table() = dlenev@mysql.com-20100727133458-m3ua9oslnx8fbbvz mysql_rm_table() = jon.hauglid@oracle.com-20101116100012-kxep9txz2fxy3nmw
This commit is contained in:
parent
3e1758d36a
commit
21faded51e
7 changed files with 36 additions and 12 deletions
|
@ -632,13 +632,13 @@ Event_scheduler::stop()
|
||||||
DBUG_PRINT("info", ("Scheduler thread has id %lu",
|
DBUG_PRINT("info", ("Scheduler thread has id %lu",
|
||||||
scheduler_thd->thread_id));
|
scheduler_thd->thread_id));
|
||||||
/* Lock from delete */
|
/* Lock from delete */
|
||||||
pthread_mutex_lock(&scheduler_thd->LOCK_thd_data);
|
pthread_mutex_lock(&scheduler_thd->LOCK_thd_kill);
|
||||||
/* This will wake up the thread if it waits on Queue's conditional */
|
/* This will wake up the thread if it waits on Queue's conditional */
|
||||||
sql_print_information("Event Scheduler: Killing the scheduler thread, "
|
sql_print_information("Event Scheduler: Killing the scheduler thread, "
|
||||||
"thread id %lu",
|
"thread id %lu",
|
||||||
scheduler_thd->thread_id);
|
scheduler_thd->thread_id);
|
||||||
scheduler_thd->awake(THD::KILL_CONNECTION);
|
scheduler_thd->awake(THD::KILL_CONNECTION);
|
||||||
pthread_mutex_unlock(&scheduler_thd->LOCK_thd_data);
|
pthread_mutex_unlock(&scheduler_thd->LOCK_thd_kill);
|
||||||
|
|
||||||
/* thd could be 0x0, when shutting down */
|
/* thd could be 0x0, when shutting down */
|
||||||
sql_print_information("Event Scheduler: "
|
sql_print_information("Event Scheduler: "
|
||||||
|
|
|
@ -515,7 +515,7 @@ terminate_slave_thread(THD *thd,
|
||||||
int error;
|
int error;
|
||||||
DBUG_PRINT("loop", ("killing slave thread"));
|
DBUG_PRINT("loop", ("killing slave thread"));
|
||||||
|
|
||||||
pthread_mutex_lock(&thd->LOCK_thd_data);
|
pthread_mutex_lock(&thd->LOCK_thd_kill);
|
||||||
#ifndef DONT_USE_THR_ALARM
|
#ifndef DONT_USE_THR_ALARM
|
||||||
/*
|
/*
|
||||||
Error codes from pthread_kill are:
|
Error codes from pthread_kill are:
|
||||||
|
@ -526,7 +526,7 @@ terminate_slave_thread(THD *thd,
|
||||||
DBUG_ASSERT(err != EINVAL);
|
DBUG_ASSERT(err != EINVAL);
|
||||||
#endif
|
#endif
|
||||||
thd->awake(THD::NOT_KILLED);
|
thd->awake(THD::NOT_KILLED);
|
||||||
pthread_mutex_unlock(&thd->LOCK_thd_data);
|
pthread_mutex_unlock(&thd->LOCK_thd_kill);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
There is a small chance that slave thread might miss the first
|
There is a small chance that slave thread might miss the first
|
||||||
|
|
|
@ -2203,6 +2203,8 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pthread_mutex_unlock(mutex);
|
pthread_mutex_unlock(mutex);
|
||||||
|
DEBUG_SYNC(thd, "waiting_for_table_unlock");
|
||||||
|
DBUG_EXECUTE_IF("sleep_after_waiting_for_table", my_sleep(1000000););
|
||||||
pthread_mutex_lock(&thd->mysys_var->mutex);
|
pthread_mutex_lock(&thd->mysys_var->mutex);
|
||||||
thd->mysys_var->current_mutex= 0;
|
thd->mysys_var->current_mutex= 0;
|
||||||
thd->mysys_var->current_cond= 0;
|
thd->mysys_var->current_cond= 0;
|
||||||
|
|
|
@ -365,6 +365,7 @@ extern "C"
|
||||||
char *thd_security_context(THD *thd, char *buffer, unsigned int length,
|
char *thd_security_context(THD *thd, char *buffer, unsigned int length,
|
||||||
unsigned int max_query_len)
|
unsigned int max_query_len)
|
||||||
{
|
{
|
||||||
|
DEBUG_SYNC(thd, "thd_security_context");
|
||||||
String str(buffer, length, &my_charset_latin1);
|
String str(buffer, length, &my_charset_latin1);
|
||||||
const Security_context *sctx= &thd->main_security_ctx;
|
const Security_context *sctx= &thd->main_security_ctx;
|
||||||
char header[64];
|
char header[64];
|
||||||
|
@ -695,6 +696,7 @@ THD::THD()
|
||||||
active_vio = 0;
|
active_vio = 0;
|
||||||
#endif
|
#endif
|
||||||
pthread_mutex_init(&LOCK_thd_data, MY_MUTEX_INIT_FAST);
|
pthread_mutex_init(&LOCK_thd_data, MY_MUTEX_INIT_FAST);
|
||||||
|
pthread_mutex_init(&LOCK_thd_kill, MY_MUTEX_INIT_FAST);
|
||||||
|
|
||||||
/* Variables with default values */
|
/* Variables with default values */
|
||||||
proc_info="login";
|
proc_info="login";
|
||||||
|
@ -999,6 +1001,8 @@ THD::~THD()
|
||||||
/* Ensure that no one is using THD */
|
/* Ensure that no one is using THD */
|
||||||
pthread_mutex_lock(&LOCK_thd_data);
|
pthread_mutex_lock(&LOCK_thd_data);
|
||||||
pthread_mutex_unlock(&LOCK_thd_data);
|
pthread_mutex_unlock(&LOCK_thd_data);
|
||||||
|
pthread_mutex_lock(&LOCK_thd_kill);
|
||||||
|
pthread_mutex_unlock(&LOCK_thd_kill);
|
||||||
add_to_status(&global_status_var, &status_var);
|
add_to_status(&global_status_var, &status_var);
|
||||||
|
|
||||||
/* Close connection */
|
/* Close connection */
|
||||||
|
@ -1026,6 +1030,7 @@ THD::~THD()
|
||||||
#endif
|
#endif
|
||||||
mysys_var=0; // Safety (shouldn't be needed)
|
mysys_var=0; // Safety (shouldn't be needed)
|
||||||
pthread_mutex_destroy(&LOCK_thd_data);
|
pthread_mutex_destroy(&LOCK_thd_data);
|
||||||
|
pthread_mutex_destroy(&LOCK_thd_kill);
|
||||||
#ifndef DBUG_OFF
|
#ifndef DBUG_OFF
|
||||||
dbug_sentry= THD_SENTRY_GONE;
|
dbug_sentry= THD_SENTRY_GONE;
|
||||||
#endif
|
#endif
|
||||||
|
@ -1104,9 +1109,11 @@ void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var,
|
||||||
void THD::awake(THD::killed_state state_to_set)
|
void THD::awake(THD::killed_state state_to_set)
|
||||||
{
|
{
|
||||||
DBUG_ENTER("THD::awake");
|
DBUG_ENTER("THD::awake");
|
||||||
DBUG_PRINT("enter", ("this: 0x%lx", (long) this));
|
DBUG_PRINT("enter", ("this: 0x%lx thread_id=%lu killed_state=%d",
|
||||||
|
(long) this, thread_id, state_to_set));
|
||||||
THD_CHECK_SENTRY(this);
|
THD_CHECK_SENTRY(this);
|
||||||
safe_mutex_assert_owner(&LOCK_thd_data);
|
safe_mutex_assert_not_owner(&LOCK_thd_data);
|
||||||
|
safe_mutex_assert_owner(&LOCK_thd_kill);
|
||||||
|
|
||||||
killed= state_to_set;
|
killed= state_to_set;
|
||||||
if (state_to_set != THD::KILL_QUERY)
|
if (state_to_set != THD::KILL_QUERY)
|
||||||
|
@ -1127,7 +1134,9 @@ void THD::awake(THD::killed_state state_to_set)
|
||||||
hack is not used.
|
hack is not used.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pthread_mutex_lock(&LOCK_thd_data);
|
||||||
close_active_vio();
|
close_active_vio();
|
||||||
|
pthread_mutex_unlock(&LOCK_thd_data);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -1342,11 +1342,23 @@ public:
|
||||||
Protects THD data accessed from other threads:
|
Protects THD data accessed from other threads:
|
||||||
- thd->query and thd->query_length (used by SHOW ENGINE
|
- thd->query and thd->query_length (used by SHOW ENGINE
|
||||||
INNODB STATUS and SHOW PROCESSLIST
|
INNODB STATUS and SHOW PROCESSLIST
|
||||||
- thd->mysys_var (used by KILL statement and shutdown).
|
|
||||||
Is locked when THD is deleted.
|
|
||||||
*/
|
*/
|
||||||
pthread_mutex_t LOCK_thd_data;
|
pthread_mutex_t LOCK_thd_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
- Protects thd->mysys_var (used during KILL statement and shutdown).
|
||||||
|
- Is Locked when THD is deleted.
|
||||||
|
|
||||||
|
Note: This responsibility was earlier handled by LOCK_thd_data.
|
||||||
|
This lock is introduced to solve a deadlock issue waiting for
|
||||||
|
LOCK_thd_data. As this lock reduces responsibility of LOCK_thd_data
|
||||||
|
the deadlock issues is solved.
|
||||||
|
Caution: LOCK_thd_kill should not be taken while holding LOCK_thd_data.
|
||||||
|
THD::awake() currently takes LOCK_thd_data after holding
|
||||||
|
LOCK_thd_kill.
|
||||||
|
*/
|
||||||
|
pthread_mutex_t LOCK_thd_kill;
|
||||||
|
|
||||||
/* all prepared statements and cursors of this connection */
|
/* all prepared statements and cursors of this connection */
|
||||||
Statement_map stmt_map;
|
Statement_map stmt_map;
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -7187,7 +7187,7 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query)
|
||||||
continue;
|
continue;
|
||||||
if (tmp->thread_id == id)
|
if (tmp->thread_id == id)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
|
pthread_mutex_lock(&tmp->LOCK_thd_kill); // Lock from delete
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7215,12 +7215,13 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query)
|
||||||
if ((thd->security_ctx->master_access & SUPER_ACL) ||
|
if ((thd->security_ctx->master_access & SUPER_ACL) ||
|
||||||
thd->security_ctx->user_matches(tmp->security_ctx))
|
thd->security_ctx->user_matches(tmp->security_ctx))
|
||||||
{
|
{
|
||||||
|
DEBUG_SYNC(thd, "kill_one_thread_before_kill");
|
||||||
tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION);
|
tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION);
|
||||||
error=0;
|
error=0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
error=ER_KILL_DENIED_ERROR;
|
error=ER_KILL_DENIED_ERROR;
|
||||||
pthread_mutex_unlock(&tmp->LOCK_thd_data);
|
pthread_mutex_unlock(&tmp->LOCK_thd_kill);
|
||||||
}
|
}
|
||||||
DBUG_PRINT("exit", ("%d", error));
|
DBUG_PRINT("exit", ("%d", error));
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
|
|
|
@ -1133,7 +1133,7 @@ void kill_zombie_dump_threads(uint32 slave_server_id)
|
||||||
if (tmp->command == COM_BINLOG_DUMP &&
|
if (tmp->command == COM_BINLOG_DUMP &&
|
||||||
tmp->server_id == slave_server_id)
|
tmp->server_id == slave_server_id)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
|
pthread_mutex_lock(&tmp->LOCK_thd_kill); // Lock from delete
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1146,7 +1146,7 @@ void kill_zombie_dump_threads(uint32 slave_server_id)
|
||||||
again. We just to do kill the thread ourselves.
|
again. We just to do kill the thread ourselves.
|
||||||
*/
|
*/
|
||||||
tmp->awake(THD::KILL_QUERY);
|
tmp->awake(THD::KILL_QUERY);
|
||||||
pthread_mutex_unlock(&tmp->LOCK_thd_data);
|
pthread_mutex_unlock(&tmp->LOCK_thd_kill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue