Perform proper cleanup for signals with 'Term' action (#3057)
Issue #3049 describes a case where terminating i3 by means of SIGTERM causes it to leak the runtime directory and all its contents. There are multiple issues at play: first, any cleanup handlers registered via atexit are never invoked when a signal terminates the program (see atexit(3)). Hence, the log SHM log cleanup performed in i3_exit is not invoked in that case. Second, compared to the shutdown path for the 'exit' command, we do not unlink the UNIX domain socket we create, causing it to be leaked as well. Third, a handler for SIGTERM is not registered at all despite handle_signal claiming to be the handler for all 'Term' signals. This change addresses all three problems and results in a graceful exit including cleanup to happen when we receive a signal with the default action 'Term'. It addresses issue #3049.
This commit is contained in:
committed by
Michael Stapelberg
parent
e4d6458cc3
commit
3e34122de4
74
src/main.c
74
src/main.c
@ -174,19 +174,62 @@ static void i3_exit(void) {
|
||||
fflush(stderr);
|
||||
shm_unlink(shmlogname);
|
||||
}
|
||||
ipc_shutdown(SHUTDOWN_REASON_EXIT);
|
||||
unlink(config.ipc_socket_path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (One-shot) Handler for all signals with default action "Core", see signal(7)
|
||||
*
|
||||
* Unlinks the SHM log and re-raises the signal.
|
||||
*
|
||||
*/
|
||||
static void handle_core_signal(int sig, siginfo_t *info, void *data) {
|
||||
if (*shmlogname != '\0') {
|
||||
shm_unlink(shmlogname);
|
||||
}
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
/*
|
||||
* (One-shot) Handler for all signals with default action "Term", see signal(7)
|
||||
*
|
||||
* Unlinks the SHM log and re-raises the signal.
|
||||
* Exits the program gracefully.
|
||||
*
|
||||
*/
|
||||
static void handle_signal(int sig, siginfo_t *info, void *data) {
|
||||
if (*shmlogname != '\0') {
|
||||
shm_unlink(shmlogname);
|
||||
static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int revents) {
|
||||
/* We exit gracefully here in the sense that cleanup handlers
|
||||
* installed via atexit are invoked. */
|
||||
exit(128 + signal->signum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up handlers for all signals with default action "Term", see signal(7)
|
||||
*
|
||||
*/
|
||||
static void setup_term_handlers(void) {
|
||||
static struct ev_signal signal_watchers[6];
|
||||
size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]);
|
||||
|
||||
/* We have to rely on libev functionality here and should not use
|
||||
* sigaction handlers because we need to invoke the exit handlers
|
||||
* and cannot do so from an asynchronous signal handling context as
|
||||
* not all code triggered during exit is signal safe (and exiting
|
||||
* the main loop from said handler is not easily possible). libev's
|
||||
* signal handlers does not impose such a constraint on us. */
|
||||
ev_signal_init(&signal_watchers[0], handle_term_signal, SIGHUP);
|
||||
ev_signal_init(&signal_watchers[1], handle_term_signal, SIGINT);
|
||||
ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM);
|
||||
ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM);
|
||||
ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1);
|
||||
ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1);
|
||||
for (size_t i = 0; i < num_watchers; i++) {
|
||||
ev_signal_start(main_loop, &signal_watchers[i]);
|
||||
/* The signal handlers should not block ev_run from returning
|
||||
* and so none of the signal handlers should hold a reference to
|
||||
* the main loop. */
|
||||
ev_unref(main_loop);
|
||||
}
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@ -853,15 +896,15 @@ int main(int argc, char *argv[]) {
|
||||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_signal;
|
||||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
if (!disable_signalhandler)
|
||||
setup_signal_handler();
|
||||
else {
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_core_signal;
|
||||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
/* Catch all signals with default action "Core", see signal(7) */
|
||||
if (sigaction(SIGQUIT, &action, NULL) == -1 ||
|
||||
sigaction(SIGILL, &action, NULL) == -1 ||
|
||||
@ -871,14 +914,7 @@ int main(int argc, char *argv[]) {
|
||||
ELOG("Could not setup signal handler.\n");
|
||||
}
|
||||
|
||||
/* Catch all signals with default action "Term", see signal(7) */
|
||||
if (sigaction(SIGHUP, &action, NULL) == -1 ||
|
||||
sigaction(SIGINT, &action, NULL) == -1 ||
|
||||
sigaction(SIGALRM, &action, NULL) == -1 ||
|
||||
sigaction(SIGUSR1, &action, NULL) == -1 ||
|
||||
sigaction(SIGUSR2, &action, NULL) == -1)
|
||||
ELOG("Could not setup signal handler.\n");
|
||||
|
||||
setup_term_handlers();
|
||||
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
|
||||
* while we are sending them a message */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
Reference in New Issue
Block a user