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
Meta tracking issue. Sub-agents will post per-crate audit reports
as sub-issues. Goal: convert every blocking sync code path in
fbuild that CAN be async to async, so the whole app runs on one
tokio runtime — the same one the embedded `ZccacheService`
(#789 Phase 4 stage 2 / #800) and the axum HTTP
server use.
Motivation
Tokio-console attaches to a single runtime and surfaces every task
spawned on it. Today only:
…run on the daemon's tokio runtime. Everything else — rayon
workers, `std::thread::spawn` background work, every
`std::process::Command::output()`, every `std::fs::read_to_string` on
a hot path — is invisible to tokio-console AND can't be cancelled
via the daemon's `tokio::sync::watch` shutdown signal.
After this migration:
one runtime
one tokio-console attach pane shows everything
a single ctrl-C collapses every in-flight operation in the
daemon (compiles, fingerprints, downloads, serial reads, deploy
child processes — everything)
`std::thread::spawn(...)` → `tokio::spawn(...)` (if the body can
be made async) or `tokio::task::spawn_blocking(...)` (if it's
CPU-bound). The current state mixes both without a clear contract.
`rayon::scope` / `rayon::par_iter` / `ThreadPoolBuilder` →
`futures::stream::FuturesUnordered` + `tokio::task::spawn`,
or keep rayon for genuinely CPU-bound parallelism but ensure it
doesn't hold tokio workers (`spawn_blocking` wrapper).
`std::process::Command::output()` / `.wait()` →
`tokio::process::Command::output().await`. Every subprocess
spawn in fbuild's hot paths (compiler, linker, esptool,
avrdude, picotool, pyocd) is a candidate.
`std::sync::Mutex` / `std::sync::RwLock` held across `.await` →
switch to `tokio::sync::Mutex` / `RwLock`. Held across nothing
→ leave alone.
`std::sync::mpsc` cross-task channel → `tokio::sync::mpsc` (or
`flume` if a sync `Receiver` is needed on the rayon side).
`std::fs::read` / `std::fs::write` on hot paths → `tokio::fs::*`
(compile output reads, fingerprint cache writes, daemon status
file). One-shot config reads at startup are fine to keep sync.
`tokio::runtime::Handle::block_on(...)` calls from async
contexts — these are pure anti-pattern; should be `.await`.
`std::sync::OnceLock` for state that gets lazy-initialised from
inside an async task → `tokio::sync::OnceCell` (the embedded
`ZccacheService` handle in `compile_backend::GLOBAL` is an
example to study).
What is NOT in scope
CPU-bound work that legitimately needs a thread pool (hashing,
compression, native cargo compilation work). Keep those on
rayon / `spawn_blocking`, but document the contract.
Anything in `crates/*/tests/` or `bench/` (those don't need to
run on the prod runtime).
`Drop` impls.
Code that's only invoked from CLI binaries with their own
short-lived runtime (`fbuild-cli`'s diagnostic subcommands).
CI scripts and Python tooling.
Sub-issue checklist
Each sub-agent files one of these. The checklist auto-updates as
sub-issues land.
`fbuild-cli` + `fbuild-python` (HTTP client, PyO3 bindings — surface async boundaries to Python via `pyo3-async-runtimes`)
`fbuild-core` + foundational crates (`subprocess.rs` is the choke point — converting it cascades into every caller)
Per-sub-issue report format
Each sub-issue should include:
a brief intro saying which crate was audited
a table of findings: `Severity` | `File:line` | `Current pattern` | `Proposed async replacement` | `Notes (cascades / blocking gotchas)`
severity per finding (CRITICAL = blocks tokio worker on the daemon's hot path; HIGH = blocks user-visible build; MEDIUM = blocks setup/discovery; LOW = invisible improvement)
a quick "what was searched / how" so a follow-up auditor can re-check
This issue auto-closes once every sub-issue is closed.
#802 audits "blocking operations with no timeout". This issue
audits "operations that could be async on the daemon runtime".
There's overlap: a blocking `reqwest::blocking::get` in #805 is
both untimely AND not on the runtime. The fixes compose — adding
a timeout AND making it `async` is the right end-state. Each
audit is filed separately so the diffs can be reviewed
independently.
Meta tracking issue. Sub-agents will post per-crate audit reports
as sub-issues. Goal: convert every blocking sync code path in
fbuild that CAN be async to async, so the whole app runs on one
tokio runtime — the same one the embedded `ZccacheService`
(#789 Phase 4 stage 2 / #800) and the axum HTTP
server use.
Motivation
Tokio-console attaches to a single runtime and surfaces every task
spawned on it. Today only:
audit writer, in-flight counter)
…run on the daemon's tokio runtime. Everything else — rayon
workers, `std::thread::spawn` background work, every
`std::process::Command::output()`, every `std::fs::read_to_string` on
a hot path — is invisible to tokio-console AND can't be cancelled
via the daemon's `tokio::sync::watch` shutdown signal.
After this migration:
daemon (compiles, fingerprints, downloads, serial reads, deploy
child processes — everything)
request deadlines (the partner audit at audit: blocking operations with no timeout (meta) #802)
compose with the existing shutdown notify
What to look for (sub-agents)
be made async) or `tokio::task::spawn_blocking(...)` (if it's
CPU-bound). The current state mixes both without a clear contract.
`futures::stream::FuturesUnordered` + `tokio::task::spawn`,
or keep rayon for genuinely CPU-bound parallelism but ensure it
doesn't hold tokio workers (`spawn_blocking` wrapper).
`tokio::process::Command::output().await`. Every subprocess
spawn in fbuild's hot paths (compiler, linker, esptool,
avrdude, picotool, pyocd) is a candidate.
switch to `tokio::sync::Mutex` / `RwLock`. Held across nothing
→ leave alone.
`flume` if a sync `Receiver` is needed on the rayon side).
(compile output reads, fingerprint cache writes, daemon status
file). One-shot config reads at startup are fine to keep sync.
the dominant pattern in `fbuild-packages` and is on the
CRITICAL list in the partner audit audit: blocking operations with no timeout in fbuild-packages (sub-issue of #802) #805.
contexts — these are pure anti-pattern; should be `.await`.
inside an async task → `tokio::sync::OnceCell` (the embedded
`ZccacheService` handle in `compile_backend::GLOBAL` is an
example to study).
What is NOT in scope
compression, native cargo compilation work). Keep those on
rayon / `spawn_blocking`, but document the contract.
run on the prod runtime).
short-lived runtime (`fbuild-cli`'s diagnostic subcommands).
Sub-issue checklist
Each sub-agent files one of these. The checklist auto-updates as
sub-issues land.
Per-sub-issue report format
Each sub-issue should include:
This issue auto-closes once every sub-issue is closed.
Relationship to #802
#802 audits "blocking operations with no timeout". This issue
audits "operations that could be async on the daemon runtime".
There's overlap: a blocking `reqwest::blocking::get` in #805 is
both untimely AND not on the runtime. The fixes compose — adding
a timeout AND making it `async` is the right end-state. Each
audit is filed separately so the diffs can be reviewed
independently.