Commit e8c1b35f authored by Monty's avatar Monty

MDEV-8476 Race condition in slave SQL thread shutdown

Patch backported from MariaDB 10.1

- Ensure that we wait with cleanup() until slave thread has stopped.
- Added signal_thd_deleted() to signal close_connections() that all THD's has been freed.

Other things
- Removed not needed calls to THD_CHECK_SENTRY() when we are calling 'delete thd'.
parent 83ed38d9
...@@ -475,7 +475,7 @@ ulong delay_key_write_options; ...@@ -475,7 +475,7 @@ ulong delay_key_write_options;
uint protocol_version; uint protocol_version;
uint lower_case_table_names; uint lower_case_table_names;
ulong tc_heuristic_recover= 0; ulong tc_heuristic_recover= 0;
int32 thread_count; int32 thread_count, service_thread_count;
int32 thread_running; int32 thread_running;
int32 slave_open_temp_tables; int32 slave_open_temp_tables;
ulong thread_created; ulong thread_created;
...@@ -1708,7 +1708,7 @@ static void close_connections(void) ...@@ -1708,7 +1708,7 @@ static void close_connections(void)
/* All threads has now been aborted */ /* All threads has now been aborted */
DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count)); DBUG_PRINT("quit",("Waiting for threads to die (count=%u)",thread_count));
mysql_mutex_lock(&LOCK_thread_count); mysql_mutex_lock(&LOCK_thread_count);
while (thread_count) while (thread_count || service_thread_count)
{ {
mysql_cond_wait(&COND_thread_count, &LOCK_thread_count); mysql_cond_wait(&COND_thread_count, &LOCK_thread_count);
DBUG_PRINT("quit",("One thread died (count=%u)",thread_count)); DBUG_PRINT("quit",("One thread died (count=%u)",thread_count));
...@@ -2730,8 +2730,27 @@ void delete_running_thd(THD *thd) ...@@ -2730,8 +2730,27 @@ void delete_running_thd(THD *thd)
delete thd; delete thd;
dec_thread_running(); dec_thread_running();
thread_safe_decrement32(&thread_count, &thread_count_lock); thread_safe_decrement32(&thread_count, &thread_count_lock);
if (!thread_count) signal_thd_deleted();
}
/*
Send a signal to unblock close_conneciton() if there is no more
threads running with a THD attached
It's safe to check for thread_count and service_thread_count outside
of a mutex as we are only interested to see if they where decremented
to 0 by a previous unlink_thd() call.
We should only signal COND_thread_count if both variables are 0,
false positives are ok.
*/
void signal_thd_deleted()
{
if (!thread_count && ! service_thread_count)
{ {
/* Signal close_connections() that all THD's are freed */
mysql_mutex_lock(&LOCK_thread_count); mysql_mutex_lock(&LOCK_thread_count);
mysql_cond_broadcast(&COND_thread_count); mysql_cond_broadcast(&COND_thread_count);
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
...@@ -2887,19 +2906,7 @@ bool one_thread_per_connection_end(THD *thd, bool put_in_cache) ...@@ -2887,19 +2906,7 @@ bool one_thread_per_connection_end(THD *thd, bool put_in_cache)
if (put_in_cache && cache_thread()) if (put_in_cache && cache_thread())
DBUG_RETURN(0); // Thread is reused DBUG_RETURN(0); // Thread is reused
/* signal_thd_deleted();
It's safe to check for thread_count outside of the mutex
as we are only interested to see if it was counted to 0 by the
above unlink_thd() call. We should only signal COND_thread_count if
thread_count is likely to be 0. (false positives are ok)
*/
if (!thread_count)
{
mysql_mutex_lock(&LOCK_thread_count);
DBUG_PRINT("signal", ("Broadcasting COND_thread_count"));
mysql_cond_broadcast(&COND_thread_count);
mysql_mutex_unlock(&LOCK_thread_count);
}
DBUG_LEAVE; // Must match DBUG_ENTER() DBUG_LEAVE; // Must match DBUG_ENTER()
#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY)
ERR_remove_state(0); ERR_remove_state(0);
...@@ -8185,7 +8192,8 @@ static int mysql_init_variables(void) ...@@ -8185,7 +8192,8 @@ static int mysql_init_variables(void)
cleanup_done= 0; cleanup_done= 0;
server_id_supplied= 0; server_id_supplied= 0;
test_flags= select_errors= dropping_tables= ha_open_options=0; test_flags= select_errors= dropping_tables= ha_open_options=0;
thread_count= thread_running= kill_cached_threads= wake_thread=0; thread_count= thread_running= kill_cached_threads= wake_thread= 0;
service_thread_count= 0;
slave_open_temp_tables= 0; slave_open_temp_tables= 0;
cached_thread_count= 0; cached_thread_count= 0;
opt_endinfo= using_udf_functions= 0; opt_endinfo= using_udf_functions= 0;
......
...@@ -59,6 +59,7 @@ void close_connection(THD *thd, uint sql_errno= 0); ...@@ -59,6 +59,7 @@ void close_connection(THD *thd, uint sql_errno= 0);
void handle_connection_in_main_thread(THD *thd); void handle_connection_in_main_thread(THD *thd);
void create_thread_to_handle_connection(THD *thd); void create_thread_to_handle_connection(THD *thd);
void delete_running_thd(THD *thd); void delete_running_thd(THD *thd);
void signal_thd_deleted();
void unlink_thd(THD *thd); void unlink_thd(THD *thd);
bool one_thread_per_connection_end(THD *thd, bool put_in_cache); bool one_thread_per_connection_end(THD *thd, bool put_in_cache);
void flush_thread_cache(); void flush_thread_cache();
...@@ -538,7 +539,7 @@ extern mysql_cond_t COND_thread_count; ...@@ -538,7 +539,7 @@ extern mysql_cond_t COND_thread_count;
extern mysql_cond_t COND_manager; extern mysql_cond_t COND_manager;
extern mysql_cond_t COND_slave_init; extern mysql_cond_t COND_slave_init;
extern int32 thread_running; extern int32 thread_running;
extern int32 thread_count; extern int32 thread_count, service_thread_count;
extern my_atomic_rwlock_t thread_running_lock, thread_count_lock; extern my_atomic_rwlock_t thread_running_lock, thread_count_lock;
extern my_atomic_rwlock_t slave_executed_entries_lock; extern my_atomic_rwlock_t slave_executed_entries_lock;
......
...@@ -294,6 +294,7 @@ handle_slave_init(void *arg __attribute__((unused))) ...@@ -294,6 +294,7 @@ handle_slave_init(void *arg __attribute__((unused)))
thd->thread_id= thread_id++; thd->thread_id= thread_id++;
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
thd->system_thread = SYSTEM_THREAD_SLAVE_INIT; thd->system_thread = SYSTEM_THREAD_SLAVE_INIT;
thread_safe_increment32(&service_thread_count, &thread_count_lock);
thd->store_globals(); thd->store_globals();
thd->security_ctx->skip_grants(); thd->security_ctx->skip_grants();
thd->set_command(COM_DAEMON); thd->set_command(COM_DAEMON);
...@@ -309,6 +310,8 @@ handle_slave_init(void *arg __attribute__((unused))) ...@@ -309,6 +310,8 @@ handle_slave_init(void *arg __attribute__((unused)))
mysql_mutex_lock(&LOCK_thread_count); mysql_mutex_lock(&LOCK_thread_count);
delete thd; delete thd;
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
thread_safe_decrement32(&service_thread_count, &thread_count_lock);
signal_thd_deleted();
my_thread_end(); my_thread_end();
mysql_mutex_lock(&LOCK_slave_init); mysql_mutex_lock(&LOCK_slave_init);
...@@ -2953,6 +2956,11 @@ static int init_slave_thread(THD* thd, Master_info *mi, ...@@ -2953,6 +2956,11 @@ static int init_slave_thread(THD* thd, Master_info *mi,
simulate_error|= (1 << SLAVE_THD_IO);); simulate_error|= (1 << SLAVE_THD_IO););
DBUG_EXECUTE_IF("simulate_sql_slave_error_on_init", DBUG_EXECUTE_IF("simulate_sql_slave_error_on_init",
simulate_error|= (1 << SLAVE_THD_SQL);); simulate_error|= (1 << SLAVE_THD_SQL););
thd->system_thread = (thd_type == SLAVE_THD_SQL) ?
SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO;
thread_safe_increment32(&service_thread_count, &thread_count_lock);
/* We must call store_globals() before doing my_net_init() */ /* We must call store_globals() before doing my_net_init() */
if (init_thr_lock() || thd->store_globals() || if (init_thr_lock() || thd->store_globals() ||
my_net_init(&thd->net, 0, MYF(MY_THREAD_SPECIFIC)) || my_net_init(&thd->net, 0, MYF(MY_THREAD_SPECIFIC)) ||
...@@ -2962,8 +2970,6 @@ static int init_slave_thread(THD* thd, Master_info *mi, ...@@ -2962,8 +2970,6 @@ static int init_slave_thread(THD* thd, Master_info *mi,
DBUG_RETURN(-1); DBUG_RETURN(-1);
} }
thd->system_thread = (thd_type == SLAVE_THD_SQL) ?
SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO;
thd->security_ctx->skip_grants(); thd->security_ctx->skip_grants();
thd->slave_thread= 1; thd->slave_thread= 1;
thd->connection_name= mi->connection_name; thd->connection_name= mi->connection_name;
...@@ -4228,11 +4234,14 @@ log space"); ...@@ -4228,11 +4234,14 @@ log space");
mi->rli.relay_log.description_event_for_queue= 0; mi->rli.relay_log.description_event_for_queue= 0;
// TODO: make rpl_status part of Master_info // TODO: make rpl_status part of Master_info
change_rpl_status(RPL_ACTIVE_SLAVE,RPL_IDLE_SLAVE); change_rpl_status(RPL_ACTIVE_SLAVE,RPL_IDLE_SLAVE);
mysql_mutex_lock(&LOCK_thread_count); mysql_mutex_lock(&LOCK_thread_count);
thd->unlink(); thd->unlink();
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
THD_CHECK_SENTRY(thd);
delete thd; delete thd;
thread_safe_decrement32(&service_thread_count, &thread_count_lock);
signal_thd_deleted();
mi->abort_slave= 0; mi->abort_slave= 0;
mi->slave_running= MYSQL_SLAVE_NOT_RUN; mi->slave_running= MYSQL_SLAVE_NOT_RUN;
mi->io_thd= 0; mi->io_thd= 0;
...@@ -4848,9 +4857,10 @@ log '%s' at position %s, relay log '%s' position: %s%s", RPL_LOG_NAME, ...@@ -4848,9 +4857,10 @@ log '%s' at position %s, relay log '%s' position: %s%s", RPL_LOG_NAME,
mysql_mutex_unlock(&LOCK_active_mi); mysql_mutex_unlock(&LOCK_active_mi);
mysql_mutex_lock(&LOCK_thread_count); mysql_mutex_lock(&LOCK_thread_count);
THD_CHECK_SENTRY(thd);
delete thd; delete thd;
mysql_mutex_unlock(&LOCK_thread_count); mysql_mutex_unlock(&LOCK_thread_count);
thread_safe_decrement32(&service_thread_count, &thread_count_lock);
signal_thd_deleted();
DBUG_LEAVE; // Must match DBUG_ENTER() DBUG_LEAVE; // Must match DBUG_ENTER()
my_thread_end(); my_thread_end();
......
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