sd-future: make src/basic blocking helpers fiber-aware
Some helpers in src/basic — ppoll_usec_full() (used by fd_wait_for_event()), loop_read(), loop_read_exact(), loop_write_full() and pidref_wait_for_terminate_full() — block the calling thread. That's the right behaviour outside a fiber but not inside one, where blocking the thread also stalls every other fiber running on the same event loop. Rewriting every caller to pick a fiber or non-fiber variant explicitly would be a lot of churn and would split otherwise-shared code paths in two. Instead, the helpers detect at runtime whether they're running on a fiber and dispatch to a suspending variant when they are. FiberOps in fiber-ops.h holds five function pointers (ppoll, read, write, timeout, cancel_wait_unref); a fiber_ops global constant is populated whenever we enter a fiber with functions that delegate to suspending variants of common syscalls. With this approach, the variants themselves stay in libsystemd which is required because they make use of sd-event. - loop_read()/loop_read_exact() take the fiber read hook on a fiber unless the caller asked for a non-blocking attempt (do_poll=false) and the fd is already non-blocking — in that case we fall through to read() to preserve the existing return-EAGAIN-immediately semantic. The hook itself suspends on EAGAIN until data is available, so neither the do_poll knob nor the explicit fd_wait_for_event() retry loop are needed on the fiber path. - loop_write_full() likewise takes the fiber write hook on a fiber, except when timeout=0 with an already-non-blocking fd (preserving the fast-return-EAGAIN semantic). The fiber path runs inside a FIBER_OPS_TIMEOUT() scope so the caller's timeout is honoured via a deadline future, mirroring SD_FIBER_TIMEOUT() but reachable from src/basic without pulling in sd-future.h. - pidref_wait_for_terminate_full() polls the pidfd via fd_wait_for_event() before each waitid() when either a finite timeout is set or we're on a fiber, and requires pidref->fd >= 0 in those cases (returning -ENOMEDIUM otherwise — extending the rule that already applied to finite timeouts). The poll suspends the fiber via the ppoll hook above; the subsequent waitid() doesn't block because the pidfd is already signalled.
This commit is contained in:
+18
-5
@@ -7,6 +7,7 @@
|
||||
#include "alloc-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fiber-ops.h"
|
||||
#include "format-util.h"
|
||||
#include "hash-funcs.h"
|
||||
#include "io-util.h"
|
||||
@@ -466,16 +467,28 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re
|
||||
if (pidref->pid == 1 || pidref_is_self(pidref))
|
||||
return -ECHILD;
|
||||
|
||||
if (timeout != USEC_INFINITY && pidref->fd < 0)
|
||||
if (pidref->fd < 0 && (timeout != USEC_INFINITY || fiber_ops_is_set()))
|
||||
return -ENOMEDIUM;
|
||||
|
||||
usec_t ts = timeout == USEC_INFINITY ? USEC_INFINITY : usec_add(now(CLOCK_MONOTONIC), timeout);
|
||||
|
||||
/* Poll the pidfd before waitid() if either there's a finite timeout (so we can honor it) or
|
||||
* we're on a fiber (so fd_wait_for_event() can suspend us instead of blocking the event loop
|
||||
* inside waitid()). Otherwise let waitid() block directly. The precondition above guarantees
|
||||
* pidref->fd >= 0 in both cases. */
|
||||
bool poll_first = ts != USEC_INFINITY || fiber_ops_is_set();
|
||||
|
||||
for (;;) {
|
||||
if (ts != USEC_INFINITY) {
|
||||
usec_t left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC));
|
||||
if (left == 0)
|
||||
return -ETIMEDOUT;
|
||||
if (poll_first) {
|
||||
usec_t left;
|
||||
|
||||
if (ts == USEC_INFINITY)
|
||||
left = USEC_INFINITY;
|
||||
else {
|
||||
left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC));
|
||||
if (left == 0)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
r = fd_wait_for_event(pidref->fd, POLLIN, left);
|
||||
if (r == 0)
|
||||
|
||||
Reference in New Issue
Block a user