Repository navigation
Description
What
On POSIX cbm_nanosleep is raw nanosleep (src/foundation/compat.h:96), and all four call sites
pass rem = NULL and discard the return value:
src/foundation/subprocess.c:958—cbm_spawn_backoff, the spawn-retry patiencesrc/daemon/bootstrap.c:274src/daemon/runtime.c:275src/foundation/diagnostics.c:464
nanosleep returns EINTR on any signal delivered to the calling thread, and unlike most blocking
calls it is not restarted by SA_RESTART — the remaining time is simply lost unless the caller
loops on rem. So every one of these waits can end early by an arbitrary amount, silently.
Why it matters
cbm_spawn_backoff is the clearest case. The comments around
CBM_SPAWN_RETRY_ATTEMPTS promise ~630 ms of patience on an ordinary build and ~5 s on a sanitized
one, and that is the whole point of the retry: ride out a burst of process creation instead of
failing a spawn that would have succeeded. A signal arriving mid-backoff cuts that budget to a
fraction of what the code says it is, and nothing reports it.
src/cli/hook_augment.c:133 installs a SIGALRM handler, so this is reachable and not theoretical.
Why this is an issue and not a PR
- It is not the cause of the
subprocess_run_spawn_failureflakes that fix(config): self-heal an orphan managed marker on removal (#1558) #1590/fix(subprocess+tsan): widen the spawn-retry window where it was silently disabled #1595 chased: CI
runs no alarm, so the truncation does not fire there. Fixing it will not change those lanes. - The right fix is not uniform. A
cbm_nanosleep_full()that loops on the remainder is obviously
right for the spawn backoff, butbootstrap.candruntime.cmay well want to wake early on a
signal during shutdown. That is a call-site-by-call-site decision, not a mechanical replacement.
Suggested shape
Add cbm_nanosleep_full() (loop while nanosleep returns -1/EINTR, feeding rem back in;
Windows Sleep already has no interruptible failure mode) and move the call sites that want an
uninterruptible wait — starting with cbm_spawn_backoff — over to it. Leave the daemon pauses on
the interruptible primitive if that is the intent, with a comment saying so.
Found while reviewing the sanitized-build wiring in #1615; unrelated to that change.
Activity
DeusData commentedon Aug 18, 2026
Thank you for distinguishing the real EINTR defect from the unrelated CI spawn failures. Current main still aliases
cbm_nanosleepdirectly to POSIXnanosleep, and the production call sites discard both the return value and remainder, so the promised backoff duration can be truncated exactly as described.The proposed boundary is sound: add an explicit full-sleep helper that resumes only on
EINTR, movecbm_spawn_backofffirst, and leave shutdown or polling pauses on the interruptible primitive only where early wakeup is intentional and documented. This should be a focused reliability change, not a mechanical replacement of every call site. Thank you for doing the call-site analysis before suggesting the helper.Merge pull request #1799 from rarepops/fix/nanosleep-eintr-1616
DeusData commentedon Sep 2, 2026
Fixed on
mainbyc537bf0f("fix(foundation): resume spawn backoff after EINTR"), merged as14249349via #1799. Thanks to @rarepops.cbm_spawn_backoffcalledcbm_nanosleep(&delay, NULL)— passingNULLfor the remainder, so a signal arriving mid-sleep silently truncated the wait to however long had already elapsed. With the backoff at10ms << shiftup to shift 8, a 2.56-second retry delay could collapse to almost nothing, making the spawn retry loop tighten under exactly the memory pressure it exists to ride out.The new
cbm_nanosleep_full()runs the canonical resume loop —nanosleepwrites the unslept remainder back into the sametimespec, which is passed straight back as the next request — so the documented budget is preserved across signals. Windows is unchanged (noEINTR), and only the spawn-backoff call site was switched; other sleeps deliberately stay interruptible.Covered by a POSIX regression in
tests/test_subprocess.cthat repeatedly interrupts the deterministic backoff withSIGALRMand asserts the full cumulative delay survives.