Skip to content

fix(cli): retire a leftover daemon and retry once instead of failing after 3 spawns (#1627) - #1776

Merged
zackees merged 1 commit into
mainfrom
agent/daemon-retire-leftover
Oct 29, 2026
Merged

zackees merged 1 commit into
mainfrom
agent/daemon-retire-leftover

Conversation

@zackees

@zackees zackees commented Oct 29, 2026

Copy link
Copy Markdown
Member

Why

Fixes the remaining ask in #1627, the flake that failed two release dry runs in a row with:

ERROR fbuild::output: daemon error: daemon did not become healthy after 3 spawn attempts (10s each)
running-process broker: direct daemon fallback (failed to connect to broker: No such file or directory)

Two earlier PRs already landed the diagnosis halves of that issue: #1732 reports the startup phase and each failed attempt, and #1734 gave CI a 30 s readiness budget. The third ask — "if the cause is a leftover daemon from an earlier step, attach to it or retire it rather than failing" — was still open. Three failed attempts remained terminal, so a runner with a leftover daemon (or a wedged one, #1360) could only be fixed by a manual rerun.

What changed

daemon_stop.rs gains a decision plus the I/O that acts on it, following the file's existing split (StopPlan = decision, run_daemon_stop = I/O):

  • SpawnRecovery / plan_spawn_recovery(endpoint_healthy, live_daemon_pid)
    • endpoint answers → Adopt (the failed spawn already achieved its goal; spawn nothing)
    • silent + a verified live fbuild-daemon PID → RetireAndRetry(pid)
    • silent + nothing alive → GiveUp (report the failure, as today)
  • retire_unresponsive_for_recovery — on RetireAndRetry it terminates that PID with the same terminate_and_confirm escalation fbuild daemon stop uses, then clears the records.

daemon_client::start_daemon calls it after the three attempts are exhausted and, if a daemon was retired, makes exactly one final spawn + readiness wait. On success the build continues; on failure the original error is reported with one added line naming the retired PID.

Safety

  • Identity is fail-closed: the PID comes from recorded_daemon_pid(), which requires the process to be alive and its image stem to be fbuild-daemon. A recycled PID is never signalled.
  • A daemon that answers is never retired — that is the Adopt branch, and it is what protects a parallel build on the same machine.
  • Endpoint-scoped, like daemon stop: only this endpoint's recorded PID is considered, never a scan for every daemon (another checkout's daemon is a neighbour, not debris).
  • Strictly additive: this runs only on a path that is a hard failure today, so no working build can take it.

Tests

Three new unit tests in daemon_stop_tests.rs (decision, no live daemon needed, matching the existing pattern for plan_stop):

running 8 tests
test cli::daemon_cmd::stop::tests::a_failed_spawn_retires_a_verified_unresponsive_daemon ... ok
test cli::daemon_cmd::stop::tests::a_failed_spawn_with_nothing_alive_to_retire_gives_up ... ok
test cli::daemon_cmd::stop::tests::a_healthy_endpoint_is_adopted_never_retired ... ok
test cli::daemon_cmd::stop::tests::a_healthy_endpoint_is_asked_to_stop ... ok
test cli::daemon_cmd::stop::tests::a_silent_endpoint_with_a_live_process_is_terminated_not_declared_absent ... ok
test cli::daemon_cmd::stop::tests::a_silent_endpoint_with_no_live_process_is_nothing_to_stop ... ok
test cli::daemon_cmd::stop::tests::a_live_non_daemon_pid_fails_the_identity_gate ... ok
test cli::daemon_cmd::stop::tests::wait_for_process_exit_returns_immediately_for_a_dead_pid ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 388 filtered out

Gates, all run in this worktree:

  • soldr cargo clippy --workspace --all-targets -- -D warnings — clean
  • soldr cargo fmt --all -- --check — clean
  • uv run --no-project python -m ci.run_dylint --package fbuild-cli — exit 0
  • ci/enforce_platform_boundary.py --print-totals — rows=44; dylint_rows=43 (44/0)
  • ci/check_dylint_allowlists.py — current · ci/check_test_layout.py — ok

Not claimed

The retirement branch is exercised at the decision level; the terminating I/O is the already-shipped terminate_and_confirm used by fbuild daemon stop, which keeps one killer in the codebase rather than adding a second. Forcing a real daemon into the silent-but-alive state inside an integration test needs a stopped process, which I did not add here — say the word and it can follow up as a #[cfg(unix)] it test that parks a daemon and asserts the retirement.

…1627)

Three failed spawn attempts were terminal even when the cause was a leftover
daemon from an earlier step still owning the cache root (root-owner.lock
busy) or a wedged one owning the endpoint (#1360). #1732 named the cause and
#1734 widened the readiness budget; this acts on it.

daemon_stop.rs gains SpawnRecovery + plan_spawn_recovery (endpoint answers ->
Adopt; silent with a verified live fbuild-daemon PID -> RetireAndRetry;
silent with nothing alive -> GiveUp) and retire_unresponsive_for_recovery,
which terminates the PID via the same terminate_and_confirm escalation
`fbuild daemon stop` uses. start_daemon calls it after the attempts are
exhausted and, when a daemon was retired, makes exactly one final spawn.

Identity is fail-closed (recorded_daemon_pid requires the image stem to be
fbuild-daemon), a daemon that answers is never retired, and the scope is this
endpoint only, so a parallel build is safe. Additive: the path is a hard
failure today.
@coderabbitai

coderabbitai Bot commented Oct 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zackees
zackees added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit e85f542 Oct 29, 2026
28 checks passed
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant