Repository navigation
Description
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:
- axum HTTP/WS handlers
- The embedded `ZccacheService` background tasks (cache writer,
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:
- 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) - tokio's structured cancellation propagates naturally, so per-
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)
- `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. - `reqwest::blocking::` → `reqwest::` (async client). This is
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. - `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-build` (per-platform orchestrators, compile_many, build_fingerprint, pipeline) — biggest crate, highest risk
- `fbuild-daemon` (HTTP/WS handlers, broker, device manager — verify async completeness)
- `fbuild-serial` (SharedSerialManager, USB-CDC retry, deploy preemption — partner with audit: blocking operations with no timeout in fbuild-serial (sub-issue of #802) #803)
- `fbuild-deploy` (esptool / avrdude / picotool / pyocd invocation, ESP32 native espflash)
- `fbuild-packages` (downloads, install pipeline, library manager — partner with audit: blocking operations with no timeout in fbuild-packages (sub-issue of #802) #805)
- `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.
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.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Jun 29, 2026
8 sub-agents finished — 8 sub-issues filed covering every Rust crate plus the cross-cutting architecture concerns:
serialportreads on a tokio worker + theserialportvstokio_serialmigration question)spawn_blockinginventory, any sync I/O leaking into axum handlers)subprocess.rs::run_commandis the choke point — the highest-leverage single conversion)await fbuild.SerialMonitor.read_lines())spawn_blocking/block_oninventory)Deployertrait async question)BuildOrchestratortrait async question — the biggest cascade)block_onsandwich at the Package trait boundary; shared asyncreqwest::Clientproposal complements audit: blocking operations with no timeout in fbuild-packages (sub-issue of #802) #805)This issue auto-closes once all 8 sub-issues close. Per-finding triage now belongs to each sub-issue's owner.
Notable cross-references:
Client::builder()chain.feat(async): whole-app tokio runtime — full async migration (#813) (#823
ci: local Linux check-ubuntu reproducer (#813) (#824)
fix(ci): cargo fmt + LOC gate fixes for #813 async migration regressions
fix(ci): cargo fmt + LOC gate fixes for #813 async migration regressi…
chore: bump version to 2.3.13
fix(async): close audit gaps from #813 sub-issues #815/#817/#818 (#842)
chore: bump version to 2.3.14