From 1e32cb01b5ae20be0d27fa05ee1555b3f67bf763 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 16 Oct 2012 17:42:30 +0200 Subject: [PATCH] unix: support signal handlers outside the main loop --- include/uv-private/uv-unix.h | 16 +- src/unix/core.c | 6 +- src/unix/internal.h | 3 +- src/unix/loop.c | 7 +- src/unix/signal.c | 582 ++++++++++++++++++++---------- test/test-list.h | 2 + test/test-signal-multiple-loops.c | 270 ++++++++++++++ uv.gyp | 1 + 8 files changed, 680 insertions(+), 207 deletions(-) create mode 100644 test/test-signal-multiple-loops.c diff --git a/include/uv-private/uv-unix.h b/include/uv-private/uv-unix.h index 42e39fcc..8f1c5e5c 100644 --- a/include/uv-private/uv-unix.h +++ b/include/uv-private/uv-unix.h @@ -156,7 +156,8 @@ typedef struct { struct uv_timer_s* rbh_root; \ } timer_handles; \ uint64_t time; \ - void* signal_ctx; \ + int signal_pipefd[2]; \ + uv__io_t signal_io_watcher; \ uv_signal_t child_watcher; \ int emfile_fd; \ UV_PLATFORM_LOOP_FIELDS \ @@ -242,7 +243,7 @@ typedef struct { ngx_queue_t queue; #define UV_TIMER_PRIVATE_FIELDS \ - /* RB_ENTRY(uv_timer_s) node; */ \ + /* RB_ENTRY(uv_timer_s) tree_entry; */ \ struct { \ struct uv_timer_s* rbe_left; \ struct uv_timer_s* rbe_right; \ @@ -289,7 +290,16 @@ typedef struct { int mode; #define UV_SIGNAL_PRIVATE_FIELDS \ - ngx_queue_t queue; + /* RB_ENTRY(uv_signal_s) tree_entry; */ \ + struct { \ + struct uv_signal_s* rbe_left; \ + struct uv_signal_s* rbe_right; \ + struct uv_signal_s* rbe_parent; \ + int rbe_color; \ + } tree_entry; \ + /* Use two counters here so we don have to fiddle with atomics. */ \ + unsigned int caught_signals; \ + unsigned int dispatched_signals; #define UV_FS_EVENT_PRIVATE_FIELDS \ uv_fs_event_cb cb; \ diff --git a/src/unix/core.c b/src/unix/core.c index bfdade3d..d7ccb68b 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -124,8 +124,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { break; case UV_SIGNAL: - uv__signal_close((uv_signal_t*)handle); - break; + uv__signal_close((uv_signal_t*) handle); + /* Signal handles may not be closed immediately. The signal code will */ + /* itself close uv__make_close_pending whenever appropriate. */ + return; default: assert(0); diff --git a/src/unix/internal.h b/src/unix/internal.h index ed8dedcf..93e99b8d 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -157,7 +157,8 @@ unsigned int uv__next_timeout(uv_loop_t* loop); /* signal */ void uv__signal_close(uv_signal_t* handle); -void uv__signal_unregister(uv_loop_t* loop); +void uv__signal_global_once_init(void); +void uv__signal_loop_cleanup(); /* thread pool */ void uv__work_submit(uv_loop_t* loop, diff --git a/src/unix/loop.c b/src/unix/loop.c index 7d41c005..c70513f3 100644 --- a/src/unix/loop.c +++ b/src/unix/loop.c @@ -31,6 +31,8 @@ int uv__loop_init(uv_loop_t* loop, int default_loop) { unsigned int i; int flags; + uv__signal_global_once_init(); + #if HAVE_KQUEUE flags = EVBACKEND_KQUEUE; #else @@ -47,10 +49,11 @@ int uv__loop_init(uv_loop_t* loop, int default_loop) { ngx_queue_init(&loop->prepare_handles); ngx_queue_init(&loop->handle_queue); loop->closing_handles = NULL; - loop->signal_ctx = NULL; loop->time = uv_hrtime() / 1000000; loop->async_pipefd[0] = -1; loop->async_pipefd[1] = -1; + loop->signal_pipefd[0] = -1; + loop->signal_pipefd[1] = -1; loop->emfile_fd = -1; loop->ev = (default_loop ? ev_default_loop : ev_loop_new)(flags); ev_set_userdata(loop->ev, loop); @@ -79,8 +82,8 @@ int uv__loop_init(uv_loop_t* loop, int default_loop) { void uv__loop_delete(uv_loop_t* loop) { + uv__signal_loop_cleanup(loop); uv__platform_loop_delete(loop); - uv__signal_unregister(loop); ev_loop_destroy(loop->ev); if (loop->async_pipefd[0] != -1) { diff --git a/src/unix/signal.c b/src/unix/signal.c index 8ef640b6..45555b52 100644 --- a/src/unix/signal.c +++ b/src/unix/signal.c @@ -22,248 +22,432 @@ #include "internal.h" #include +#include +#include #include #include -#include #include -#include - -struct signal_ctx { - int pipefd[2]; - uv__io_t io_watcher; - unsigned int nqueues; - ngx_queue_t queues[1]; /* variable length */ -}; - -static void uv__signal_handler(int signum); -static void uv__signal_event(uv_loop_t* loop, uv__io_t* w, int events); -static struct signal_ctx* uv__signal_ctx_new(uv_loop_t* loop); -static void uv__signal_ctx_delete(struct signal_ctx* ctx); -static void uv__signal_write(int fd, unsigned int val); -static unsigned int uv__signal_read(int fd); -static unsigned int uv__signal_max(void); -int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle) { - uv__handle_init(loop, (uv_handle_t*)handle, UV_SIGNAL); - handle->signum = 0; - return 0; +typedef struct { + uv_signal_t* handle; + int signum; +} uv__signal_msg_t; + +RB_HEAD(uv__signal_tree_s, uv_signal_s); + + +static int uv__signal_unlock(); +static void uv__signal_event(uv_loop_t* loop, uv__io_t* watcher, int events); +static int uv__signal_compare(uv_signal_t* w1, uv_signal_t* w2); +static void uv__signal_stop(uv_signal_t* handle); + + +static pthread_once_t uv__signal_global_init_guard = PTHREAD_ONCE_INIT; +static struct uv__signal_tree_s uv__signal_tree = + RB_INITIALIZER(uv__signal_tree); +static int uv__signal_lock_pipefd[2]; + + +RB_GENERATE_STATIC(uv__signal_tree_s, + uv_signal_s, tree_entry, + uv__signal_compare) + + +static void uv__signal_global_init() { + if (uv__make_pipe(uv__signal_lock_pipefd, 0)) + abort(); + + if (uv__signal_unlock()) + abort(); } -int uv_signal_start(uv_signal_t* handle, uv_signal_cb signal_cb, int signum_) { - struct signal_ctx* ctx; - struct sigaction sa; - unsigned int signum; - uv_loop_t* loop; - ngx_queue_t* q; +void uv__signal_global_once_init(void) { + pthread_once(&uv__signal_global_init_guard, uv__signal_global_init); +} - /* XXX doing this check in uv_signal_init() - the logical place for it - - * leads to an infinite loop when uv__loop_init() inits a signal watcher - */ - /* FIXME */ - assert(handle->loop == uv_default_loop() && - "uv_signal_t is currently only supported by the default loop"); - loop = handle->loop; - signum = signum_; - if (uv__is_active(handle)) - return uv__set_artificial_error(loop, UV_EBUSY); +static int uv__signal_lock() { + int r; + char data; - if (signal_cb == NULL) - return uv__set_artificial_error(loop, UV_EINVAL); + do { + r = read(uv__signal_lock_pipefd[0], &data, sizeof data); + } while (r < 0 && errno == EINTR); - if (signum <= 0) - return uv__set_artificial_error(loop, UV_EINVAL); + return (r < 0) ? -1 : 0; +} - ctx = loop->signal_ctx; - if (ctx == NULL) { - ctx = uv__signal_ctx_new(loop); +static int uv__signal_unlock() { + int r; + char data = 42; - if (ctx == NULL) - return uv__set_artificial_error(loop, UV_ENOMEM); + do { + r = write(uv__signal_lock_pipefd[1], &data, sizeof data); + } while (r < 0 && errno == EINTR); - loop->signal_ctx = ctx; + return (r < 0) ? -1 : 0; +} + + +static void uv__signal_block_and_lock(sigset_t* saved_sigmask) { + sigset_t new_mask; + + if (sigfillset(&new_mask)) + abort(); + + if (pthread_sigmask(SIG_SETMASK, &new_mask, saved_sigmask)) + abort(); + + if (uv__signal_lock()) + abort(); +} + + +static void uv__signal_unlock_and_unblock(sigset_t* saved_sigmask) { + if (uv__signal_unlock()) + abort(); + + if (pthread_sigmask(SIG_SETMASK, saved_sigmask, NULL)) + abort(); +} + + +inline static uv_signal_t* uv__signal_first_handle(int signum) { + /* This function must be called with the signal lock held. */ + uv_signal_t lookup; + uv_signal_t* handle; + + lookup.signum = signum; + lookup.loop = NULL; + + handle = RB_NFIND(uv__signal_tree_s, &uv__signal_tree, &lookup); + + if (handle != NULL && handle->signum == signum) + return handle; + + return NULL; +} + + +void uv__signal_handler(int signum) { + uv__signal_msg_t msg; + uv_signal_t* handle; + + memset(&msg, 0, sizeof msg); + + uv__signal_lock(); + + for (handle = uv__signal_first_handle(signum); + handle != NULL && handle->signum == signum; + handle = RB_NEXT(uv__signal_tree_s, &uv__signal_tree, handle)) { + int r; + + msg.signum = signum; + msg.handle = handle; + + /* write() should be atomic for small data chunks, so the entire message + * should be written at once. In theory the pipe could become full, in + * which case the user is out of luck. + */ + do { + r = write(handle->loop->signal_pipefd[1], &msg, sizeof msg); + } while (r == -1 && errno == EINTR); + + assert(r == sizeof msg || + (r == -1 && errno != EAGAIN && errno != EWOULDBLOCK)); + + if (r != -1) + handle->caught_signals++; } - if (signum > ctx->nqueues) - return uv__set_artificial_error(loop, UV_EINVAL); + uv__signal_unlock(); +} - q = ctx->queues + signum; - if (!ngx_queue_empty(q)) - goto skip; +static uv_err_t uv__signal_register_handler(int signum) { + /* When this function is called, the signal lock must be held. */ + struct sigaction sa; /* XXX use a separate signal stack? */ memset(&sa, 0, sizeof(sa)); + if (sigfillset(&sa.sa_mask)) + abort(); sa.sa_handler = uv__signal_handler; /* XXX save old action so we can restore it later on? */ if (sigaction(signum, &sa, NULL)) - return uv__set_artificial_error(loop, UV_EINVAL); + return uv__new_sys_error(errno); + + return uv_ok_; +} + + +static void uv__signal_unregister_handler(int signum) { + /* When this function is called, the signal lock must be held. */ + struct sigaction sa; + int r; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + + r = sigaction(signum, &sa, NULL); + /* sigaction can only fail with EINVAL or EFAULT; an attempt to deregister a + * signal implies that it was successfully registered earlier, so EINVAL + * should never happen. + */ + assert(r == 0); +} + + +static int uv__signal_loop_once_init(uv_loop_t* loop) { + /* Return if already initialized. */ + if (loop->signal_pipefd[0] != -1) + return 0; + + if (uv__make_pipe(loop->signal_pipefd, UV__F_NONBLOCK)) + return -1; + + uv__io_init(&loop->signal_io_watcher, + uv__signal_event, + loop->signal_pipefd[0], + UV__IO_READ); + uv__io_start(loop, &loop->signal_io_watcher); + + return 0; +} + + +void uv__signal_loop_cleanup(uv_loop_t* loop) { + ngx_queue_t* q; + + /* Stop all the signal watchers that are still attached to this loop. This + * ensures that the (shared) signal tree doesn't contain any invalid entries + * entries, and that signal handlers are removed when appropriate. + */ + ngx_queue_foreach(q, &loop->handle_queue) { + uv_handle_t* handle = ngx_queue_data(q, uv_handle_t, handle_queue); + + if (handle->type == UV_SIGNAL) + uv__signal_stop((uv_signal_t*) handle); + } + + if (loop->signal_pipefd[0] != -1) { + close(loop->signal_pipefd[0]); + loop->signal_pipefd[0] = -1; + } + + if (loop->signal_pipefd[1] != -1) { + close(loop->signal_pipefd[1]); + loop->signal_pipefd[1] = -1; + } +} + + +int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle) { + if (uv__signal_loop_once_init(loop)) + return uv__set_sys_error(loop, errno); + + uv__handle_init(loop, (uv_handle_t*) handle, UV_SIGNAL); + handle->signum = 0; + handle->caught_signals = 0; + handle->dispatched_signals = 0; + + return 0; +} + + +void uv__signal_close(uv_signal_t* handle) { + + uv__signal_stop(handle); + + /* If there are any caught signals "trapped" in the signal pipe, we can't + * call the close callback yet. Otherwise, add the handle to the finish_close + * queue. + */ + if (handle->caught_signals == handle->dispatched_signals) { + uv__make_close_pending((uv_handle_t*) handle); + } +} + + +int uv_signal_start(uv_signal_t* handle, uv_signal_cb signal_cb, int signum) { + sigset_t saved_sigmask; + + assert(!(handle->flags & (UV_CLOSING | UV_CLOSED))); + + /* If the user supplies signum == 0, then return an error already. If the + * signum is otherwise invalid then uv__signal_register will find out + * eventually. + */ + if (signum == 0) { + uv__set_artificial_error(handle->loop, UV_EINVAL); + return -1; + } + + /* Short circuit: if the signal watcher is already watching {signum} don't + * go through the process of deregistering and registering the handler. + * Additionally, this avoids pending signals getting lost in the small time + * time frame that handle->signum == 0. + */ + if (signum == handle->signum) { + handle->signal_cb = signal_cb; + return 0; + } + + /* If the signal handler was already active, stop it first. */ + if (handle->signum != 0) { + uv__signal_stop(handle); + } + + uv__signal_block_and_lock(&saved_sigmask); + + /* If at this point there are no active signal watchers for this signum (in + * any of the loops), it's time to try and register a handler for it here. + */ + if (uv__signal_first_handle(signum) == NULL) { + uv_err_t err = uv__signal_register_handler(signum); + if (err.code != UV_OK) { + /* Registering the signal handler failed. Must be an invalid signal. */ + handle->loop->last_err = err; + uv__signal_unlock_and_unblock(&saved_sigmask); + return -1; + } + } -skip: - ngx_queue_insert_tail(q, &handle->queue); - uv__handle_start(handle); handle->signum = signum; + RB_INSERT(uv__signal_tree_s, &uv__signal_tree, handle); + + uv__signal_unlock_and_unblock(&saved_sigmask); + handle->signal_cb = signal_cb; + uv__handle_start(handle); + + return 0; +} + + +static void uv__signal_event(uv_loop_t* loop, uv__io_t* watcher, int events) { + uv__signal_msg_t* msg; + uv_signal_t* handle; + char buf[sizeof(uv__signal_msg_t) * 32]; + size_t bytes, end, i; + int r; + + bytes = 0; + + do { + r = read(loop->signal_pipefd[0], buf + bytes, sizeof(buf) - bytes); + + if (r == -1 && errno == EINTR) + continue; + + if (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + /* If there are bytes in the buffer already (which really is extremely + * unlikely if possible at all) we can't exit the function here. We'll + * spin until more bytes are read instead. + */ + if (bytes > 0) + continue; + + /* Otherwise, there was nothing there. */ + return; + } + + /* Other errors really should never happen. */ + if (r == -1) + abort(); + + bytes += r; + + /* `end` is rounded down to a multiple of sizeof(uv__signal_msg_t). */ + end = (bytes / sizeof(uv__signal_msg_t)) * sizeof(uv__signal_msg_t); + + for (i = 0; i < end; i += sizeof(uv__signal_msg_t)) { + msg = (uv__signal_msg_t*) (buf + i); + handle = msg->handle; + + if (msg->signum == handle->signum) { + assert(!(handle->flags & UV_CLOSING)); + handle->signal_cb(handle, handle->signum); + } + + handle->dispatched_signals++; + + /* If uv_close was called while there were caught signals that were not + * yet dispatched, the uv__finish_close was deferred. Make close pending + * now if this has happened. + */ + if ((handle->flags & UV_CLOSING) && + (handle->caught_signals == handle->dispatched_signals)) { + uv__make_close_pending((uv_handle_t*) handle); + } + } + + bytes -= end; + + /* If there are any "partial" messages left, move them to the start of the + * the buffer, and spin. This should not happen. + */ + if (bytes) { + memmove(buf, buf + end, bytes); + continue; + } + } while (end == sizeof buf); +} + + +static int uv__signal_compare(uv_signal_t* w1, uv_signal_t* w2) { + /* Compare signums first so all watchers with the same signnum end up + * adjacent. + */ + if (w1->signum < w2->signum) return -1; + if (w1->signum > w2->signum) return 1; + + /* Sort by loop pointer, so we can easily look up the first item after + * { .signum = x, .loop = NULL }. + */ + if (w1->loop < w2->loop) return -1; + if (w1->loop > w2->loop) return 1; + + if (w1 < w2) return -1; + if (w1 > w2) return 1; return 0; } int uv_signal_stop(uv_signal_t* handle) { - struct signal_ctx* ctx; - struct sigaction sa; - unsigned int signum; - uv_loop_t* loop; - - if (!uv__is_active(handle)) - return 0; - - signum = handle->signum; - loop = handle->loop; - ctx = loop->signal_ctx; - assert(signum > 0); - assert(signum <= ctx->nqueues); - - ngx_queue_remove(&handle->queue); - uv__handle_stop(handle); - handle->signum = 0; - - if (!ngx_queue_empty(ctx->queues + signum)) - goto skip; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; /* XXX restore previous action? */ - - if (sigaction(signum, &sa, NULL)) - return uv__set_artificial_error(loop, UV_EINVAL); - -skip: + assert(!(handle->flags & (UV_CLOSING | UV_CLOSED))); + uv__signal_stop(handle); return 0; } -void uv__signal_close(uv_signal_t* handle) { - uv_signal_stop(handle); -} +static void uv__signal_stop(uv_signal_t* handle) { + uv_signal_t* removed_handle; + sigset_t saved_sigmask; - -void uv__signal_unregister(uv_loop_t* loop) { - uv__signal_ctx_delete(loop->signal_ctx); - loop->signal_ctx = NULL; -} - - -static void uv__signal_handler(int signum) { - struct signal_ctx* ctx = uv_default_loop()->signal_ctx; - uv__signal_write(ctx->pipefd[1], (unsigned int) signum); -} - - -static void uv__signal_event(uv_loop_t* loop, uv__io_t* w, int events) { - struct signal_ctx* ctx; - unsigned int signum; - uv_signal_t* h; - ngx_queue_t* q; - - ctx = container_of(w, struct signal_ctx, io_watcher); - signum = uv__signal_read(ctx->pipefd[0]); - assert(signum > 0); - assert(signum <= ctx->nqueues); - - ngx_queue_foreach(q, ctx->queues + signum) { - h = ngx_queue_data(q, uv_signal_t, queue); - h->signal_cb(h, signum); - } -} - - -static struct signal_ctx* uv__signal_ctx_new(uv_loop_t* loop) { - struct signal_ctx* ctx; - unsigned int nqueues; - unsigned int i; - - nqueues = uv__signal_max(); - assert(nqueues > 0); - - /* The first ctx->queues entry is never used. It wastes a few bytes of memory - * but it saves us from having to substract 1 from the signum all the time - - * which inevitably someone will forget to do. - */ - ctx = calloc(1, sizeof(*ctx) + sizeof(ctx->queues[0]) * (nqueues + 1)); - if (ctx == NULL) - return NULL; - - if (uv__make_pipe(ctx->pipefd, UV__F_NONBLOCK)) { - free(ctx); - return NULL; - } - - uv__io_init(&ctx->io_watcher, uv__signal_event, ctx->pipefd[0], UV__IO_READ); - uv__io_start(loop, &ctx->io_watcher); - ctx->nqueues = nqueues; - - for (i = 1; i <= nqueues; i++) - ngx_queue_init(ctx->queues + i); - - return ctx; -} - - -static void uv__signal_ctx_delete(struct signal_ctx* ctx) { - if (ctx == NULL) return; - close(ctx->pipefd[0]); - close(ctx->pipefd[1]); - free(ctx); -} - - -static void uv__signal_write(int fd, unsigned int val) { - ssize_t n; - - do - n = write(fd, &val, sizeof(val)); - while (n == -1 && errno == EINTR); - - if (n == sizeof(val)) + /* If the watcher wasn't started, this is a no-op. */ + if (handle->signum == 0) return; - if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) - return; /* pipe full - nothing we can do about that */ + uv__signal_block_and_lock(&saved_sigmask); - abort(); -} - - -static unsigned int uv__signal_read(int fd) { - unsigned int val; - ssize_t n; - - do - n = read(fd, &val, sizeof(val)); - while (n == -1 && errno == EINTR); - - if (n == sizeof(val)) - return val; - - abort(); -} - - -static unsigned int uv__signal_max(void) { -#if defined(_SC_RTSIG_MAX) - int max = sysconf(_SC_RTSIG_MAX); - if (max != -1) return max; -#endif -#if defined(SIGRTMAX) - return SIGRTMAX; -#elif defined(NSIG) - return NSIG; -#else - return 32; -#endif + removed_handle = RB_REMOVE(uv__signal_tree_s, &uv__signal_tree, handle); + assert(removed_handle == handle); + + /* Check if there are other active signal watchers observing this signal. If + * not, unregister the signal handler. + */ + if (uv__signal_first_handle(handle->signum) == NULL) + uv__signal_unregister_handler(handle->signum); + + uv__signal_unlock_and_unblock(&saved_sigmask); + + handle->signum = 0; + uv__handle_stop(handle); } diff --git a/test/test-list.h b/test/test-list.h index 1efd9106..fa05ca93 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -203,6 +203,7 @@ TEST_DECLARE (fs_stat_root) TEST_DECLARE (spawn_setuid_setgid) TEST_DECLARE (we_get_signal) TEST_DECLARE (we_get_signals) +TEST_DECLARE (signal_multiple_loops) #endif HELPER_DECLARE (tcp4_echo_server) HELPER_DECLARE (tcp6_echo_server) @@ -409,6 +410,7 @@ TASK_LIST_START TEST_ENTRY (spawn_setuid_setgid) TEST_ENTRY (we_get_signal) TEST_ENTRY (we_get_signals) + TEST_ENTRY (signal_multiple_loops) #endif TEST_ENTRY (fs_file_noent) diff --git a/test/test-signal-multiple-loops.c b/test/test-signal-multiple-loops.c new file mode 100644 index 00000000..d0223d56 --- /dev/null +++ b/test/test-signal-multiple-loops.c @@ -0,0 +1,270 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +/* This test does not pretend to be cross-platform. */ +#ifndef _WIN32 + +#include "uv.h" +#include "task.h" + +#include +#include +#include +#include +#include +#include +#include + + +#define NUM_SIGNAL_HANDLING_THREADS 25 +#define NUM_LOOP_CREATING_THREADS 10 + + +static uv_sem_t sem; +static uv_mutex_t counter_lock; +static volatile int stop = 0; + +static volatile int signal1_cb_counter = 0; +static volatile int signal2_cb_counter = 0; +static volatile int loop_creation_counter = 0; + + +static void increment_counter(volatile int* counter) { + uv_mutex_lock(&counter_lock); + ++(*counter); + uv_mutex_unlock(&counter_lock); +} + + +static void signal1_cb(uv_signal_t* handle, int signum) { + ASSERT(signum == SIGUSR1); + increment_counter(&signal1_cb_counter); + uv_signal_stop(handle); +} + + +static void signal2_cb(uv_signal_t* handle, int signum) { + ASSERT(signum == SIGUSR2); + increment_counter(&signal2_cb_counter); + uv_signal_stop(handle); +} + + +static void signal_handling_worker(void* context) { + uintptr_t mask = (uintptr_t) context; + uv_loop_t* loop; + uv_signal_t signal1a; + uv_signal_t signal1b; + uv_signal_t signal2; + int r; + + loop = uv_loop_new(); + ASSERT(loop != NULL); + + /* Setup the signal watchers and start them. */ + if (mask & SIGUSR1) { + r = uv_signal_init(loop, &signal1a); + ASSERT(r == 0); + r = uv_signal_start(&signal1a, signal1_cb, SIGUSR1); + ASSERT(r == 0); + r = uv_signal_init(loop, &signal1b); + ASSERT(r == 0); + r = uv_signal_start(&signal1b, signal1_cb, SIGUSR1); + ASSERT(r == 0); + } + if (mask & SIGUSR2) { + r = uv_signal_init(loop, &signal2); + ASSERT(r == 0); + r = uv_signal_start(&signal2, signal2_cb, SIGUSR2); + ASSERT(r == 0); + } + + /* Signal watchers are now set up. */ + uv_sem_post(&sem); + + /* Wait for all signals. The signal callbacks stop the watcher, so uv_run + * will return when all signal watchers caught a signal. + */ + r = uv_run(loop); + ASSERT(r == 0); + + /* Restart the signal watchers. */ + if (mask & SIGUSR1) { + r = uv_signal_start(&signal1a, signal1_cb, SIGUSR1); + ASSERT(r == 0); + r = uv_signal_start(&signal1b, signal1_cb, SIGUSR1); + ASSERT(r == 0); + } + if (mask & SIGUSR2) { + r = uv_signal_start(&signal2, signal2_cb, SIGUSR2); + ASSERT(r == 0); + } + + /* Wait for signals once more. */ + uv_sem_post(&sem); + + r = uv_run(loop); + ASSERT(r == 0); + + /* Close the watchers. */ + if (mask & SIGUSR1) { + uv_close((uv_handle_t*) &signal1a, NULL); + uv_close((uv_handle_t*) &signal1b, NULL); + } + if (mask & SIGUSR2) { + uv_close((uv_handle_t*) &signal2, NULL); + } + + /* Wait for the signal watchers to close. */ + r = uv_run(loop); + ASSERT(r == 0); + + uv_loop_delete(loop); +} + + +static void signal_unexpected_cb(uv_signal_t* handle, int signum) { + ASSERT(0 && "signal_unexpected_cb should never be called"); +} + + +static void loop_creating_worker(void* context) { + (void) context; + + do { + uv_loop_t* loop; + uv_signal_t signal; + int r; + + loop = uv_loop_new(); + ASSERT(loop != NULL); + + r = uv_signal_init(loop, &signal); + ASSERT(r == 0); + + r = uv_signal_start(&signal, signal_unexpected_cb, SIGTERM); + ASSERT(r == 0); + + uv_close((uv_handle_t*) &signal, NULL); + + r = uv_run(loop); + ASSERT(r == 0); + + uv_loop_delete(loop); + + increment_counter(&loop_creation_counter); + } while (!stop); +} + + +TEST_IMPL(signal_multiple_loops) { + int i, r; + uv_thread_t loop_creating_threads[NUM_LOOP_CREATING_THREADS]; + uv_thread_t signal_handling_threads[NUM_SIGNAL_HANDLING_THREADS]; + sigset_t sigset; + + r = uv_sem_init(&sem, 0); + ASSERT(r == 0); + + r = uv_mutex_init(&counter_lock); + ASSERT(r == 0); + + /* Create a couple of threads that create a destroy loops continuously. */ + for (i = 0; i < NUM_LOOP_CREATING_THREADS; i++) { + r = uv_thread_create(&loop_creating_threads[i], + loop_creating_worker, + NULL); + ASSERT(r == 0); + } + + /* Create a couple of threads that actually handle signals. */ + for (i = 0; i < NUM_SIGNAL_HANDLING_THREADS; i++) { + uintptr_t mask; + + switch (i % 3) { + case 0: mask = SIGUSR1; break; + case 1: mask = SIGUSR2; break; + case 2: mask = SIGUSR1 | SIGUSR2; break; + } + + r = uv_thread_create(&signal_handling_threads[i], + signal_handling_worker, + (void*) mask); + ASSERT(r == 0); + } + + /* Wait until all threads have started and set up their signal watchers. */ + for (i = 0; i < NUM_SIGNAL_HANDLING_THREADS; i++) + uv_sem_wait(&sem); + + r = kill(getpid(), SIGUSR1); + ASSERT(r == 0); + r = kill(getpid(), SIGUSR2); + ASSERT(r == 0); + + /* Wait for all threads to handle these signals. */ + for (i = 0; i < NUM_SIGNAL_HANDLING_THREADS; i++) + uv_sem_wait(&sem); + + /* Block all signals to this thread, so we are sure that from here the signal + * handler runs in another thread. This is is more likely to catch thread and + * signal safety issues if there are any. + */ + sigfillset(&sigset); + pthread_sigmask(SIG_SETMASK, &sigset, NULL); + + r = kill(getpid(), SIGUSR1); + ASSERT(r == 0); + r = kill(getpid(), SIGUSR2); + ASSERT(r == 0); + + /* Wait for all signal handling threads to exit. */ + for (i = 0; i < NUM_SIGNAL_HANDLING_THREADS; i++) { + r = uv_thread_join(&signal_handling_threads[i]); + ASSERT(r == 0); + } + + /* Tell all loop creating threads to stop. */ + stop = 1; + + /* Wait for all loop creating threads to exit. */ + for (i = 0; i < NUM_LOOP_CREATING_THREADS; i++) { + r = uv_thread_join(&loop_creating_threads[i]); + ASSERT(r == 0); + } + + printf("signal1_cb calls: %d\n", signal1_cb_counter); + printf("signal2_cb calls: %d\n", signal2_cb_counter); + printf("loops created and destroyed: %d\n", loop_creation_counter); + + ASSERT(signal1_cb_counter == 4 * NUM_SIGNAL_HANDLING_THREADS); + ASSERT(signal2_cb_counter == 2 * NUM_SIGNAL_HANDLING_THREADS); + /* We don't know exactly how much loops will be created and destroyed, but at + * least there should be 1 for every loop creating thread. + */ + ASSERT(loop_creation_counter >= NUM_LOOP_CREATING_THREADS); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +#endif /* !_WIN32 */ diff --git a/uv.gyp b/uv.gyp index e0abd167..14930cfa 100644 --- a/uv.gyp +++ b/uv.gyp @@ -283,6 +283,7 @@ 'test/test-shutdown-close.c', 'test/test-shutdown-eof.c', 'test/test-signal.c', + 'test/test-signal-multiple-loops.c', 'test/test-spawn.c', 'test/test-fs-poll.c', 'test/test-stdio-over-pipes.c',