Skip to content

test: share cargo target dirs per fixture family - #3268

Merged
messense merged 1 commit into
PyO3:mainfrom
messense:test-shared-target-dirs
Jul 19, 2026
Merged

test: share cargo target dirs per fixture family#3268
messense merged 1 commit into
PyO3:mainfrom
messense:test-shared-target-dirs

Conversation

@messense

Copy link
Copy Markdown
Member

Summary

Follow-up to #3267 (see #2189): the remaining big lever for test time. Isolated per-case target dirs meant every test case recompiled its whole dependency tree — ~3100 rustc invocations per CI job, 55% of which sccache cannot cache (cdylib links, proc-macros, build scripts).

Design

  • Cases with compatible build configurations share a per-family target dir (shared-pyo3 / shared-cffi / shared-uniffi / shared-misc), so dependency trees compile once per family. Cargo's own build lock makes concurrent builds of different crates in one dir safe, and artifacts for different feature sets coexist keyed by cargo's fingerprint.
  • Concurrent maturin builds of the same crate are not safe in a shared dir: maturin stages built artifacts through target/maturin/ (renaming them out of cargo's output path and back), so two same-crate builds race on the staged files — reproduced locally with the develop + integration uniffi-proc-macro cases running concurrently. Tests therefore hold a per-fixture fs4 file lock for the duration of the build (the same pattern the harness already uses for the cffi-provider venv). Different fixtures still build in parallel.
  • Cases keep an isolated dir when they change compile flags (pgo, zig, explicit --target), build against unusual interpreters (conda, uv multi-python), or assert on target dir contents (pep517 profile checks, stable-ABI selection which deletes the dir).
  • One small src/ hardening: the pyo3 config file maturin writes into target/maturin/ is now written via temp file + rename so concurrent builds sharing a target dir never observe a partial config.
  • The develop test timeouts go from 120s to 600s since a test can now legitimately wait on the fixture lock; the timeouts still catch hangs.

Local measurements (full suite, cold state, no sccache, Apple Silicon)

main this branch
Wall time 50.7s 47.5–63.8s
User CPU 511s 287s
Sys CPU 85s 52s
Involuntary context switches 899k 467k
test-crates/targets size after run 3.6 GB 2.1 GB
Result 345 passed 345 passed (3 runs) + warm rerun 32.8s

Total CPU work drops ~44% with no wall-time regression and no lock starvation (the slowest single test got faster, 38.5s → 27.2s). A many-core dev machine hides main's redundant compiles behind parallelism; CI's 4-core runners are CPU-bound, so the CPU cut is what should translate to CI wall time there. CI won't see the full 44% since sccache already absorbs ~60% of compiles — this targets exactly the uncacheable remainder.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NNNLg7XZnxM8Aiqrzo6c5q

Isolated per-case target dirs meant every test case recompiled its whole
dependency tree (~3100 rustc invocations per CI job, 55% uncacheable by
sccache). Cases with compatible build configurations now share a
per-family target dir (pyo3/cffi/uniffi/misc), so dependencies compile
once and cargo's build lock serializes concurrent builds in a dir.

Concurrent builds of *different* crates in one target dir are safe under
cargo's lock, but maturin stages built artifacts under target/maturin/
(moving them out of cargo's output path and back), so concurrent builds
of the *same* crate race on the staged files. Tests therefore take a
per-fixture file lock around the build while different fixtures still
run in parallel. The pyo3 config file maturin writes into target/maturin
is now written via temp file + rename so concurrent builds sharing a
target dir never observe a partial config.

Cases keep an isolated target dir when they change compile flags (pgo,
zig, explicit --target), build against unusual interpreters (conda, uv
multi-python), or assert on target dir contents (pep517 profile checks,
stable ABI selection). The develop test timeouts are raised from 120s to
600s since tests can now legitimately wait on the fixture lock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNNLg7XZnxM8Aiqrzo6c5q
@messense
messense merged commit dee0356 into PyO3:main Jul 19, 2026
85 checks passed
@messense
messense deleted the test-shared-target-dirs branch July 19, 2026 05:26
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.

1 participant