]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-event: Allow passing WNOWAIT to sd_event_add_child()
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 6 Nov 2025 13:30:06 +0000 (14:30 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 14 Nov 2025 09:34:32 +0000 (10:34 +0100)
This allows doing the reaping outside of the callback, we'll use this
when adding fibers in a later commit.

man/sd_event_add_child.xml
src/libsystemd/sd-event/sd-event.c
src/libsystemd/sd-event/test-event.c

index 4bf07baf59209aa3f71013e77684034139e3f644..28ff6245165cb119890861b5ca4ee67cd965ea15 100644 (file)
     parameter specifies the PID of the process to watch, which must be a direct child process of the invoking
     process. The <parameter>options</parameter> parameter determines which state changes will be watched for.
     It must contain an OR-ed mask of <constant>WEXITED</constant> (watch for the child process terminating),
-    <constant>WSTOPPED</constant> (watch for the child process being stopped by a signal), and
-    <constant>WCONTINUED</constant> (watch for the child process being resumed by a signal). See
+    <constant>WSTOPPED</constant> (watch for the child process being stopped by a signal),
+    <constant>WCONTINUED</constant> (watch for the child process being resumed by a signal) and
+    <constant>WNOWAIT</constant> (Do not reap the child process after it exits). See
     <citerefentry project='man-pages'><refentrytitle>waitid</refentrytitle><manvolnum>2</manvolnum></citerefentry>
     for further information.</para>
 
index fbe7656925ed82c4cc7b875e15430b42e4837628..5794b63aaaf6d476d7ec94733daa08ee8d5ae01c 100644 (file)
@@ -48,7 +48,7 @@ static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) {
         /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */
         return s &&
                 s->type == SOURCE_CHILD &&
-                s->child.options == WEXITED;
+                (s->child.options & ~WNOWAIT) == WEXITED;
 }
 
 static bool event_source_is_online(sd_event_source *s) {
@@ -1583,7 +1583,7 @@ _public_ int sd_event_add_child(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(pid > 1, -EINVAL);
-        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED|WNOWAIT)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_origin_changed(e), -ECHILD);
@@ -1675,7 +1675,7 @@ _public_ int sd_event_add_child_pidfd(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(pidfd >= 0, -EBADF);
-        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED|WNOWAIT)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_origin_changed(e), -ECHILD);
@@ -3689,7 +3689,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori
 
                 zero(s->child.siginfo);
                 if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo,
-                           WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0)
+                           WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | (s->child.options & ~WNOWAIT)) < 0)
                         return negative_errno();
 
                 if (s->child.siginfo.si_pid != 0) {
@@ -3737,7 +3737,6 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) {
         /* Note that pidfd would also generate EPOLLHUP when the process gets reaped. But at this point we
          * only permit EPOLLIN, under the assumption that upon EPOLLHUP the child source should already
          * be set to pending, and we would have returned early above. */
-        assert(!s->child.exited);
 
         zero(s->child.siginfo);
         if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0)
@@ -4174,10 +4173,11 @@ static int source_dispatch(sd_event_source *s) {
 
                 r = s->child.callback(s, &s->child.siginfo, s->userdata);
 
-                /* Now, reap the PID for good. */
+                /* Now, reap the PID for good (unless WNOWAIT was specified by the caller). */
                 if (zombie) {
-                        (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED);
-                        s->child.waited = true;
+                        (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT));
+                        if (!FLAGS_SET(s->child.options, WNOWAIT))
+                                s->child.waited = true;
                 }
 
                 break;
index 31931aea193e8c06cdbfef701cbc328b7395dde0..52bdcf7ae224a888e164c1b21ac6b25295cace66 100644 (file)
@@ -985,4 +985,84 @@ TEST(defer_add_post) {
         ASSERT_TRUE(dispatched_post);
 }
 
+static int child_handler_wnowait(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        int *counter = ASSERT_PTR(userdata);
+
+        (*counter)++;
+
+        if (*counter == 5)
+                ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0));
+
+        return 0;
+}
+
+TEST(child_wnowait) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+        ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
+
+        ASSERT_OK(sd_event_default(&e));
+
+        /* Fork a subprocess */
+        pid_t pid;
+        ASSERT_OK_ERRNO(pid = fork());
+
+        if (pid == 0)
+                /* Child process - exit with a specific code */
+                _exit(42);
+
+        /* Add a child source with WNOWAIT flag */
+        _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+        int counter = 0;
+        ASSERT_OK(sd_event_add_child(e, &s, pid, WEXITED|WNOWAIT, child_handler_wnowait, &counter));
+        ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ON));
+
+        /* Run the event loop - this should call the handler */
+        ASSERT_OK(sd_event_loop(e));
+        ASSERT_EQ(counter, 5);
+
+        /* Since we used WNOWAIT, the child should still be waitable */
+        siginfo_t si = {};
+        ASSERT_OK_ERRNO(waitid(P_PID, pid, &si, WEXITED|WNOHANG));
+        ASSERT_EQ(si.si_pid, pid);
+        ASSERT_EQ(si.si_code, CLD_EXITED);
+        ASSERT_EQ(si.si_status, 42);
+}
+
+TEST(child_pidfd_wnowait) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+        ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD));
+
+        ASSERT_OK(sd_event_default(&e));
+
+        /* Fork a subprocess */
+        pid_t pid;
+        ASSERT_OK_ERRNO(pid = fork());
+
+        if (pid == 0)
+                /* Child process - exit with a specific code */
+                _exit(42);
+
+        _cleanup_close_ int pidfd = -EBADF;
+        ASSERT_OK_ERRNO(pidfd = pidfd_open(pid, 0));
+
+        /* Add a child source with WNOWAIT flag */
+        _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+        int counter = 0;
+        ASSERT_OK(sd_event_add_child_pidfd(e, &s, pidfd, WEXITED|WNOWAIT, child_handler_wnowait, &counter));
+        ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ON));
+
+        /* Run the event loop - this should call the handler */
+        ASSERT_OK(sd_event_loop(e));
+        ASSERT_EQ(counter, 5);
+
+        /* Since we used WNOWAIT, the child should still be waitable */
+        siginfo_t si = {};
+        ASSERT_OK_ERRNO(waitid(P_PIDFD, pidfd, &si, WEXITED|WNOHANG));
+        ASSERT_EQ(si.si_pid, pid);
+        ASSERT_EQ(si.si_code, CLD_EXITED);
+        ASSERT_EQ(si.si_status, 42);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);