You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When N fbuild invocations start concurrently with no daemon running (a common CI / parallel-build pattern), they all spawn a full fbuild-daemon. The direct spawn path has no spawn lock, no single-flight, and no pre-bind probe: ensure_direct_daemon_running (crates/fbuild-cli/src/daemon_client.rs:818-861, entered via ensure_daemon_running() from cli/build.rs:59, cli/deploy.rs:164, device, mcp, …) does health-check → spawn → poll 10 s, retried 3×. The broker path (try_acquire_broker_daemon, daemon_client.rs:699-772) would centralize spawn but silently falls back to the direct path whenever no broker is running — the common case.
Arbitration happens only at TCP bind (SO_EXCLUSIVEADDRUSE on Windows; losers retry 3×500 ms then exit(1), crates/fbuild-daemon/src/main.rs:557-638). The expensive problem: each loser fully initializes before it loses the bind race — containment-group install (main.rs:48), USB overlay network fetch (main.rs:86), and the embedded zccache service start (main.rs:93, CompileBackend::start()) all run beforebind_listener_with_retry at main.rs:188. So N-1 doomed daemons each pay full startup and concurrently hammer ~/.fbuild/<mode>/zccache/. And a bind-race loser's exit(1) after ~1.5 s can be misread by the retrying CLI as a spawn failure rather than "someone else won."
Bind before heavy init: reorder fbuild-daemon startup so bind_listener_with_retry runs first; a bind-race loser then exits cheaply without starting containment, USB overlay, or the embedded zccache service (main.rs:48/86/93 moved after main.rs:188).
Loser defers, not fails: a bind-race loser should signal "winner exists" so the retrying CLI polls the winner's health instead of treating exit(1) as a spawn failure.
Acceptance criteria
N concurrent fbuild invocations produce exactly one fbuild-daemon (spawn-slot single-flight); losers adopt the winner.
A bind-race loser does not start the embedded zccache service / containment / USB overlay before exiting.
A concurrency test (≥16 threads) asserts one daemon and no CLI misreads a loser exit as failure.
Separate from the endpoint-identity issue (filed alongside) — that one decides which daemon owns the endpoint; this one ensures only one is spawned.
Related
Cross-repo sibling of zccache #1007 (uses the #952 spawn-slot single-flight) and soldr #1493. Filed after a cross-repo daemon-deployment audit; soldr's .spawn.lock and zccache's spawn slot are the reference implementations.
Context
When N
fbuildinvocations start concurrently with no daemon running (a common CI / parallel-build pattern), they all spawn a fullfbuild-daemon. The direct spawn path has no spawn lock, no single-flight, and no pre-bind probe:ensure_direct_daemon_running(crates/fbuild-cli/src/daemon_client.rs:818-861, entered viaensure_daemon_running()fromcli/build.rs:59,cli/deploy.rs:164, device, mcp, …) does health-check → spawn → poll 10 s, retried 3×. The broker path (try_acquire_broker_daemon,daemon_client.rs:699-772) would centralize spawn but silently falls back to the direct path whenever no broker is running — the common case.Arbitration happens only at TCP bind (
SO_EXCLUSIVEADDRUSEon Windows; losers retry 3×500 ms thenexit(1),crates/fbuild-daemon/src/main.rs:557-638). The expensive problem: each loser fully initializes before it loses the bind race — containment-group install (main.rs:48), USB overlay network fetch (main.rs:86), and the embedded zccache service start (main.rs:93,CompileBackend::start()) all run beforebind_listener_with_retryatmain.rs:188. So N-1 doomed daemons each pay full startup and concurrently hammer~/.fbuild/<mode>/zccache/. And a bind-race loser'sexit(1)after ~1.5 s can be misread by the retrying CLI as a spawn failure rather than "someone else won."Proposal
get_daemon_dir()(non-blockingcreate_new); the winner spawns, losers defer and poll health — the pattern soldr uses (.spawn.lockinsoldr-cli/src/daemon/lifecycle.rs) and zccache uses (perf(build): framework libs (~150s) recompiled per project; core cache never hydrates #952 spawn slot).fbuild-daemonstartup sobind_listener_with_retryruns first; a bind-race loser then exits cheaply without starting containment, USB overlay, or the embedded zccache service (main.rs:48/86/93moved aftermain.rs:188).exit(1)as a spawn failure.Acceptance criteria
fbuildinvocations produce exactly onefbuild-daemon(spawn-slot single-flight); losers adopt the winner.Decisions
.spawn.lock, zccache perf(build): framework libs (~150s) recompiled per project; core cache never hydrates #952).Related
Cross-repo sibling of zccache #1007 (uses the #952 spawn-slot single-flight) and soldr #1493. Filed after a cross-repo daemon-deployment audit; soldr's
.spawn.lockand zccache's spawn slot are the reference implementations.