diff --git a/src/unix/internal.h b/src/unix/internal.h index 12d4da93..16be13b9 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -282,6 +282,7 @@ uv_handle_type uv__handle_type(int fd); FILE* uv__open_file(const char* path); int uv__getpwuid_r(uv_passwd_t* pwd); int uv__search_path(const char* prog, char* buf, size_t* buflen); +void uv__wait_children(uv_loop_t* loop); /* random */ int uv__random_devurandom(void* buf, size_t buflen); diff --git a/src/unix/kqueue.c b/src/unix/kqueue.c index 75e91107..efbc561d 100644 --- a/src/unix/kqueue.c +++ b/src/unix/kqueue.c @@ -284,6 +284,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds; for (i = 0; i < nfds; i++) { ev = events + i; + if (ev->filter == EVFILT_PROC) { + uv__wait_children(loop); + nevents++; + continue; + } fd = ev->ident; /* Skip invalidated events, see uv__platform_invalidate_fd */ if (fd == -1) diff --git a/src/unix/process.c b/src/unix/process.c index 91bf3c50..c1f6bd4b 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -49,10 +49,20 @@ extern char **environ; # include "zos-base.h" #endif +#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#include +#endif + +#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) static void uv__chld(uv_signal_t* handle, int signum) { + assert(signum == SIGCHLD); + uv__wait_children(handle->loop); +} +#endif + +void uv__wait_children(uv_loop_t* loop) { uv_process_t* process; - uv_loop_t* loop; int exit_status; int term_signal; int status; @@ -61,10 +71,7 @@ static void uv__chld(uv_signal_t* handle, int signum) { QUEUE* q; QUEUE* h; - assert(signum == SIGCHLD); - QUEUE_INIT(&pending); - loop = handle->loop; h = &loop->process_handles; q = QUEUE_HEAD(h); @@ -420,7 +427,9 @@ int uv_spawn(uv_loop_t* loop, if (err) goto error; +#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD); +#endif /* Acquire write lock to prevent opening new fds in worker threads */ uv_rwlock_wrlock(&loop->cloexec_lock); @@ -495,6 +504,13 @@ int uv_spawn(uv_loop_t* loop, /* Only activate this handle if exec() happened successfully */ if (exec_errorno == 0) { +#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + struct kevent event; + EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0); + if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL)) + abort(); +#endif + QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue); uv__handle_start(process); } diff --git a/test/test-list.h b/test/test-list.h index 1f566861..a43edf1a 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -320,6 +320,7 @@ TEST_DECLARE (spawn_reads_child_path) TEST_DECLARE (spawn_inherit_streams) TEST_DECLARE (spawn_quoted_path) TEST_DECLARE (spawn_tcp_server) +TEST_DECLARE (spawn_exercise_sigchld_issue) TEST_DECLARE (fs_poll) TEST_DECLARE (fs_poll_getpath) TEST_DECLARE (fs_poll_close_request) @@ -950,6 +951,7 @@ TASK_LIST_START TEST_ENTRY (spawn_inherit_streams) TEST_ENTRY (spawn_quoted_path) TEST_ENTRY (spawn_tcp_server) + TEST_ENTRY (spawn_exercise_sigchld_issue) TEST_ENTRY (fs_poll) TEST_ENTRY (fs_poll_getpath) TEST_ENTRY (fs_poll_close_request) diff --git a/test/test-spawn.c b/test/test-spawn.c index 9f2eb24b..dfd5458e 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -1891,6 +1891,44 @@ TEST_IMPL(spawn_quoted_path) { #endif } +TEST_IMPL(spawn_exercise_sigchld_issue) { + int r; + int i; + uv_process_options_t dummy_options = {0}; + uv_process_t dummy_processes[100]; + char* args[2]; + + init_process_options("spawn_helper1", exit_cb); + + r = uv_spawn(uv_default_loop(), &process, &options); + ASSERT_EQ(r, 0); + + // This test exercises a bug in the darwin kernel that causes SIGCHLD not to + // be delivered sometimes. Calling posix_spawn many times increases the + // likelihood of encountering this issue, so spin a few times to make this + // test more reliable. + dummy_options.file = args[0] = "program-that-had-better-not-exist"; + args[1] = NULL; + dummy_options.args = args; + dummy_options.exit_cb = fail_cb; + dummy_options.flags = 0; + for (i = 0; i < 100; i++) { + r = uv_spawn(uv_default_loop(), &dummy_processes[i], &dummy_options); + if (r != UV_ENOENT) + ASSERT_EQ(r, UV_EACCES); + uv_close((uv_handle_t*) &dummy_processes[i], close_cb); + } + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT_EQ(r, 0); + + ASSERT_EQ(exit_cb_called, 1); + ASSERT_EQ(close_cb_called, 101); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + /* Helper for child process of spawn_inherit_streams */ #ifndef _WIN32 void spawn_stdin_stdout(void) {