sd-event: Allow passing WNOWAIT to sd_event_add_child()

This allows doing the reaping outside of the callback, we'll use this
when adding fibers in a later commit.
This commit is contained in:
Daan De Meyer
2025-11-06 14:30:06 +01:00
parent 0a79791d0a
commit c11e1001db
3 changed files with 91 additions and 10 deletions

View File

@@ -116,8 +116,9 @@
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>

View 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;

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