Skip to content

fix(graph): lock cache-miss actions by output path - #429

Merged
fortmarek merged 1 commit into
mainfrom
fix/output-path-action-locks
Nov 9, 2026
Merged

fortmarek merged 1 commit into
mainfrom
fix/output-path-action-locks

Conversation

@fortmarek

Copy link
Copy Markdown
Member

Summary

The tests (macos-latest) job intermittently fails with rustc archive errors such as failed to build archive at .once/out/rustls-0.23.40/librustls-RUSTLS_0_23_40.rlib: failed to map object file: memory map must have a non-zero length (run 42652108237, on a tree identical to a passing commit).

The test schedule runs several once test processes in one workspace. Cache-miss actions were serialized by a lock keyed on the action digest, but a Rust crate's out-dir is keyed by its label. When two processes compute different digests for the same crate, both rustc runs write the same out-dir at once and delete or truncate each other's object files.

The failed run's evidence shows how the digests diverged:

  • The aws-lc-sys build-script action was a cache hit in one process and a miss in another, which re-ran it and produced different bytes, so every downstream digest (including rustls) differed.
  • One batch was analysed with a different PATH, giving a third set of digests.

Non-reproducible inputs will keep producing such splits, so this change locks each declared output path instead of the digest. Actions without declared outputs still lock their digest. Identical actions still contend and reuse the cached result after waiting, because the same digest implies the same outputs. The locks are taken all-or-none, so a waiter never holds one lock while waiting for another.

Testing

  • New regression test: two concurrent builds whose actions write the same output under different digests. It fails with the digest-keyed lock and passes with this change.
  • cargo test -p once-cli --bins graph::analysis::actions (116 passed), cargo clippy -p once-cli --all-targets -- -D warnings, cargo fmt --all -- --check.

Concurrent test batches can compute different digests for the same
action when an upstream build is not reproducible or the host analysis
differs, so the digest lock let two rustc runs share one out-dir and
delete each other's object files. Lock each declared output path
instead, falling back to the digest for actions without outputs.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1c142-88f4-71a1-9ae1-20b34c4a6881
Co-authored-by: Marek Fort <marek@tuist.dev>
@fortmarek
fortmarek merged commit b194c3e into main Nov 9, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants