]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: lib-signals: Removed explicit notification of ioloop change through lib_signals_...
authorStephan Bosch <stephan.bosch@dovecot.fi>
Thu, 2 Mar 2017 22:13:48 +0000 (23:13 +0100)
committerTimo Sirainen <tss@dovecot.fi>
Thu, 7 Sep 2017 08:34:08 +0000 (11:34 +0300)
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.

12 files changed:
src/doveadm/client-connection-http.c
src/doveadm/client-connection.c
src/lib-auth/auth-master.c
src/lib-program-client/program-client-local.c
src/lib-program-client/test-program-client-local.c
src/lib/Makefile.am
src/lib/child-wait.c
src/lib/child-wait.h
src/lib/lib-signals.c
src/lib/lib-signals.h
src/lib/test-lib-signals.c [new file with mode: 0644]
src/lib/test-lib.inc

index d12a8aabfbaed7299633ab2893343518985c9cb6..9dbbb5b972fd7a7b94ff30481031da8efe7f1d01 100644 (file)
@@ -360,7 +360,6 @@ doveadm_http_server_command_execute(struct client_connection_http *conn)
        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;
@@ -378,7 +377,6 @@ doveadm_http_server_command_execute(struct client_connection_http *conn)
        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);
index d98c6564d3901e2d875980ad4c73be19132ee44c..3deca328d6f5f5c987294390205c76818712b66c 100644 (file)
@@ -237,7 +237,6 @@ static int doveadm_cmd_handle(struct client_connection *conn,
           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);
@@ -247,7 +246,6 @@ static int doveadm_cmd_handle(struct client_connection *conn,
                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);
index 6f8bde4e18d993c9a71e0ef9a54cd7e834bf30ae..0b611c317458a8dbb54c00b20dc7cf62d2e97068 100644 (file)
@@ -348,14 +348,12 @@ static void auth_master_set_io(struct auth_master_connection *conn)
        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);
index d5ce9d02355153f0a620e253d59c755fade00ef7..f86557270bcc64cf2ed380b5ba145596ad1653dc 100644 (file)
@@ -466,7 +466,7 @@ void program_client_local_switch_ioloop(struct program_client *pclient)
 
        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 *
index addf0867a1cf3dfbd7c4adcb5d46d3d8d2604a10..6cecb0ad14aa0216cc7c8734abb279ce375a7ec9 100644 (file)
@@ -113,8 +113,6 @@ void test_program_io_async(void) {
        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);
 
@@ -135,7 +133,6 @@ void test_program_io_async(void) {
        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);
 
@@ -180,10 +177,12 @@ int main(void)
                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;
 }
index f1dbf56842051a4d01deeb21b0649de4975e4596..8791f1fc62b99282b7a4cbe454f96e91ea1ba876 100644 (file)
@@ -355,6 +355,7 @@ test_lib_SOURCES = \
        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 \
index 64593193d40451bea131b2d08509af7e8869ab0f..7c0b2546284684cfbe736c7401ae3235822e34bd 100644 (file)
@@ -90,14 +90,23 @@ sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
                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)
@@ -107,7 +116,10 @@ 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);
 
index aa83b25fa0bf027a7dff42f90c171912b7c80f2f..6d19f734c0e21eeccff24e0a1b489425af8014eb 100644 (file)
@@ -26,6 +26,8 @@ void child_wait_free(struct child_wait **wait);
 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);
 
index f2b1ee3c369a6fd8cb667ffdec3bd77ba6828737..be9de2d61436304b23554022d99cc67481defdac 100644 (file)
@@ -1,6 +1,7 @@
 /* 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"
@@ -27,7 +28,10 @@ struct signal_handler {
        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;
@@ -42,7 +46,10 @@ static bool signals_initialized = FALSE;
 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)
 {
@@ -153,6 +160,72 @@ static void sig_ignore(int signo ATTR_UNUSED)
           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)
 {
@@ -163,6 +236,12 @@ 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)
@@ -189,16 +268,86 @@ signal_read(void *context ATTR_UNUSED)
 
        /* 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;
@@ -233,6 +382,16 @@ void lib_signals_set_handler(int signo, enum libsig_flags flags,
        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)
@@ -241,20 +400,9 @@ void lib_signals_set_handler(int signo, enum libsig_flags flags,
                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)
@@ -305,13 +453,24 @@ void lib_signals_unset_handler(int signo, signal_handler_t *handler,
                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)
@@ -346,10 +505,8 @@ void lib_signals_init(void)
                        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)
@@ -371,12 +528,16 @@ 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);
 }
index 85b240fdfc35225097decb8d2fa2d70e4454113a..1991be0c38cb664f49d61e97d33fbff3e65f364d 100644 (file)
@@ -8,7 +8,10 @@ enum libsig_flags {
           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)
 
@@ -28,6 +31,9 @@ extern volatile unsigned int signal_term_counter;
 /* 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)
@@ -38,9 +44,10 @@ void lib_signals_unset_handler(int signo,
                               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. */
diff --git a/src/lib/test-lib-signals.c b/src/lib/test-lib-signals.c
new file mode 100644 (file)
index 0000000..eac0454
--- /dev/null
@@ -0,0 +1,244 @@
+/* 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();
+}
index 7ee3aff794c903691396d39609b87050764fe5b3..0d59a92e30b3873d630913e2bfda9b617423cb1a 100644 (file)
@@ -42,6 +42,7 @@ TEST(test_istream_tee)
 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)