process: monitor for exit with kqueue on BSDs (#3441)

This adds a workaround for an xnu kernel bug that sometimes results in
SIGCHLD not being delivered. The workaround is to use kevent to listen
for EVFILT_PROC/NOTE_EXIT events instead of relying on SIGCHLD on *BSD.
 
Apple rdar: FB9529664
Refs: https://github.com/libuv/libuv/pull/3257
This commit is contained in:
Jeremy Rose 2022-01-31 11:49:22 -08:00 committed by GitHub
parent bb0b4bb783
commit d9e90857f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 4 deletions

View File

@ -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);

View File

@ -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)

View File

@ -49,10 +49,20 @@ extern char **environ;
# include "zos-base.h"
#endif
#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/event.h>
#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);
}

View File

@ -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)

View File

@ -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) {