Before, if the ioloop changed, the application had to explicitly notify lib-signals using lib_signals_reset_ioloop().
This is error-prone and requires doing this all over the Dovecot code base.
Now, lib-signals registers an ioloop switch callback that deals with this implicitly.
The application can detach lib-signals from the ioloop explicitly if delayed signal handling is not required/desired in the new ioloop.
Specific delayed signal handlers can be exempt from this automated behavior using a flag, meaning that such signal handlers need to be moved between ioloops explicitly.
doveadm_cmd_params_null_terminate_arrays(&conn->pargv);
cctx.argv = array_get(&conn->pargv, (unsigned int*)&cctx.argc);
ioloop = io_loop_create();
- lib_signals_reset_ioloop();
doveadm_exit_code = 0;
cctx.cli = FALSE;
client_connection_set_proctitle(&conn->client, "");
io_loop_set_current(prev_ioloop);
- lib_signals_reset_ioloop();
o_stream_switch_ioloop(conn->client.output);
io_loop_set_current(ioloop);
io_loop_destroy(&ioloop);
running one and we can't call the original one recursively, so
create a new ioloop. */
ioloop = io_loop_create();
- lib_signals_reset_ioloop();
if (cmd_ver2 != NULL)
doveadm_cmd_server_run_ver2(conn, argc, argv, cctx);
doveadm_mail_cmd_server_run(conn, mctx, cctx);
io_loop_set_current(prev_ioloop);
- lib_signals_reset_ioloop();
o_stream_switch_ioloop(conn->output);
io_loop_set_current(ioloop);
io_loop_destroy(&ioloop);
conn->io = io_add(conn->fd, IO_READ, auth_input, conn);
conn->to = timeout_add(1000*MASTER_AUTH_LOOKUP_TIMEOUT_SECS,
auth_request_timeout, conn);
- lib_signals_reset_ioloop();
}
static void auth_master_unset_io(struct auth_master_connection *conn)
{
if (conn->prev_ioloop != NULL) {
io_loop_set_current(conn->prev_ioloop);
- lib_signals_reset_ioloop();
}
if (conn->ioloop != NULL) {
io_loop_set_current(conn->ioloop);
if (plclient->to_kill != NULL)
plclient->to_kill = io_loop_move_timeout(&plclient->to_kill);
- lib_signals_reset_ioloop();
+ child_wait_switch_ioloop();
}
struct program_client *
struct program_client *pc =
program_client_local_create("/bin/cat", args, &pc_set);
- lib_signals_reset_ioloop();
-
struct istream *is = test_istream_create(pclient_test_io_string);
program_client_set_input(pc, is);
o_stream_unref(&os);
buffer_free(&output);
io_loop_set_current(prev_ioloop);
- lib_signals_reset_ioloop();
io_loop_set_current(ioloop);
io_loop_destroy(&ioloop);
NULL
};
+ lib_init();
struct ioloop *ioloop = io_loop_create();
lib_signals_init();
ret = test_run(tests);
lib_signals_deinit();
io_loop_destroy(&ioloop);
+ lib_deinit();
return ret;
}
test-istream-unix.c \
test-json-parser.c \
test-json-tree.c \
+ test-lib-signals.c \
test-llist.c \
test-log-throttle.c \
test-malloc-overflow.c \
i_error("waitpid() failed: %m");
}
+void child_wait_switch_ioloop(void)
+{
+ lib_signals_switch_ioloop(SIGCHLD, sigchld_handler, NULL);
+}
+
void child_wait_init(void)
{
- if (child_wait_refcount++ > 0) return;
+ if (child_wait_refcount++ > 0) {
+ child_wait_switch_ioloop();
+ return;
+ }
hash_table_create_direct(&child_pids, default_pool, 0);
- lib_signals_set_handler(SIGCHLD, LIBSIG_FLAGS_SAFE,
- sigchld_handler, NULL);
+ lib_signals_set_handler(SIGCHLD,
+ LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE,
+ sigchld_handler, NULL);
}
void child_wait_deinit(void)
struct child_wait *value;
i_assert(child_wait_refcount > 0);
- if (--child_wait_refcount > 0) return;
+ if (--child_wait_refcount > 0) {
+ child_wait_switch_ioloop();
+ return;
+ }
lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL);
void child_wait_add_pid(struct child_wait *wait, pid_t pid);
void child_wait_remove_pid(struct child_wait *wait, pid_t pid);
+void child_wait_switch_ioloop(void);
+
void child_wait_init(void);
void child_wait_deinit(void);
/* Copyright (c) 2001-2017 Dovecot authors, see the included COPYING file */
#include "lib.h"
+#include "array.h"
#include "ioloop.h"
#include "fd-close-on-exec.h"
#include "fd-set-nonblock.h"
void *context;
enum libsig_flags flags;
- struct signal_handler *next;
+ struct signal_handler *next;
+ struct ioloop *current_ioloop;
+
+ bool shadowed:1;
};
volatile unsigned int signal_term_counter = 0;
static struct io *io_sig = NULL;
static siginfo_t pending_signals[MAX_SIGNAL_VALUE+1];
+static ARRAY(siginfo_t) pending_shadowed_signals;
static bool have_pending_signals = FALSE;
+static bool ioloop_attached = FALSE;
+static bool ioloop_switched = FALSE;
const char *lib_signal_code_to_str(int signo, int sicode)
{
the system call might be restarted */
}
+static void
+signal_handle_shadowed(void)
+{
+ const siginfo_t *sis;
+ unsigned int count, i;
+
+ if (!array_is_created(&pending_shadowed_signals) ||
+ array_count(&pending_shadowed_signals) == 0)
+ return;
+
+ sis = array_get(&pending_shadowed_signals, &count);
+ for (i = 0; i < count; i++) {
+ struct signal_handler *h;
+ bool shadowed = FALSE;
+
+ i_assert(sis[i].si_signo > 0);
+ for (h = signal_handlers[sis[i].si_signo]; h != NULL; h = h->next) {
+ if ((h->flags & LIBSIG_FLAG_DELAYED) == 0 ||
+ (h->flags & LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE) == 0)
+ continue;
+ if (h->shadowed && h->current_ioloop != current_ioloop) {
+ shadowed = TRUE;
+ continue;
+ }
+ /* handler can be called now */
+ h->shadowed = FALSE;
+ h->handler(&sis[i], h->context);
+ }
+ if (!shadowed) {
+ /* no handlers are shadowed anymore; delete the signal info */
+ array_delete(&pending_shadowed_signals, i, 1);
+ sis = array_get(&pending_shadowed_signals, &count);
+ }
+ }
+}
+
+static void
+signal_check_shadowed(void)
+{
+ if (!array_is_created(&pending_shadowed_signals) ||
+ array_count(&pending_shadowed_signals) == 0)
+ return;
+
+ if (io_sig != NULL)
+ io_set_pending(io_sig);
+}
+
+static void
+signal_shadow(int signo, const siginfo_t *si)
+{
+ const siginfo_t *sis;
+ unsigned int count, i;
+
+ /* remember last signal info for handlers that cannot run in
+ current ioloop */
+ if (!array_is_created(&pending_shadowed_signals))
+ i_array_init(&pending_shadowed_signals, 4);
+ sis = array_get(&pending_shadowed_signals, &count);
+ for (i = 0; i < count; i++) {
+ i_assert(sis[i].si_signo != 0);
+ if (sis[i].si_signo == signo)
+ break;
+ }
+ array_idx_set(&pending_shadowed_signals, i, si);
+}
+
static void ATTR_NULL(1)
signal_read(void *context ATTR_UNUSED)
{
int signo;
ssize_t ret;
+ if (ioloop_switched) {
+ ioloop_switched = FALSE;
+ /* handle any delayed signal handlers that emerged from the shadow */
+ signal_handle_shadowed();
+ }
+
if (sigfillset(&fullset) < 0)
i_fatal("sigfillset() failed: %m");
if (sigprocmask(SIG_BLOCK, &fullset, &oldset) < 0)
/* call the delayed handlers after signals are copied and unblocked */
for (signo = 0; signo < MAX_SIGNAL_VALUE; signo++) {
+ bool shadowed = FALSE;
+
if (signals[signo].si_signo == 0)
continue;
for (h = signal_handlers[signo]; h != NULL; h = h->next) {
- if ((h->flags & LIBSIG_FLAG_DELAYED) != 0)
- h->handler(&signals[signo], h->context);
+ if ((h->flags & LIBSIG_FLAG_DELAYED) == 0) {
+ /* handler already called immediately in signal context */
+ continue;
+ }
+ if ((h->flags & LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE) != 0 &&
+ h->current_ioloop != current_ioloop) {
+ /* cannot run handler in current ioloop (shadowed) */
+ h->shadowed = TRUE;
+ shadowed = TRUE;
+ continue;
+ }
+ /* handler can be called now */
+ h->handler(&signals[signo], h->context);
+ }
+
+ if (shadowed) {
+ /* remember last signal info for handlers that cannot run in
+ current ioloop (shadowed) */
+ signal_shadow(signo, &signals[signo]);
}
}
}
+static void
+lib_signals_enable_delayed_hander(void)
+{
+ if (current_ioloop != NULL) {
+ io_sig = io_add(sig_pipe_fd[0], IO_READ,
+ signal_read, (void *)NULL);
+ }
+}
+
+static void
+lib_signals_disable_delayed_hander(void)
+{
+ if (io_sig != NULL)
+ io_remove(&io_sig);
+}
+
+static void
+lib_signals_ioloop_switched(struct ioloop *prev_ioloop ATTR_UNUSED)
+{
+ ioloop_switched = TRUE;
+
+ lib_signals_disable_delayed_hander();
+ lib_signals_enable_delayed_hander();
+
+ /* check whether we can now handle any shadowed delayed signals */
+ signal_check_shadowed();
+}
+
+void lib_signals_ioloop_detach(void)
+{
+ if (!ioloop_attached) {
+ i_assert(io_sig == NULL);
+ return;
+ }
+ ioloop_attached = FALSE;
+
+ lib_signals_disable_delayed_hander();
+ io_loop_remove_switch_callback(lib_signals_ioloop_switched);
+}
+
+void lib_signals_ioloop_attach(void)
+{
+ if (ioloop_attached)
+ return;
+ ioloop_attached = TRUE;
+
+ lib_signals_disable_delayed_hander();
+ lib_signals_enable_delayed_hander();
+ io_loop_add_switch_callback(lib_signals_ioloop_switched);
+}
+
static void lib_signals_set(int signo, enum libsig_flags flags)
{
struct sigaction act;
if (signal_handlers[signo] == NULL && signals_initialized)
lib_signals_set(signo, flags);
+ h = i_new(struct signal_handler, 1);
+ h->handler = handler;
+ h->context = context;
+ h->flags = flags;
+ h->current_ioloop = current_ioloop;
+
+ /* atomically set to signal_handlers[] list */
+ h->next = signal_handlers[signo];
+ signal_handlers[signo] = h;
+
if ((flags & LIBSIG_FLAG_DELAYED) != 0 && sig_pipe_fd[0] == -1) {
/* first delayed handler */
if (pipe(sig_pipe_fd) < 0)
fd_set_nonblock(sig_pipe_fd[1], TRUE);
fd_close_on_exec(sig_pipe_fd[0], TRUE);
fd_close_on_exec(sig_pipe_fd[1], TRUE);
- if (signals_initialized) {
- io_sig = io_add(sig_pipe_fd[0], IO_READ,
- signal_read, (void *)NULL);
- }
+ if (signals_initialized)
+ lib_signals_ioloop_attach();
}
-
- h = i_new(struct signal_handler, 1);
- h->handler = handler;
- h->context = context;
- h->flags = flags;
-
- /* atomically set to signal_handlers[] list */
- h->next = signal_handlers[signo];
- signal_handlers[signo] = h;
}
void lib_signals_ignore(int signo, bool restart_syscalls)
signo, (void *)handler, context);
}
-void lib_signals_reset_ioloop(void)
+void lib_signals_switch_ioloop(int signo,
+ signal_handler_t *handler, void *context)
{
- if (io_sig != NULL) {
- io_remove(&io_sig);
- io_sig = io_add(sig_pipe_fd[0], IO_READ,
- signal_read, (void *)NULL);
+ struct signal_handler *h;
+
+ for (h = signal_handlers[signo]; h != NULL; h = h->next) {
+ if (h->handler == handler && h->context == context) {
+ i_assert((h->flags & LIBSIG_FLAG_DELAYED) != 0);
+ i_assert((h->flags & LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE) != 0);
+ h->current_ioloop = current_ioloop;
+ /* check whether we can now handle any shadowed delayed signals */
+ signal_check_shadowed();
+ return;
+ }
}
+
+ i_panic("lib_signals_switch_ioloop(%d, %p, %p): handler not found",
+ signo, (void *)handler, context);
}
void lib_signals_syscall_error(const char *prefix)
lib_signals_set(i, signal_handlers[i]->flags);
}
- if (sig_pipe_fd[0] != -1) {
- io_sig = io_add(sig_pipe_fd[0], IO_READ,
- signal_read, (void *)NULL);
- }
+ if (sig_pipe_fd[0] != -1)
+ lib_signals_ioloop_attach();
}
void lib_signals_deinit(void)
}
}
- if (io_sig != NULL)
- io_remove(&io_sig);
+ lib_signals_ioloop_detach();
+
if (sig_pipe_fd[0] != -1) {
if (close(sig_pipe_fd[0]) < 0)
i_error("close(sigpipe) failed: %m");
if (close(sig_pipe_fd[1]) < 0)
i_error("close(sigpipe) failed: %m");
+ sig_pipe_fd[0] = sig_pipe_fd[1] = -1;
}
+
+ if (array_is_created(&pending_shadowed_signals))
+ array_free(&pending_shadowed_signals);
}
do any kind of work */
LIBSIG_FLAG_DELAYED = 0x01,
/* Restart syscalls instead of having them fail with EINTR */
- LIBSIG_FLAG_RESTART = 0x02
+ LIBSIG_FLAG_RESTART = 0x02,
+ /* Don't automatically shift delayed signal handling for this signal
+ to a newly started ioloop. */
+ LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE = 0x04,
};
#define LIBSIG_FLAGS_SAFE (LIBSIG_FLAG_DELAYED | LIBSIG_FLAG_RESTART)
/* Convert si_code to string */
const char *lib_signal_code_to_str(int signo, int sicode);
+void lib_signals_ioloop_detach(void);
+void lib_signals_ioloop_attach(void);
+
/* Set signal handler for specific signal. */
void lib_signals_set_handler(int signo, enum libsig_flags flags,
signal_handler_t *handler, void *context)
signal_handler_t *handler, void *context)
ATTR_NULL(3);
-/* Remove and add the internal I/O handler back. This is necessary to get
- the delayed signals to work when using multiple I/O loops. */
-void lib_signals_reset_ioloop(void);
+/* Switch ioloop for a specific signal handler created with
+ LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE. */
+void lib_signals_switch_ioloop(int signo,
+ signal_handler_t *handler, void *context);
/* Log a syscall error inside a (non-delayed) signal handler where i_error() is
unsafe. errno number will be appended to the prefix. */
--- /dev/null
+/* Copyright (c) 2015-2017 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "time-util.h"
+#include "ioloop.h"
+#include "lib-signals.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+
+struct test_context_delayed {
+ bool timed_out:1;
+ bool signal_handled:1;
+};
+
+static void
+kill_timeout(struct test_context_delayed *tctx ATTR_UNUSED)
+{
+ if (kill(getpid(), SIGALRM) < 0)
+ i_fatal("Failed to send signal: %m");
+}
+
+static void
+test_timeout(struct test_context_delayed *tctx)
+{
+ tctx->timed_out = TRUE;
+ io_loop_stop(current_ioloop);
+}
+
+static void
+signal_handler_delayed(const siginfo_t *si ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ struct test_context_delayed *tctx =
+ (struct test_context_delayed *)context;
+ tctx->signal_handled = TRUE;
+ io_loop_stop(current_ioloop);
+}
+
+static void
+test_lib_signals_delayed(void)
+{
+ struct test_context_delayed tctx;
+ struct timeout *to_kill, *to_test;
+ struct ioloop *ioloop;
+
+ test_begin("lib-signals delayed - init lib-signals first");
+
+ i_zero(&tctx);
+
+ lib_signals_init();
+ lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE,
+ signal_handler_delayed, &tctx);
+
+ ioloop = io_loop_create();
+ to_kill = timeout_add_short(200, kill_timeout, &tctx);
+ to_test = timeout_add_short(400, test_timeout, &tctx);
+ io_loop_run(ioloop);
+
+ timeout_remove(&to_kill);
+ timeout_remove(&to_test);
+ io_loop_destroy(&ioloop);
+
+ lib_signals_deinit();
+
+ test_assert(!tctx.timed_out);
+ test_assert(tctx.signal_handled);
+
+ test_end();
+
+ test_begin("lib-signals delayed - init ioloop first");
+
+ i_zero(&tctx);
+
+ ioloop = io_loop_create();
+
+ lib_signals_init();
+ lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE,
+ signal_handler_delayed, &tctx);
+
+ to_kill = timeout_add_short(200, kill_timeout, &tctx);
+ to_test = timeout_add_short(400, test_timeout, &tctx);
+ io_loop_run(ioloop);
+
+ timeout_remove(&to_kill);
+ timeout_remove(&to_test);
+
+ lib_signals_deinit();
+
+ io_loop_destroy(&ioloop);
+
+ test_assert(!tctx.timed_out);
+ test_assert(tctx.signal_handled);
+
+ test_end();
+
+}
+
+static void
+test_lib_signals_delayed_nested_ioloop(void)
+{
+ struct test_context_delayed tctx;
+ struct timeout *to_kill, *to_test;
+ struct ioloop *ioloop1, *ioloop2;
+
+ test_begin("lib-signals delayed in nested ioloop");
+
+ i_zero(&tctx);
+
+ lib_signals_init();
+ lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE,
+ signal_handler_delayed, &tctx);
+
+ /* briefly run outer ioloop */
+ ioloop1 = io_loop_create();
+ to_test = timeout_add_short(100, test_timeout, &tctx);
+ io_loop_run(ioloop1);
+ timeout_remove(&to_test);
+ test_assert(tctx.timed_out);
+ test_assert(!tctx.signal_handled);
+ tctx.timed_out = FALSE;
+
+ /* run inner ioloop, which triggers the signal */
+ ioloop2 = io_loop_create();
+ to_kill = timeout_add_short(200, kill_timeout, &tctx);
+ to_test = timeout_add_short(400, test_timeout, &tctx);
+ io_loop_run(ioloop2);
+
+ timeout_remove(&to_kill);
+ timeout_remove(&to_test);
+ io_loop_destroy(&ioloop2);
+
+ io_loop_destroy(&ioloop1);
+
+ lib_signals_deinit();
+
+ test_assert(!tctx.timed_out);
+ test_assert(tctx.signal_handled);
+
+ test_end();
+}
+
+static void
+test_lib_signals_delayed_no_ioloop_automove(void)
+{
+ struct test_context_delayed tctx;
+ struct timeout *to_kill, *to_test;
+ struct ioloop *ioloop1, *ioloop2;
+
+ test_begin("lib-signals delayed with NO_IOLOOP_AUTOMOVE - unmoved");
+
+ i_zero(&tctx);
+
+ ioloop1 = io_loop_create();
+
+ lib_signals_init();
+ lib_signals_set_handler(SIGALRM,
+ LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE,
+ signal_handler_delayed, &tctx);
+
+ /* briefly run outer ioloop */
+ to_test = timeout_add_short(100, test_timeout, &tctx);
+ io_loop_run(ioloop1);
+ timeout_remove(&to_test);
+ test_assert(tctx.timed_out);
+ test_assert(!tctx.signal_handled);
+ tctx.timed_out = FALSE;
+
+ /* run inner ioloop, which triggers the signal but musn't handle it */
+ ioloop2 = io_loop_create();
+ to_kill = timeout_add_short(200, kill_timeout, &tctx);
+ to_test = timeout_add_short(400, test_timeout, &tctx);
+ io_loop_run(ioloop2);
+
+ test_assert(tctx.timed_out);
+ test_assert(!tctx.signal_handled);
+ tctx.timed_out = FALSE;
+
+ timeout_remove(&to_kill);
+ timeout_remove(&to_test);
+ io_loop_destroy(&ioloop2);
+
+ /* run outer ioloop once more */
+ to_test = timeout_add_short(100, test_timeout, &tctx);
+ io_loop_run(ioloop1);
+ timeout_remove(&to_test);
+
+ lib_signals_deinit();
+
+ io_loop_destroy(&ioloop1);
+
+ test_assert(!tctx.timed_out);
+ test_assert(tctx.signal_handled);
+
+ test_end();
+
+ test_begin("lib-signals delayed with NO_IOLOOP_AUTOMOVE - moved");
+
+ i_zero(&tctx);
+
+ ioloop1 = io_loop_create();
+
+ lib_signals_init();
+ lib_signals_set_handler(SIGALRM,
+ LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE,
+ signal_handler_delayed, &tctx);
+
+ /* briefly run outer ioloop */
+ to_test = timeout_add_short(100, test_timeout, &tctx);
+ io_loop_run(ioloop1);
+ timeout_remove(&to_test);
+ test_assert(tctx.timed_out);
+ test_assert(!tctx.signal_handled);
+ tctx.timed_out = FALSE;
+
+ /* run inner ioloop, which triggers the signal */
+ ioloop2 = io_loop_create();
+ lib_signals_switch_ioloop(SIGALRM,
+ signal_handler_delayed, &tctx);
+
+ to_kill = timeout_add_short(200, kill_timeout, &tctx);
+ to_test = timeout_add_short(400, test_timeout, &tctx);
+ io_loop_run(ioloop2);
+
+ test_assert(!tctx.timed_out);
+ test_assert(tctx.signal_handled);
+
+ timeout_remove(&to_kill);
+ timeout_remove(&to_test);
+ io_loop_destroy(&ioloop2);
+
+ lib_signals_deinit();
+ io_loop_destroy(&ioloop1);
+
+ test_end();
+}
+
+
+void test_lib_signals(void)
+{
+ test_lib_signals_delayed();
+ test_lib_signals_delayed_nested_ioloop();
+ test_lib_signals_delayed_no_ioloop_automove();
+}
TEST(test_istream_unix)
TEST(test_json_parser)
TEST(test_json_tree)
+TEST(test_lib_signals)
TEST(test_llist)
TEST(test_log_throttle)
TEST(test_malloc_overflow)