fix(files,server): age-gate stale-artifact sweep to stop it deleting live concurrent exports#172
Merged
Merged
Conversation
…live concurrent exports sweep_stale_artifacts previously matched staging/displaced siblings by name alone, with no way to tell a genuine crash orphan from another live process's in-flight export. A concurrent export of the same target could delete the first export's staging directory outright, and if the timing landed between swap_into_place's two renames, the displaced original too, defeating the rollback and permanently losing the target with no recovery path. The sweep now only removes a staging/stale sibling once it is at least STALE_ARTIFACT_MIN_AGE (5 minutes) old by mtime, which reliably distinguishes a crash leftover from a live sibling's artifacts. swap_into_place's displaced backup has its mtime explicitly refreshed before the rename (a rename preserves the source's mtime rather than resetting it), so it is covered by the same age gate as staging instead of inheriting the original target's already-stale mtime. GeneratorService::save_categorized_tools also gains a keyed Arc<Mutex<()>> per output_dir, mirroring the existing per-server-id introspectors lock, so two JSON-RPC calls targeting the same output directory no longer race on the underlying staging/swap. Fixes #169
bug-ops
force-pushed
the
fix/169-stale-sweep-race
branch
from
July 9, 2026 20:21
b91d5bf to
857c5e5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FileSystem::sweep_stale_artifactsmatched staging/displaced siblings by name alone, with no way to distinguish a genuine crash orphan from another live process's in-flight export. A concurrent export of the same target could delete the first export's staging directory outright, and if the timing landed betweenswap_into_place's two renames, the displaced original too — defeating the rollback and permanently losing the target with no recovery path.STALE_ARTIFACT_MIN_AGE(5 minutes) old by mtime, reliably distinguishing a crash leftover from a live sibling's artifacts.swap_into_place's displaced backup has its mtime explicitly refreshed (viatouch_dirontarget, before the rename) so it's covered by the same age gate as staging, instead of inheriting the original target's already-stale mtime.GeneratorService::save_categorized_toolsgains a keyedArc<Mutex<()>>peroutput_dir, mirroring the existing per-server-idintrospectorslock, so two JSON-RPC calls targeting the same output directory no longer race on the underlying staging/swap.Fixes #169
Test plan
cargo +nightly fmt --checkcargo +stable clippy --all-targets --all-features --workspace -- -D warningscargo nextest run --all-features --workspace --no-fail-fast(740 passed)cargo test --doc --all-features --workspaceRUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --all-featuresdisplace_existing_target_refreshes_displaced_mtime,sweep_stale_artifacts_skips_recent_orphans,sweep_stale_artifacts_older_than_removes_aged_orphans,test_export_does_not_sweep_recent_sibling_artifacts(mcp-files);test_export_lock_for_same_output_dir_shares_one_lock,test_export_lock_for_different_output_dirs_get_independent_locks,test_export_lock_stale_eviction_does_not_remove_unrelated_entry(mcp-server)