Repository navigation
Write every utils::fs atomic file through one stage-and-rename core (#728) - #858
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Every atomic file write (lockfiles, manifests, vendored artifacts, group-commit replays) now goes through one blocking core that owns the stage name, the stage's creation mode, the fsync, the set-mode-then-rename order and the unlink on failure. The async writers run it on the blocking pool, and the blob cache builds its .socket-dl- stage name through the same helper. A hardening fix to the write path now lands once instead of twice. No behavior change: the same bytes, modes, fsyncs and stage names. Refs #728 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] The
The cause is #605's store-copy merge. #851 fixes these tests, and #850 adjusts the same file. I'm not porting that fix here, because this routine never edits a file that open PRs are already changing. CI will go green here once #851 merges and Generated by Claude Code |
Brings in the vex_consumed alias test fix (#849) that main's red test/test-release/coverage jobs were waiting on. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c5118ba. Configure here.
|
Burn-down agent: labeled Ready for review.
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #728
Summary
Every atomic file write in
utils::fsnow goes through one blocking core,stage_and_rename_blocking. The core owns the stage name, the stage's creation mode, the fsync, the order of setting the mode and then renaming, the unlink on error, and the directory fsync. Before this, the async writer (stage_and_rename+create_stage+commit_stage) and the blockingatomic_write_synccopied that code line for line. Three places built stage names. Now onestage_path(path, prefix)builds them, and the blob cache's.socket-dl-stage uses it too.Why
.npmrcstage-mode fix only exists in both copies because someone remembered to apply it to each.getblob writer reuses this) · D ≈3 (2 writer copies → 1, 3 stage-name builders → 1, 4 permission prologues → 1) · R L. This is the top eligible item inregister/90-refactor.md's Queue. The higher-scoring rows touch files that open PRs are changing.What changed
utils/fs.rs:WriteOpts { capture, durable, preserve_mode, record }struct, with three named policies:COMMIT_POINT,ARTIFACTandUNSYNCED.write_atomic, which handles group-commit capture and the durability barrier, then runs the core onrun_blockingand records the path for the next barrier.stage_and_rename_blocking.atomic_write_syncis the core withdurable: true.stage_open_optionsreplacescreate_stage.api/blob_fetcher.rs: the streaming cache writer builds its stage name withutils::fs::stage_path(dest, ".socket-dl-"). Since Stream patch blob and diff downloads to disk (#571) #607 that writer streams the body and checks the hash between stage and rename, so it keeps its own body (the reason is noted on the issue).Deleted
create_stage,atomic_write_bytes_as, the asyncstage_and_rename/commit_stagepair, the copy insideatomic_write_sync, four repeated "read the destination's permissions" prologues, and the stage-name code inblob_fetcher.git diff --stat: 2 files, +256 / −180.WriteOptstable and its docs..socket-stage-and.socket-dl-are now formatted in exactly one production function (stage_path).Behavior
None. The bytes, modes, fsync points, stage-name shape and error cleanup are all the same:
Two internal differences, neither observable in the output:
contentonce into the blocking closure. They now make one hop to the blocking pool instead of one per tokio fs op.The blob stage's fallback stem for a path with no file name is now
fileinstead ofblob. That path can't occur, because blob destinations are always<dir>/<hash>.Tests
every_writer_shares_one_stage_and_rename_coreruns all six writers (with bothpreserve_modearms for unsynced and sync) on a 0600 destination. Each writer writes the exact bytes and leaves no stage behind. A writer keeps the 0600 bits exactly when its policy preserves mode, so the blocking and async writers agree.stage_path_is_a_unique_hidden_sibling.stage_open_options. The RLIMIT_FSIZE torn-write child test, group-commit replay and capture tests, and durability tests still pass.Commands:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --lib: 5027 passed. 4 failed, the known root-only tests that fail onmaintoo (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files).cargo test -p socket-patch-core --test blob_fetcher_edges_e2e --test covgap_api_blob_fetcher: 30 passed.cargo test -p socket-patch-cli --all-features --test vendor_group_commit_e2e --test in_process_agent_reapply --test cli_apply_silent: 21 passed.cargo test --workspace --all-features: not completed locally, because the sandbox ran out of disk linking the 145+ CLI test binaries. CI covers it:test (ubuntu-latest),test (windows-latest)and every e2e job;-p socket-patch-cli --lib:test (macos-latest),test-releaseandcoverage. The failures are the twovex_consumedtests that fail onmaintoo, which Fix vex alias tests broken by store-copy merge #851 fixes (see the comment below).Risk
L. This is the crash-safety write path, so the risk is in the details. The fsync, mode and rename order is copied from the existing blocking writer, which the group-commit replay already used, and the tests above pin each policy.
Note
Medium Risk
Touches the crash-safe patch and cache write path (fsync, rename, permissions), though behavior is intended to stay the same and new tests lock each writer policy.
Overview
Fixes #728 by routing every
utils::fsatomic writer through a single blockingstage_and_rename_blockingpath, with async entry points delegating viawrite_atomicand aWriteOptspolicy table (COMMIT_POINT,ARTIFACT,UNSYNCED) for capture, durability barrier, fsync, mode preservation, and artifact recording.The duplicated async stack (
create_stage,commit_stage,stage_and_rename,atomic_write_bytes_as) and the extra copy insideatomic_write_syncare removed; the six public writers become thin wrappers. Stage file names are built in one place:stage_path(path, prefix), and the blob cache downloader inblob_fetchernow uses it with the.socket-dl-prefix instead of local formatting.Tests add coverage that all writers share the same core (bytes, modes, no stage litter) and that
stage_pathproduces unique hidden siblings for both.socket-stage-and.socket-dl-prefixes.Reviewed by Cursor Bugbot for commit c5118ba. Configure here.
Generated by Claude Code