Commit 2f5174e5 authored by Vladislav Vaintroub's avatar Vladislav Vaintroub

MDEV-33075 Resolve server shutdown issues on macOS, Solaris, and FreeBSD

This commit addresses multiple server shutdown problems observed on macOS,
Solaris, and FreeBSD:

1. Corrected a non-portable assumption where socket shutdown was expected
to wake up poll() with listening sockets in the main thread.

Use more robust self-pipe to wake up poll() by writing to the pipe's write
end.

2. Fixed a random crash on macOS from pthread_kill(signal_handler)
when the signal_handler was detached and the thread had already exited.

Use more robust `kill(getpid(), SIGTERM)` to wake up the signal handler
thread.

3. Made sure, that signal handler thread always exits once `abort_loop` is
set, and also calls `my_thread_end()` and clears `signal_thread_in_use`
when exiting.

This fixes warning "1 thread did not exit"  by `my_global_thread_end()`
seen on FreeBSD/macOS when the process is terminated via signal.

Additionally, the shutdown code underwent light refactoring
for better readability and maintainability:
- Modified `break_connect_loop()` to no longer wait for the main thread,
  aligning behavior with Windows (since 10.4).
- Removed dead code related to the unused `USE_ONE_SIGNAL_HAND`
  preprocessor constant.
- Eliminated support for `#ifndef HAVE_POLL` in `handle_connection_sockets`
  This code is also dead, since 10.4
parent b0e77c08
...@@ -134,9 +134,6 @@ ...@@ -134,9 +134,6 @@
#endif #endif
#include <my_service_manager.h> #include <my_service_manager.h>
#ifdef __linux__
#include <sys/eventfd.h>
#endif
#include <source_revision.h> #include <source_revision.h>
...@@ -1396,6 +1393,8 @@ struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD() ...@@ -1396,6 +1393,8 @@ struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD()
Dynamic_array<MYSQL_SOCKET> listen_sockets(PSI_INSTRUMENT_MEM, 0); Dynamic_array<MYSQL_SOCKET> listen_sockets(PSI_INSTRUMENT_MEM, 0);
bool unix_sock_is_online= false; bool unix_sock_is_online= false;
static int systemd_sock_activation; /* systemd socket activation */ static int systemd_sock_activation; /* systemd socket activation */
/** wakeup listening(main) thread by writing to this descriptor */
static int termination_event_fd= -1; static int termination_event_fd= -1;
...@@ -1658,66 +1657,21 @@ static my_bool warn_threads_active_after_phase_2(THD *thd, void *) ...@@ -1658,66 +1657,21 @@ static my_bool warn_threads_active_after_phase_2(THD *thd, void *)
static void break_connect_loop() static void break_connect_loop()
{ {
#ifdef EXTRA_DEBUG
int count=0;
#endif
abort_loop= 1; abort_loop= 1;
#if defined(_WIN32) #if defined(_WIN32)
mysqld_win_initiate_shutdown(); mysqld_win_initiate_shutdown();
#else #else
/* Avoid waiting for ourselves when thread-handling=no-threads. */
if (pthread_equal(pthread_self(), select_thread))
return;
DBUG_PRINT("quit", ("waiting for select thread: %lu",
(ulong)select_thread));
mysql_mutex_lock(&LOCK_start_thread); mysql_mutex_lock(&LOCK_start_thread);
while (select_thread_in_use) if (termination_event_fd >= 0)
{
struct timespec abstime;
int UNINIT_VAR(error);
DBUG_PRINT("info",("Waiting for select thread"));
{
/*
Cannot call shutdown on systemd socket activated descriptors
as their clone in the pid 1 is reused.
*/
int lowest_fd, shutdowns= 0;
lowest_fd= sd_listen_fds(0) + SD_LISTEN_FDS_START;
for(size_t i= 0; i < listen_sockets.size(); i++)
{
int fd= mysql_socket_getfd(listen_sockets.at(i));
if (fd >= lowest_fd)
{
shutdowns++;
mysql_socket_shutdown(listen_sockets.at(i), SHUT_RDWR);
}
}
if (!shutdowns && termination_event_fd >=0)
{ {
uint64_t u= 1; uint64_t u= 1;
if (write(termination_event_fd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) if(write(termination_event_fd, &u, sizeof(uint64_t)) < 0)
sql_print_error("Couldn't send event to terminate listen loop");
}
}
set_timespec(abstime, 2);
for (uint tmp=0 ; tmp < 10 && select_thread_in_use; tmp++)
{ {
error= mysql_cond_timedwait(&COND_start_thread, &LOCK_start_thread, sql_print_error("Couldn't send event to terminate listen loop");
&abstime); abort();
if (error != EINTR)
break;
} }
#ifdef EXTRA_DEBUG
if (error != 0 && error != ETIMEDOUT && !count++)
sql_print_error("Got error %d from mysql_cond_timedwait", error);
#endif
} }
if (termination_event_fd >= 0)
close(termination_event_fd);
mysql_mutex_unlock(&LOCK_start_thread); mysql_mutex_unlock(&LOCK_start_thread);
#endif /* _WIN32 */ #endif /* _WIN32 */
} }
...@@ -1963,11 +1917,6 @@ extern "C" void unireg_abort(int exit_code) ...@@ -1963,11 +1917,6 @@ extern "C" void unireg_abort(int exit_code)
static void mysqld_exit(int exit_code) static void mysqld_exit(int exit_code)
{ {
DBUG_ENTER("mysqld_exit"); DBUG_ENTER("mysqld_exit");
/*
Important note: we wait for the signal thread to end,
but if a kill -15 signal was sent, the signal thread did
spawn the kill_server_thread thread, which is running concurrently.
*/
rpl_deinit_gtid_waiting(); rpl_deinit_gtid_waiting();
rpl_deinit_gtid_slave_state(); rpl_deinit_gtid_slave_state();
wait_for_signal_thread_to_end(); wait_for_signal_thread_to_end();
...@@ -2124,17 +2073,17 @@ static void clean_up(bool print_message) ...@@ -2124,17 +2073,17 @@ static void clean_up(bool print_message)
*/ */
static void wait_for_signal_thread_to_end() static void wait_for_signal_thread_to_end()
{ {
uint i; #ifndef _WIN32
/* /*
Wait up to 10 seconds for signal thread to die. We use this mainly to Wait up to 10 seconds for signal thread to die. We use this mainly to
avoid getting warnings that my_thread_end has not been called avoid getting warnings that my_thread_end has not been called
*/ */
for (i= 0 ; i < 100 && signal_thread_in_use; i++) for (uint i= 0 ; i < 100 && signal_thread_in_use; i++)
{ {
if (pthread_kill(signal_thread, MYSQL_KILL_SIGNAL) == ESRCH) kill(getpid(), MYSQL_KILL_SIGNAL);
break;
my_sleep(100); // Give it time to die my_sleep(100); // Give it time to die
} }
#endif
} }
#endif /*EMBEDDED_LIBRARY*/ #endif /*EMBEDDED_LIBRARY*/
...@@ -2676,9 +2625,6 @@ static void use_systemd_activated_sockets() ...@@ -2676,9 +2625,6 @@ static void use_systemd_activated_sockets()
listen_sockets.push(sock); listen_sockets.push(sock);
} }
systemd_sock_activation= 1; systemd_sock_activation= 1;
termination_event_fd= eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK);
if (termination_event_fd == -1)
sql_print_warning("eventfd failed %d", errno);
free(names); free(names);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
...@@ -3232,22 +3178,9 @@ static void start_signal_handler(void) ...@@ -3232,22 +3178,9 @@ static void start_signal_handler(void)
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
#if defined(USE_ONE_SIGNAL_HAND)
pthread_handler_t kill_server_thread(void *arg __attribute__((unused)))
{
my_thread_init(); // Initialize new thread
break_connect_loop();
my_thread_end();
pthread_exit(0);
return 0;
}
#endif
/** This threads handles all signals */ /** This threads handles all signals */
/* ARGSUSED */ /* ARGSUSED */
pthread_handler_t signal_hand(void *arg __attribute__((unused))) pthread_handler_t signal_hand(void *)
{ {
sigset_t set; sigset_t set;
int sig; int sig;
...@@ -3291,20 +3224,17 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) ...@@ -3291,20 +3224,17 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused)))
int error; int error;
int origin; int origin;
if (abort_loop)
break;
while ((error= my_sigwait(&set, &sig, &origin)) == EINTR) /* no-op */; while ((error= my_sigwait(&set, &sig, &origin)) == EINTR) /* no-op */;
if (cleanup_done)
{ if (abort_loop)
DBUG_PRINT("quit",("signal_handler: calling my_thread_end()")); break;
my_thread_end();
DBUG_LEAVE; // Must match DBUG_ENTER()
signal_thread_in_use= 0;
pthread_exit(0); // Safety
return 0; // Avoid compiler warnings
}
switch (sig) { switch (sig) {
case SIGTERM: case SIGTERM:
case SIGQUIT: case SIGQUIT:
case SIGKILL:
#ifdef EXTRA_DEBUG #ifdef EXTRA_DEBUG
sql_print_information("Got signal %d to shutdown server",sig); sql_print_information("Got signal %d to shutdown server",sig);
#endif #endif
...@@ -3312,31 +3242,15 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) ...@@ -3312,31 +3242,15 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused)))
logger.set_handlers(global_system_variables.sql_log_slow ? LOG_FILE:LOG_NONE, logger.set_handlers(global_system_variables.sql_log_slow ? LOG_FILE:LOG_NONE,
opt_log ? LOG_FILE:LOG_NONE); opt_log ? LOG_FILE:LOG_NONE);
DBUG_PRINT("info",("Got signal: %d abort_loop: %d",sig,abort_loop)); DBUG_PRINT("info",("Got signal: %d abort_loop: %d",sig,abort_loop));
if (!abort_loop)
{
/* Delete the instrumentation for the signal thread */
PSI_CALL_delete_current_thread();
#ifdef USE_ONE_SIGNAL_HAND
pthread_t tmp;
if (unlikely((error= mysql_thread_create(0, /* Not instrumented */
&tmp, &connection_attrib,
kill_server_thread,
(void*) &sig))))
sql_print_error("Can't create thread to kill server (errno= %d)",
error);
#else
my_sigset(sig, SIG_IGN);
break_connect_loop(); break_connect_loop();
#endif DBUG_ASSERT(abort_loop);
}
break; break;
case SIGHUP: case SIGHUP:
#if defined(SI_KERNEL) #if defined(SI_KERNEL)
if (!abort_loop && origin != SI_KERNEL) if (origin != SI_KERNEL)
#elif defined(SI_USER) #elif defined(SI_USER)
if (!abort_loop && origin <= SI_USER) if (origin <= SI_USER)
#else
if (!abort_loop)
#endif #endif
{ {
int not_used; int not_used;
...@@ -3363,6 +3277,11 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) ...@@ -3363,6 +3277,11 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused)))
break; /* purecov: tested */ break; /* purecov: tested */
} }
} }
DBUG_PRINT("quit", ("signal_handler: calling my_thread_end()"));
my_thread_end();
DBUG_LEAVE; // Must match DBUG_ENTER()
signal_thread_in_use= 0;
pthread_exit(0); // Safety
return(0); /* purecov: deadcode */ return(0); /* purecov: deadcode */
} }
...@@ -6270,16 +6189,11 @@ void handle_connections_sockets() ...@@ -6270,16 +6189,11 @@ void handle_connections_sockets()
uint error_count=0; uint error_count=0;
struct sockaddr_storage cAddr; struct sockaddr_storage cAddr;
int retval; int retval;
#ifdef HAVE_POLL
// for ip_sock, unix_sock and extra_ip_sock // for ip_sock, unix_sock and extra_ip_sock
Dynamic_array<struct pollfd> fds(PSI_INSTRUMENT_MEM); Dynamic_array<struct pollfd> fds(PSI_INSTRUMENT_MEM);
#else
fd_set readFDs,clientFDs;
#endif
DBUG_ENTER("handle_connections_sockets"); DBUG_ENTER("handle_connections_sockets");
#ifdef HAVE_POLL
for (size_t i= 0; i < listen_sockets.size(); i++) for (size_t i= 0; i < listen_sockets.size(); i++)
{ {
struct pollfd local_fds; struct pollfd local_fds;
...@@ -6289,27 +6203,25 @@ void handle_connections_sockets() ...@@ -6289,27 +6203,25 @@ void handle_connections_sockets()
fds.push(local_fds); fds.push(local_fds);
set_non_blocking_if_supported(listen_sockets.at(i)); set_non_blocking_if_supported(listen_sockets.at(i));
} }
#else int termination_fds[2];
FD_ZERO(&clientFDs); if (pipe(termination_fds))
for (size_t i= 0; i < listen_sockets.size(); i++)
{ {
int fd= mysql_socket_getfd(listen_sockets.at(i)); sql_print_error("pipe() failed %d", errno);
FD_SET(fd, &clientFDs); DBUG_VOID_RETURN;
set_non_blocking_if_supported(listen_sockets.at(i));
} }
#ifdef FD_CLOEXEC
for (int fd : termination_fds)
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif #endif
if (termination_event_fd >= 0)
{ mysql_mutex_lock(&LOCK_start_thread);
#ifdef HAVE_POLL termination_event_fd= termination_fds[1];
mysql_mutex_unlock(&LOCK_start_thread);
struct pollfd event_fd; struct pollfd event_fd;
event_fd.fd= termination_event_fd; event_fd.fd= termination_fds[0];
event_fd.events= POLLIN; event_fd.events= POLLIN;
fds.push(event_fd); fds.push(event_fd);
#else
FD_SET(termination_event_fd, &clientFDs);
#endif
/* no need to read this fd, abrt_loop is set before it gets a chance */
}
sd_notify(0, "READY=1\n" sd_notify(0, "READY=1\n"
"STATUS=Taking your SQL requests now...\n"); "STATUS=Taking your SQL requests now...\n");
...@@ -6317,12 +6229,7 @@ void handle_connections_sockets() ...@@ -6317,12 +6229,7 @@ void handle_connections_sockets()
DBUG_PRINT("general",("Waiting for connections.")); DBUG_PRINT("general",("Waiting for connections."));
while (!abort_loop) while (!abort_loop)
{ {
#ifdef HAVE_POLL
retval= poll(fds.get_pos(0), fds.size(), -1); retval= poll(fds.get_pos(0), fds.size(), -1);
#else
readFDs=clientFDs;
retval= select(FD_SETSIZE, &readFDs, NULL, NULL, NULL);
#endif
if (retval < 0) if (retval < 0)
{ {
...@@ -6344,7 +6251,6 @@ void handle_connections_sockets() ...@@ -6344,7 +6251,6 @@ void handle_connections_sockets()
break; break;
/* Is this a new connection request ? */ /* Is this a new connection request ? */
#ifdef HAVE_POLL
for (size_t i= 0; i < fds.size(); ++i) for (size_t i= 0; i < fds.size(); ++i)
{ {
if (fds.at(i).revents & POLLIN) if (fds.at(i).revents & POLLIN)
...@@ -6353,16 +6259,6 @@ void handle_connections_sockets() ...@@ -6353,16 +6259,6 @@ void handle_connections_sockets()
break; break;
} }
} }
#else // HAVE_POLL
for (size_t i=0; i < listen_sockets.size(); i++)
{
if (FD_ISSET(mysql_socket_getfd(listen_sockets.at(i)), &readFDs))
{
sock= listen_sockets.at(i);
break;
}
}
#endif // HAVE_POLL
for (uint retry=0; retry < MAX_ACCEPT_RETRY && !abort_loop; retry++) for (uint retry=0; retry < MAX_ACCEPT_RETRY && !abort_loop; retry++)
{ {
...@@ -6390,6 +6286,12 @@ void handle_connections_sockets() ...@@ -6390,6 +6286,12 @@ void handle_connections_sockets()
} }
} }
} }
mysql_mutex_lock(&LOCK_start_thread);
for(int fd : termination_fds)
close(fd);
termination_event_fd= -1;
mysql_mutex_unlock(&LOCK_start_thread);
sd_notify(0, "STOPPING=1\n" sd_notify(0, "STOPPING=1\n"
"STATUS=Shutdown in progress\n"); "STATUS=Shutdown in progress\n");
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
......
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