You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(core): shape bundle-build file reads with the io_priority setting (#179)
Closes the asymmetry I flagged in #176.
## The gap
After #176, `io_priority` shaped large-file upload reads through the
per-handle
hint. Bundled small-file reads were still not shaped: they go through
`build_bundle`'s own opens, covered only by the *thread* guard from #170
- and
on Windows `low` maps to `THREAD_PRIORITY_BELOW_NORMAL`, which is
CPU-only. So a
`low` backup of a folder full of small files still read at normal I/O
priority,
which is exactly the workload someone is most likely to reach for when
testing
the setting.
(At `idle` this was already covered: `THREAD_MODE_BACKGROUND_BEGIN`
lowers I/O
and memory priority alongside CPU. The gap was `low`-only.)
## The change
`build_bundle` takes a `WorkPriority` and hints each member's file
handle as it
opens it. The executor passes the same value it already reads for the
thread
guard, so both levers run at one level and a settings change still takes
effect
per bundle with no restart.
The bundle path is the one place that visibly needs **both** levers,
which is
what makes the two-lever split concrete: the thread guard covers the
gzip CPU,
the handle hint covers the reads.
## Why `std::fs::read` had to go
`std::fs::read` opens and reads in one call and never exposes the
`File`, so
there is no handle to hint - and the hint has to land on the handle
*before* the
reads it is meant to shape. It is now split into a small `read_member`
helper
that opens, hints, then reads. Behaviour is deliberately identical:
- **Same share mode.** `std::fs::read` uses `File::open` internally, so
the
open's sharing/locking semantics are unchanged - which matters because
the
locked-file/VSS logic elsewhere depends on those semantics.
- **Same allocation.** The buffer is still sized from a stat, reusing
the `pre`
stat the loop already took instead of re-statting.
- **The size argument is a capacity hint, never a read bound.** It
deliberately
does not short-circuit the read. Treating it as a bound would hide a
grew-mid-read member from the caller's post-read coherency stat, which
remains
the sole judge of whether the bytes are a usable snapshot.
Nothing else in the build loop moved: the pre-stat size re-validation,
the
accumulated-bytes ceiling, the post-read coherency check, and the skip
bookkeeping are untouched.
## Tests
- `priority_does_not_change_the_archive_or_the_members` - builds the
same inputs
(including one member that must be skipped) at all three levels and
asserts
the `.tar.gz` bytes, the packed members, and the skip list are
identical.
Byte-equality is a meaningful assertion here because the gzip layer is
written
with a zeroed mtime for reproducibility, so two builds over the same
inputs
are bit-identical. This is the guard that `io_priority` stays a pure
scheduling hint.
- `read_member_matches_fs_read_and_ignores_a_wrong_size_hint` - all
bytes come
back at size hints of 0, exact, and oversized, and a missing file errors
rather than returning short, matching `std::fs::read` exactly.
## Gates
- `cargo fmt --all -- --check` clean
- `cargo clippy --workspace --all-targets -- -D warnings` clean
- `cargo test -p driven-core`: 461 passed, 0 failed
- `cargo test -p driven-app`: 296 passed; `driven-chaos`: 44 passed
- LF endings, ASCII dashes only. No `ui/` changes.
Unlike #176, this diff contains **no `cfg`-gated code** - `read_member`
is plain
`std`, and the per-OS branching all lives inside the
already-cross-checked
`apply_to_file_handle`. So there is no platform-divergent surface for
the
Windows-only local run to have missed; CI's ubuntu and macos legs are
the
confirmation.
## Platform reality, unchanged from #176
This is a Windows-only win. Linux and macOS have no per-descriptor I/O
priority
- both scope it to the thread - so `apply_to_file_handle` is a no-op
there and
bundle reads keep whatever the thread guard gives them.
## Docs
`design/DESIGN.md` s11.2 previously named `open_shared` as the single
hint site.
Updated: both `open_shared` and `build_bundle` hint handles now, with a
note on
why the bundle path needs both levers and why the hint is
redundant-but-harmless
at `idle`.
Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01JLB3E2Jm7knNJd37fVpH8X
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments