Browse Source

libuv: macos: reap child processes more reliably when using kqueue

Backport libuv commit `42cc412c4a` (darwin,process: feed kevent the
signal to reap children, 2023-02-01, `v1.45.0~55`) from libuv PR 3893.

Fixes: #25839
Brad King 1 year ago
parent
commit
7e3519e705

+ 1 - 0
Utilities/cmlibuv/src/unix/core.c

@@ -366,6 +366,7 @@ static int uv__backend_timeout(const uv_loop_t* loop) {
       (uv__has_active_handles(loop) || uv__has_active_reqs(loop)) &&
       QUEUE_EMPTY(&loop->pending_queue) &&
       QUEUE_EMPTY(&loop->idle_handles) &&
+      (loop->flags & UV_LOOP_REAP_CHILDREN) == 0 &&
       loop->closing_handles == NULL)
     return uv__next_timeout(loop);
   return 0;

+ 1 - 0
Utilities/cmlibuv/src/unix/internal.h

@@ -272,6 +272,7 @@ int uv__epoll_init(uv_loop_t* loop);
 int uv__platform_loop_init(uv_loop_t* loop);
 void uv__platform_loop_delete(uv_loop_t* loop);
 void uv__platform_invalidate_fd(uv_loop_t* loop, int fd);
+int uv__process_init(uv_loop_t* loop);
 
 /* various */
 void uv__async_close(uv_async_t* handle);

+ 18 - 25
Utilities/cmlibuv/src/unix/kqueue.c

@@ -235,6 +235,9 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
                   ARRAY_SIZE(events),
                   timeout == -1 ? NULL : &spec);
 
+    if (nfds == -1)
+      assert(errno == EINTR);
+
     if (pset != NULL)
       pthread_sigmask(SIG_UNBLOCK, pset, NULL);
 
@@ -242,36 +245,26 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
      * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the
      * operating system didn't reschedule our process while in the syscall.
      */
-    SAVE_ERRNO(uv__update_time(loop));
-
-    if (nfds == 0) {
-      if (reset_timeout != 0) {
-        timeout = user_timeout;
-        reset_timeout = 0;
-        if (timeout == -1)
-          continue;
-        if (timeout > 0)
-          goto update_timeout;
+    uv__update_time(loop);
+
+    if (nfds == 0 || nfds == -1) {
+      /* If kqueue is empty or interrupted, we might still have children ready
+       * to reap immediately. */
+      if (loop->flags & UV_LOOP_REAP_CHILDREN) {
+        loop->flags &= ~UV_LOOP_REAP_CHILDREN;
+        uv__wait_children(loop);
+        assert((reset_timeout == 0 ? timeout : user_timeout) == 0);
+        return; /* Equivalent to fall-through behavior. */
       }
 
-      assert(timeout != -1);
-      return;
-    }
-
-    if (nfds == -1) {
-      if (errno != EINTR)
-        abort();
-
       if (reset_timeout != 0) {
         timeout = user_timeout;
         reset_timeout = 0;
-      }
-
-      if (timeout == 0)
+      } else if (nfds == 0) {
+        /* Reached the user timeout value. */
+        assert(timeout != -1);
         return;
-
-      if (timeout == -1)
-        continue;
+      }
 
       /* Interrupted by a signal. Update timeout and poll again. */
       goto update_timeout;
@@ -423,13 +416,13 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
       return;
     }
 
+update_timeout:
     if (timeout == 0)
       return;
 
     if (timeout == -1)
       continue;
 
-update_timeout:
     assert(timeout > 0);
 
     diff = loop->time - base;

+ 1 - 4
Utilities/cmlibuv/src/unix/loop.c

@@ -79,12 +79,9 @@ int uv_loop_init(uv_loop_t* loop) {
     goto fail_platform_init;
 
   uv__signal_global_once_init();
-  err = uv_signal_init(loop, &loop->child_watcher);
+  err = uv__process_init(loop);
   if (err)
     goto fail_signal_init;
-
-  uv__handle_unref(&loop->child_watcher);
-  loop->child_watcher.flags |= UV_HANDLE_INTERNAL;
   QUEUE_INIT(&loop->process_handles);
 
   err = uv_rwlock_init(&loop->cloexec_lock);

+ 27 - 0
Utilities/cmlibuv/src/unix/process.c

@@ -92,8 +92,28 @@ static void uv__chld(uv_signal_t* handle, int signum) {
   assert(signum == SIGCHLD);
   uv__wait_children(handle->loop);
 }
+
+
+int uv__process_init(uv_loop_t* loop) {
+  int err;
+
+  err = uv_signal_init(loop, &loop->child_watcher);
+  if (err)
+    return err;
+  uv__handle_unref(&loop->child_watcher);
+  loop->child_watcher.flags |= UV_HANDLE_INTERNAL;
+  return 0;
+}
+
+
+#else
+int uv__process_init(uv_loop_t* loop) {
+  memset(&loop->child_watcher, 0, sizeof(loop->child_watcher));
+  return 0;
+}
 #endif
 
+
 void uv__wait_children(uv_loop_t* loop) {
   uv_process_t* process;
   int exit_status;
@@ -118,6 +138,7 @@ void uv__wait_children(uv_loop_t* loop) {
       continue;
     options = 0;
     process->flags &= ~UV_HANDLE_REAP;
+    loop->nfds--;
 #else
     options = WNOHANG;
 #endif
@@ -1070,6 +1091,10 @@ int uv_spawn(uv_loop_t* loop,
       process->flags |= UV_HANDLE_REAP;
       loop->flags |= UV_LOOP_REAP_CHILDREN;
     }
+    /* This prevents uv__io_poll() from bailing out prematurely, being unaware
+     * that we added an event here for it to react to. We will decrement this
+     * again after the waitpid call succeeds. */
+    loop->nfds++;
 #endif
 
     process->pid = pid;
@@ -1138,6 +1163,8 @@ int uv_kill(int pid, int signum) {
 void uv__process_close(uv_process_t* handle) {
   QUEUE_REMOVE(&handle->queue);
   uv__handle_stop(handle);
+#ifdef UV_USE_SIGCHLD
   if (QUEUE_EMPTY(&handle->loop->process_handles))
     uv_signal_stop(&handle->loop->child_watcher);
+#endif
 }

+ 2 - 0
Utilities/cmlibuv/src/unix/signal.c

@@ -279,6 +279,8 @@ static int uv__signal_loop_once_init(uv_loop_t* loop) {
 
 
 int uv__signal_loop_fork(uv_loop_t* loop) {
+  if (loop->signal_pipefd[0] == -1)
+    return 0;
   uv__io_stop(loop, &loop->signal_io_watcher, POLLIN);
   uv__close(loop->signal_pipefd[0]);
   uv__close(loop->signal_pipefd[1]);