Repository navigation
Hash agent-mode jar members through the shared streaming zip comparator instead of buffering each member #914
Description
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: register comment.
Kind: refactor (performance; one duplicated routine). Source: new finding, register row C51. It is the agent-mode twin of C01 (#569, fixed by #587).
Problem (main @ 9c43dfc)
There are two routines that hash named zip members with Git SHA-256 and compare them to a patch record, and they have drifted apart.
Vendored (streams): vendor/common.rs::zip_bytes_match_after_hashes. Since #587, following the maintainer's direction on #569, it streams each member through compute_git_sha256_from_std_reader:
if !compute_git_sha256_from_std_reader(entry.size(), entry)
.is_ok_and(|hash| hash == info.after_hash)Agent mode (buffers): patch/jvm_jar.rs::verify_member_bytes inflates every patched member into a Vec and then hashes it:
let mut entry = a.by_name(member).ok()?;
let mut buf = Vec::new();
entry.read_to_end(&mut buf).ok()?;
Some(buf)
// …
content.map(|c| compute_git_sha256_from_bytes(&c))verify_member_bytes runs on every agent-mode Maven/Gradle jar record: in apply verification through verify_members (apply.rs L397–L401), in vex verification (vex/verify.rs L344–L349), in apply_jar_swap (L464) and in the rollback restore (L634). That covers every copy in ~/.m2 and in each Gradle cache hash directory.
Measured (temporary core lib test, debug build, peak RSS from /proc/self/status VmHWM, run twice with the same result): a jar holding one deflated 1 GiB member (1.0 MB on disk) plus one small member, with a record for the big member.
| Routine | Peak RSS before | Peak RSS after |
|---|---|---|
jvm_jar::verify_member_bytes |
13 MiB | 1,067 MiB |
common::zip_bytes_match_after_hashes |
13 MiB | 26 MiB |
Symptoms
None filed. Impact: memory grows with the inflated member size on each agent-mode jar verification, in apply, rollback and vex. This is the same defect #569 fixed for vendored mode, so it is per the maintainer a performance problem, not a trust one; no cap is proposed. Size: ~20 lines.
Proposed change
- Add one helper next to the stream hasher, for example
hash::git_sha256::zip_member_git_sha256(archive, name) -> Option<io::Result<String>>. It looks up a member and streams it throughcompute_git_sha256_from_std_reader. zip_bytes_match_after_hashesandverify_member_bytesboth call it.- Deleted: the buffered
read_to_endclosure inverify_member_bytes, and the duplicate lookup-and-hash loop inzip_bytes_match_after_hashes. - Keep each caller's lookup rules as they are: vendored refuses keys that fail
is_safe_relative_subpath, and agent mode reports a missing member asNotFound/Ready.
Size and scope
hash/git_sha256.rs,patch/jvm_jar.rs,vendor/common.rs: about 40 changed production lines.- Out of scope:
unpatched_membersandvendor::verify::read_zip_bytes_to_map(they need whole member bytes to rebuild the jar); the vendored per-file readers invendor/mod.rs(the sibling area); hash case (Agent-mode apply and rollback reject a manifest hash in uppercase hex that blob download accepts as valid #707); the digest helpers that Route Gradle digests through utils::digest #878 and sbt, Mill and scala-cli support in agent, hosted and vendored modes #690 touch injvm_jar.rs.
Acceptance criteria
-
verify_member_bytesdoesn't buffer a member:rg "read_to_end" crates/socket-patch-core/src/patch/jvm_jar.rsprints nothing in the verify path. - A regression test in
jvm_jarbuilds (streaming) a jar with a large zero-filled deflated member and checks theVerifyResultfor it. The peak-RSS check can stay out of CI, as Stream ZIP member hashes during vendored verification #587's did. - The existing
jvm_jartests,patch::applyandvex::verifyjar tests, the Maven/Gradle agent e2e tests andvendor::commontests stay green. -
cargo clippy --workspace --all-features -- -D warningsis clean.
Dependencies
None. It touches jvm_jar.rs lines that #878 and #690 don't edit (those change the private sha256_hex/sha1_hex).
Activity
audit-core: C29 child 2 (#913), C51 (#914), §0 refresh
mikolalysenko commentedon Oct 6, 2026
[agent] Triaged as priority:p3 (Maven/Gradle agent-mode refactor). Not a duplicate: #569 (fixed by #587) covered the vendored routine only. No open PR covers it yet.
Generated by Claude Code
refactor: re-rank queue, capacity full
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming this issue for the architecture refactor routine (highest leverage among free files: removes the buffered jar-member read on every agent-mode Maven/Gradle verify, S 3). Slice: the shared streaming helper plus
jvm_jar.rs;vendor/common.rsfollows when #1227 frees it. Branch: arch-refactor/914-jar-member-stream. Claim-ID: 2026-10-09T08:58:00Z-02b9a7Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Draft PR: #1253 (shared
zip_member_git_sha256helper + thejvm_jar.rsverify path). Thevendor/common.rscaller remains for a follow-up once #1227 no longer changes that file.Generated by Claude Code
Stream agent-mode jar members when hashing
mikolalysenko commentedon Oct 9, 2026
[agent] The agent-mode slice merged in #1253:
jvm_jar::verify_member_bytesnow streams each member throughhash::git_sha256::zip_member_git_sha256(302 → 30 MiB peak RSS on a 256 MiB member). Remaining: movevendor/common.rs::zip_bytes_match_after_hashesonto the same helper (the file is free now that #1227 merged) and commit a test for a member whose declared size disagrees with its stream. Releasing the claim until a PR takes that slice.Generated by Claude Code
mikolalysenko commentedon Oct 15, 2026
[agent] Janitor: closing as completed. #1253 (
e9be7462) meets the acceptance criteria on maine76cb9b:patch/jvm_jar.rs::verify_member_bytesstreams throughhash::git_sha256::zip_member_git_sha256, with noread_to_endleft in the file, andverify_member_bytes_streams_a_large_deflated_memberis the regression test.vendor/common.rs::zip_bytes_match_after_hashesalready streamed (viacompute_git_sha256_from_std_reader), so this issue's buffering bug is gone. Pointing that caller at the shared helper as well is optional cleanup that the audit can file separately.Generated by Claude Code