From c4dbb60cffbaba4c376e7b9717e090b0ec5a0b68 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 15 Aug 2012 03:11:47 +0200 Subject: [PATCH] windows: basic signal handling support with uv_signal_t This still needs tests. --- include/uv-private/uv-win.h | 26 +++- include/uv.h | 19 +++ src/win/core.c | 3 + src/win/handle-inl.h | 7 +- src/win/handle.c | 8 +- src/win/internal.h | 20 ++- src/win/req-inl.h | 4 + src/win/signal.c | 297 ++++++++++++++++++++++++++++++++++-- 8 files changed, 354 insertions(+), 30 deletions(-) diff --git a/include/uv-private/uv-win.h b/include/uv-private/uv-win.h index f45ce2b0..dbbecedc 100644 --- a/include/uv-private/uv-win.h +++ b/include/uv-private/uv-win.h @@ -35,6 +35,7 @@ typedef intptr_t ssize_t; #include #include +#include #include #include @@ -47,6 +48,24 @@ typedef intptr_t ssize_t; # define S_IFLNK 0xA000 #endif +/* Additional signals supported by uv_signal and or uv_kill. The CRT defines + * the following signals already: + * + * #define SIGINT 2 + * #define SIGILL 4 + * #define SIGABRT_COMPAT 6 + * #define SIGFPE 8 + * #define SIGSEGV 11 + * #define SIGTERM 15 + * #define SIGBREAK 21 + * #define SIGABRT 22 + * + * The additional signals have values that are common on other Unix + * variants (Linux and Darwin) + */ +#define SIGHUP 1 +#define SIGKILL 9 + /* * Guids and typedefs for winsock extension functions * Mingw32 doesn't have these :-( @@ -255,7 +274,8 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); UV_PROCESS_EXIT, \ UV_READ, \ UV_UDP_RECV, \ - UV_WAKEUP, + UV_WAKEUP, \ + UV_SIGNAL_REQ, #define UV_REQ_PRIVATE_FIELDS \ union { \ @@ -516,7 +536,9 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); char* buffer; #define UV_SIGNAL_PRIVATE_FIELDS \ - /* empty */ + RB_ENTRY(uv_signal_s) tree_entry; \ + struct uv_req_s signal_req; \ + unsigned long pending_signum; int uv_utf16_to_utf8(const WCHAR* utf16Buffer, size_t utf16Size, char* utf8Buffer, size_t utf8Size); diff --git a/include/uv.h b/include/uv.h index b9c8be9a..cca152b2 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1580,11 +1580,30 @@ UV_EXTERN int uv_fs_poll_stop(uv_fs_poll_t* handle); * * TODO(bnoordhuis) As of 2012-08-10 only the default event loop supports * signals. That will be fixed. + * + * Some signal support is available on Windows: + * + * SIGINT is normally delivered when the user presses CTRL+C. However, like + * on Unix, it is not generated when terminal raw mode is enabled. + * + * SIGBREAK is delivered when the user pressed CTRL+BREAK. + * + * SIGHUP is generated when the user closes the console window. On SIGHUP the + * program is given approximately 10 seconds to perform cleanup. After that + * Windows will unconditionally terminate it. + * + * Watchers for other signals can be successfully created, but these signals + * are never generated. These signals are: SIGILL, SIGABRT, SIGFPE, SIGSEGV, + * SIGTERM and SIGKILL. + * + * Note that calls to raise() or abort() to programmatically raise a signal are + * not detected by libuv; these will not trigger a signal watcher. */ struct uv_signal_s { UV_HANDLE_FIELDS uv_signal_cb signal_cb; UV_SIGNAL_PRIVATE_FIELDS + int signum; }; /* These functions are no-ops on Windows. */ diff --git a/src/win/core.c b/src/win/core.c index d704d886..2c76b100 100644 --- a/src/win/core.c +++ b/src/win/core.c @@ -55,6 +55,9 @@ static void uv_init(void) { /* Initialize FS */ uv_fs_init(); + /* Initialize signal stuff */ + uv_signals_init(); + /* Initialize console */ uv_console_init(); diff --git a/src/win/handle-inl.h b/src/win/handle-inl.h index fc2f4f14..44010266 100644 --- a/src/win/handle-inl.h +++ b/src/win/handle-inl.h @@ -123,6 +123,9 @@ INLINE static void uv_process_endgames(uv_loop_t* loop) { uv_async_endgame(loop, (uv_async_t*) handle); break; + case UV_SIGNAL: + uv_signal_endgame(loop, (uv_signal_t*) handle); + case UV_PROCESS: uv_process_endgame(loop, (uv_process_t*) handle); break; @@ -135,10 +138,6 @@ INLINE static void uv_process_endgames(uv_loop_t* loop) { uv__fs_poll_endgame(loop, (uv_fs_poll_t*) handle); break; - case UV_SIGNAL: - uv_signal_endgame(loop, (uv_signal_t*) handle); - break; - default: assert(0); break; diff --git a/src/win/handle.c b/src/win/handle.c index 36c94d02..74bd56c0 100644 --- a/src/win/handle.c +++ b/src/win/handle.c @@ -124,6 +124,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { uv_async_close(loop, (uv_async_t*) handle); return; + case UV_SIGNAL: + uv_signal_close(loop, (uv_signal_t*) handle); + return; + case UV_PROCESS: uv_process_close(loop, (uv_process_t*) handle); return; @@ -138,10 +142,6 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { uv_want_endgame(loop, handle); return; - case UV_SIGNAL: - uv_signal_close(loop, (uv_signal_t*) handle); - return; - default: /* Not supported */ abort(); diff --git a/src/win/internal.h b/src/win/internal.h index 212b2b16..b0d1d18e 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -232,6 +232,19 @@ void uv_process_async_wakeup_req(uv_loop_t* loop, uv_async_t* handle, uv_req_t* req); +/* + * Signal watcher + */ +void uv_signals_init(); +int uv__signal_dispatch(int signum); + +void uv_signal_close(uv_loop_t* loop, uv_signal_t* handle); +void uv_signal_endgame(uv_loop_t* loop, uv_signal_t* handle); + +void uv_process_signal_req(uv_loop_t* loop, uv_signal_t* handle, + uv_req_t* req); + + /* * Spawn */ @@ -274,13 +287,6 @@ void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle); void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle); -/* - * Signals. - */ -void uv_signal_close(uv_loop_t* loop, uv_signal_t* handle); -void uv_signal_endgame(uv_loop_t* loop, uv_signal_t* handle); - - /* * Utilities. */ diff --git a/src/win/req-inl.h b/src/win/req-inl.h index 541fb3c2..353fe90b 100644 --- a/src/win/req-inl.h +++ b/src/win/req-inl.h @@ -187,6 +187,10 @@ INLINE static void uv_process_reqs(uv_loop_t* loop) { uv_process_async_wakeup_req(loop, (uv_async_t*) req->data, req); break; + case UV_SIGNAL_REQ: + uv_process_signal_req(loop, (uv_signal_t*) req->data, req); + break; + case UV_POLL_REQ: uv_process_poll_req(loop, (uv_poll_t*) req->data, req); break; diff --git a/src/win/signal.c b/src/win/signal.c index 9742ec61..75321e10 100644 --- a/src/win/signal.c +++ b/src/win/signal.c @@ -18,40 +18,311 @@ * IN THE SOFTWARE. */ +#include +#include + #include "uv.h" #include "internal.h" #include "handle-inl.h" -#include +#include "req-inl.h" -int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle) { - uv__handle_init(loop, (uv_handle_t*)handle, UV_SIGNAL); +RB_HEAD(uv_signal_tree_s, uv_signal_s); + +static struct uv_signal_tree_s uv__signal_tree = RB_INITIALIZER(uv__signal_tree); +static ssize_t volatile uv__signal_control_handler_refs = 0; +static CRITICAL_SECTION uv__signal_lock; + + +void uv_signals_init() { + InitializeCriticalSection(&uv__signal_lock); +} + + +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 ((uintptr_t) w1->loop < (uintptr_t) w2->loop) return -1; + if ((uintptr_t) w1->loop > (uintptr_t) w2->loop) return 1; + + if ((uintptr_t) w1 < (uintptr_t) w2) return -1; + if ((uintptr_t) w1 > (uintptr_t) w2) return 1; + return 0; } -int uv_signal_start(uv_signal_t* handle, uv_signal_cb signal_cb, int signum) { - /* XXX call uv__handle_start() and bump the refcount? */ +RB_GENERATE_STATIC(uv_signal_tree_s, uv_signal_s, tree_entry, uv__signal_compare); + + +/* + * Dispatches signal {signum} to all active uv_signal_t watchers in all loops. + * Returns 1 if the signal was dispatched to any watcher, or 0 if there were + * no active signal watchers observing this signal. + */ +int uv__signal_dispatch(int signum) { + uv_signal_t lookup; + uv_signal_t* handle; + int dispatched = 0; + + EnterCriticalSection(&uv__signal_lock); + + lookup.signum = signum; + lookup.loop = NULL; + + for (handle = RB_NFIND(uv_signal_tree_s, &uv__signal_tree, &lookup); + handle != NULL && handle->signum == signum; + handle = RB_NEXT(uv_signal_tree_s, &uv__signal_tree, handle)) { + unsigned long previous = InterlockedExchange(&handle->pending_signum, signum); + + if (!previous) { + POST_COMPLETION_FOR_REQ(handle->loop, &handle->signal_req); + } + + dispatched = 1; + } + + LeaveCriticalSection(&uv__signal_lock); + + return dispatched; +} + + +static BOOL WINAPI uv__signal_control_handler(DWORD type) { + switch (type) { + case CTRL_C_EVENT: + return uv__signal_dispatch(SIGINT); + + case CTRL_BREAK_EVENT: + return uv__signal_dispatch(SIGBREAK); + + case CTRL_CLOSE_EVENT: + if (uv__signal_dispatch(SIGHUP)) { + /* Windows will terminate the process after the control handler */ + /* returns. After that it will just terminate our process. Therefore */ + /* block the signal handler so the main loop has some time to pick */ + /* up the signal and do something for a few seconds. */ + Sleep(INFINITE); + return TRUE; + } + return FALSE; + + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* These signals are only sent to services. Services have their own */ + /* notification mechanism, so there's no point in handling these. */ + + default: + /* We don't handle these. */ + return FALSE; + } +} + + +static uv_err_t uv__signal_register_control_handler() { + /* When this function is called, the uv__signal_lock must be held. */ + + /* If the console control handler has already been hooked, just add a */ + /* reference. */ + if (uv__signal_control_handler_refs > 0) + return uv_ok_; + + if (!SetConsoleCtrlHandler(uv__signal_control_handler, TRUE)) + return uv__new_sys_error(GetLastError()); + + uv__signal_control_handler_refs++; + + return uv_ok_; +} + + +static uv_err_t uv__signal_unregister_control_handler() { + /* When this function is called, the uv__signal_lock must be held. */ + + /* Don't unregister if the number of console control handlers exceeds one. */ + /* Just remove a reference in that case. */ + if (uv__signal_control_handler_refs > 1) { + uv__signal_control_handler_refs--; + return uv_ok_; + } + + assert(uv__signal_control_handler_refs == 1); + + if (!SetConsoleCtrlHandler(uv__signal_control_handler, FALSE)) + return uv__new_sys_error(GetLastError()); + + uv__signal_control_handler_refs--; + + return uv_ok_; +} + + +static uv_err_t uv__signal_register(int signum) { + switch (signum) { + case SIGINT: + case SIGBREAK: + case SIGHUP: + return uv__signal_register_control_handler(); + + default: + /* Unsupported signal */ + return uv__new_artificial_error(UV_ENOTSUP); + } +} + + +static uv_err_t uv__signal_unregister(int signum) { + switch (signum) { + case SIGINT: + case SIGBREAK: + case SIGHUP: + return uv__signal_unregister_control_handler(); + + default: + /* Unsupported signal */ + return uv__new_artificial_error(UV_ENOTSUP); + } +} + + +int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle) { + uv_req_t* req; + + uv__handle_init(loop, (uv_handle_t*) handle, UV_SIGNAL); + handle->pending_signum = 0; + handle->signum = 0; + handle->signal_cb = NULL; + + req = &handle->signal_req; + uv_req_init(loop, req); + req->type = UV_SIGNAL_REQ; + req->data = handle; + + uv__handle_start(handle); + return 0; } int uv_signal_stop(uv_signal_t* handle) { + uv_err_t err; + uv_signal_t* removed_handle; + + /* If the watcher wasn't started, this is a no-op. */ + if (handle->signum == 0) + return 0; + + EnterCriticalSection(&uv__signal_lock); + + err = uv__signal_unregister(handle->signum); + if (err.code != UV_OK) { + /* Uh-oh, didn't work. */ + handle->loop->last_err = err; + LeaveCriticalSection(&uv__signal_lock); + return -1; + } + + removed_handle = RB_REMOVE(uv_signal_tree_s, &uv__signal_tree, handle); + assert(removed_handle == handle); + + LeaveCriticalSection(&uv__signal_lock); + + handle->signum = 0; + uv__handle_stop(handle); + return 0; } -void uv_signal_close(uv_loop_t* loop, uv_signal_t* handle) { +int uv_signal_start(uv_signal_t* handle, uv_signal_cb signal_cb, int signum) { + uv_err_t err; + + + /* 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 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) < 0) { + return -1; + } + + EnterCriticalSection(&uv__signal_lock); + + err = uv__signal_register(signum); + if (err.code != UV_OK) { + /* Uh-oh, didn't work. */ + handle->loop->last_err = err; + LeaveCriticalSection(&uv__signal_lock); + return -1; + } + + handle->signum = signum; + RB_INSERT(uv_signal_tree_s, &uv__signal_tree, handle); + + LeaveCriticalSection(&uv__signal_lock); + + handle->signal_cb = signal_cb; uv__handle_start(handle); - uv_want_endgame(loop, (uv_handle_t*)handle); + + return 0; +} + + +void uv_process_signal_req(uv_loop_t* loop, uv_signal_t* handle, + uv_req_t* req) { + unsigned long dispatched_signum; + + assert(handle->type == UV_SIGNAL); + assert(req->type == UV_SIGNAL_REQ); + + dispatched_signum = InterlockedExchange(&handle->pending_signum, 0); + assert(dispatched_signum != 0); + + /* Check if the pending signal equals the signum that we are watching for. */ + /* These can get out of sync when the handler is stopped and restarted */ + /* while the signal_req is pending. */ + if (dispatched_signum == handle->signum) + handle->signal_cb(handle, dispatched_signum); + + if (handle->flags & UV_HANDLE_CLOSING) { + /* When it is closing, it must be stopped at this point. */ + assert(handle->signum == 0); + uv_want_endgame(loop, (uv_handle_t*) handle); + } +} + + +void uv_signal_close(uv_loop_t* loop, uv_signal_t* handle) { + uv_signal_stop(handle); + + if (handle->pending_signum == 0) { + uv__handle_start(handle); + uv_want_endgame(loop, (uv_handle_t*) handle); + } } void uv_signal_endgame(uv_loop_t* loop, uv_signal_t* handle) { - if (handle->flags & UV_HANDLE_CLOSING) { - assert(!(handle->flags & UV_HANDLE_CLOSED)); - handle->flags |= UV_HANDLE_CLOSED; - uv__handle_stop(handle); - uv__handle_close(handle); - } + assert(handle->flags & UV_HANDLE_CLOSING); + assert(!(handle->flags & UV_HANDLE_CLOSED)); + + assert(handle->signum = 0); + assert(handle->pending_signum = 0); + + handle->flags |= UV_HANDLE_CLOSED; + + uv__handle_stop(handle); + uv__handle_close(handle); }