Repository navigation
Fix yarn classic hosted scan lock rescans (#1423) - #1426
Mikola Lysenko (mikolalysenko) merged 8 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A hosted scan of a yarn classic project re-read the whole yarn.lock once per patched package to decide which served tarballs it must fetch. On a 3000-package lock with 60 patches that made hosted scans about 2.6x slower than before #1328, even on a rescan that fetches nothing. The lock's registry copies are now collected in one pass, and the cheap "lock already pins this artifact" test runs first, so a rescan of a fully pinned lock never reads the blocks at all. The selected tarballs are unchanged. Fixes #1423 Assisted-by: Claude Code:claude-opus-5-5
Keep the old quick "is this name in the lock at all" check before the registry-copy lookup, so a grant for a package the lock never mentions costs a substring search, not a read of every block. The blocks now come from the shared memoized scan, which the rewriter also reads later in the same run. The regression test counts lock reads instead of timing them: none on a fully pinned rescan, exactly one for many unpinned deps. It fails if the cheap filters move back behind the block read. Refs #1423 Assisted-by: Claude Code:claude-opus-5-5
A hosted scan of a yarn classic project downloads each patched package's served tarball to check its sha1 and dependencies. Those downloads ran one at a time, so 60 patches cost 60 round trips in a row. They now run through the same ordered, bounded window the patch record fetch uses, and the results are folded in the same order as before, so pins, skips and --debug output are unchanged. A new test serves three tarballs (slow, fast, missing) and checks each pin carries its own tarball's sha1 and only the missing one is skipped. Refs #1423 Assisted-by: Claude Code:claude-opus-5-5
The yarn classic dependency check (#591) runs once per patched package and split every key of yarn.lock into its patterns each time, even when the served package has no dependencies at all. On a large lock this was the biggest remaining cost of a hosted scan. It now returns at once when there is nothing to look up, and only splits the keys whose text names one of the wanted packages. The result is the same set of unlocked descriptors as before. Refs #1423 Assisted-by: Claude Code:claude-opus-5-5
Picking the served tarballs to fetch still searched the whole yarn.lock twice per patched package: once for its pinned URL and once for its name. The lock is now read once for every URL it pins with a fragment, and each package is a set lookup. Refs #1423 Assisted-by: Claude Code:claude-opus-5-5
This reverts commit ee84a85.
The yarn classic dependency check (#591) runs once per patched package and split every key of yarn.lock into its patterns each time. On a large lock that was the biggest remaining cost of a hosted scan. The rewriter already splits each key once, so the check now uses a set built from those patterns, made the first time a package needs it. The result is the same set of unlocked descriptors. Target selection also goes back to a plain block scan (the shared memo it used is not read by the hosted rewriter, so it only held a copy of the lock), and builds its pinned-URL set only once a package with a sha1 asks for it. Refs #1423 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review 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 9d0c1bc. Configure here.
|
Labeled Ready for review at head
Slack announcement not sent this run (Slack send unavailable); the next run will retry. Generated by Claude Code |
Final review briefWhat it does: Removes the 2.6x slowdown in yarn classic hosted scans that #1328 introduced (#1423). Two checks that re-read the whole Risk: low. It refactors the code into precomputed sets and keeps the same predicates ( Look here
Verified
Changes I made: none. Open questions (non-blocking):
Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1423
Summary
A hosted scan of a yarn classic project got about 2.6x slower after #1328. Two
checks that #1328 added re-read the whole
yarn.lockonce for every patchedpackage. Each lock is now read once per scan. On the 3000-package / 60-patch
bench,
yarn-classic/hostedandyarn-classic/rescanare both about 56%faster than current
main. Rescan is back to its pre-#1328 time.Root cause
Both are O(patched packages × lock size):
(
hosted::engine::yarn_classic_artifact_targets). For each candidate it ranclassic_locks_registry_copy, which re-ranscan_blocksover the whole lockand split every key. It also ran two whole-lock substring searches per
candidate: one for the pinned URL and one for the name.
(
classic_deps::unlocked_descriptors). For each patched package it splitevery key of the lock into a fresh
BTreeSet, even when the package has nodependencies.
Changes
select_classic_artifactscollects the lock's registry copies(
classic_registry_copies) once, and only if some candidate still needsthem. It also collects every URL the lock already pins with a
#fragment(
quoted_fragment_urls) in one lazy pass. The cheap "already pinned" testruns first, so a rescan whose grants all carry a sha1 never reads the blocks.
heads). The Yarn classic: a patch that adds a dependency to the package's own package.json leaves vendored yarn.lock with a dangling dependency (offline frozen install fails, lock churns), and hosted silently installs without it #591 check nowlooks descriptors up in a
HashSet<&str>built from those patterns the firsttime a package needs it (
classic_deps::unlocked_descriptors_in). Thevendored path keeps
unlocked_descriptors, because it calls it once perpackage.
descriptors are reported as unlocked.
The history has one reverted experiment: a commit that fetched the tarballs
concurrently, then its revert. Code review found that it could hold up to 32
whole tarballs in memory, and that retries would hit the server together.
The wheel fetch window was designed specifically to avoid both. It also gained
almost nothing on the bench, so it was backed out.
Bench (
socket-patch-bench compare --runs 20, same 4 vCPU sandbox, bench fixture from #1422)Against
main(1779317):yarn-classic/hostedyarn-classic/rescanyarn-berry/hostedyarn-berry/rescanAgainst the commit before #1328 (
f864148):yarn-classic/hostedyarn-classic/rescanMost of the remaining hosted delta comes from the 60 served-tarball downloads
(plus gunzip and hashing) that #1328 added deliberately, for the #558 sha1 and
the #591 dependency check. #1423 itself calls those downloads intentional, so
this PR leaves them in place. Callgrind on the hosted scan: total instructions
dropped from 1.74G to about 1.1G.
yarn_classic_artifact_targetswent from68% inclusive to under 4%, and
unlocked_amongwent from 29% to 0%.Tests
hosted::engine::tests::classic_artifact_targets_read_the_lock_blocks_at_most_oncecounts block reads: 0 on a fully pinned rescan, exactly 1 for 200 unpinned depsleft: 1, right: 0) if the cheap pinned filter moves behind the block read. An earlier timing version of this test took 58 s with per-dep scans and 1.7 s with the fixquoted_fragment_urls_reads_each_resolved_url,classic_registry_copy_is_matched_by_block_name_and_source(updated to the set)classic_deps::tests::unlocked_descriptors_in_matches_the_block_scan(the set form agrees with the block scan)Commands run locally:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features: 14229 passed, 13 failed. All 13also fail on
mainin this sandbox and none touch yarn code. Ten arepermission-failure tests that can't fail as root (chmod is ignored), for
example
covgap_commands_vendor::*_state_write_failure_*andvendor::pypi_poetry::tests::wire_write_failure_*. The others needpatches-api.socket.dev, which the proxy blocks, for examplee2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed.scripts/yarn-classic-vex-matrix.sh 1.22.22 1.0.2(real yarn,SOCKET_PATCH_YARN_E2E_REQUIRED=1): exit 0, every suite passed (110 PASScells)
cargo test -p socket-patch-cli --test scan --test hosted_memory_engine --test e2e_redirect_yarn_classic_build --test e2e_vendor_yarn_classic_build --test e2e_vendor_yarn_classic_dev_flow:all pass
Review notes (
/code-review high, findings not fixed)The read is bounded at once per lock. The alternative was a whole-lock
substring search per dep, which is what this PR removes.
yarn-berry'sberry_lock_locksscans per dep too. That is a differentbackend and not part of this regression; the berry bench is unchanged. Left
for a separate issue.
Follow-ups
concurrent, they need the wheel window's design: a small cap, a first-attempt
window, then serial resumption.
🤖 Generated with Claude Code
https://claude.ai/code/session_018G3cdPFQuuBbZahpQSKpoB
Generated by Claude Code