Skip to content

perf(package-manager): parallelize cold-batch slot linking#12249

Merged
zkochan merged 1 commit into
mainfrom
perf-parallelize-cold-link
Jun 6, 2026
Merged

perf(package-manager): parallelize cold-batch slot linking#12249
zkochan merged 1 commit into
mainfrom
perf-parallelize-cold-link

Conversation

@zkochan

@zkochan zkochan commented Jun 6, 2026

Copy link
Copy Markdown
Member

Problem

CreateVirtualStore has two batches: a warm batch (packages already in the store) and a cold batch (packages it had to download). The warm batch links every slot in a single rayon par_iter — fast. The cold batch, however, ran each per-snapshot InstallPackageBySnapshot as a future inside one cooperative try_join_all task, and each future called the blocking CreateVirtualDirBySnapshot::run (a rayon::join over CAS import + symlink) directly.

Because all those futures share a single task, a blocking call in one prevents the others from being polled — so on a fresh install the 1308 slot-links serialized one-at-a-time, each paying its own rayon::join dispatch.

This is invisible on warm/restore installs (everything is warm), but it dominates the cold path — which is exactly what a pnpr client install hits: its fast offloaded resolution means every package goes through the cold batch. Phase timing on a fresh ~1300-package install:

pnpr (cold batch):   CreateVirtualStore = ~14.5s
local fresh (warm):  CreateVirtualStore = ~5-7s   (store pre-filled during its slower resolve)

Same link work, ~2-3x slower, purely from the serialization.

Fix

Defer the slot link out of the per-snapshot download future (new InstallPackageBySnapshot::defer_link) and run all the cold links in one parallel rayon pass after the downloads — mirroring the warm batch.

Result

Fresh ~1300-package install through a pnpr server:

before after
CreateVirtualStore ~14.5s ~9.5s
end-to-end install ~18.5s ~13s

And end-to-end, pnpr now beats the equivalent non-pnpr install:

ON  (pnpr):  ~13s
OFF:         ~15.5s

…which is the expected outcome, since the pnpr client offloads resolution and downloads ~half the metadata. Progress events are unchanged in kind and order (fetched/found_in_store during download, imported once the slot is linked).

Tests

685 pacquet-package-manager + pacquet-cli tests pass (incl. the pnpr_install integration tests, which exercise the cold batch). cargo fmt + clippy clean.

Related

Composes with #12248 (dedicated CAS-write pool), which removes a separate extraction↔link rayon-pool contention; both target the same materialize phase.


Written by an agent (Claude Code, claude-opus-4-8).

Summary by CodeRabbit

  • Refactor

    • Optimized package installation workflow by restructuring virtual store slot linking operations.
  • Tests

    • Updated package installation tests to reflect changes to slot linking behavior.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cceb2ce4-081c-4120-8482-3a4931de4ef5

📥 Commits

Reviewing files that changed from the base of the PR and between d276a78 and 7966e73.

📒 Files selected for processing (3)
  • pacquet/crates/package-manager/src/create_virtual_store.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization
Derive simple conversions for branded strings using #[derive(derive_more::From)] and #[derive(derive_more::Into)] instead of handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like `${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Choose owned vs. borrowed parameters to minimize copies; widen to t...

Files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
🧠 Learnings (25)
📓 Common learnings
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:29.444Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not invoke `BuildModules` and discards `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput`. This is pre-existing, documented behavior (mirroring upstream `link.ts:167-170`): `importing_done` fires once extraction and symlink linking are complete, and the fresh-lockfile path does not run lifecycle scripts. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Do not flag this omission as a bug; wiring lifecycle scripts into the fresh-lockfile path is tracked as future work separate from perf refactors.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In pacquet (pnpm/pnpm repo), `ResolvedPackage.optional` AND-folding intentionally mirrors pnpm's resolveDependencies.ts:1627-1648 revisit behavior: only the directly-visited package's `optional` flag is updated on revisit, not transitive descendants. pnpm CLI corrects stale optional flags via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`. Pacquet does not yet have this pruner equivalent, so raw `node.optional` flows directly into snapshot/virtual-store via `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up issue to port `copyDependencySubGraph` is planned.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not run lifecycle scripts (`BuildModules` is not invoked, and `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput` is discarded). This is pre-existing behavior documented by an in-code comment mirroring upstream `link.ts:167-170`. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Wiring lifecycle scripts into the fresh-lockfile path is tracked as future work, separate from this file's concern.
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not invoke `BuildModules` and discards `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput`. This is pre-existing, documented behavior (mirroring upstream `link.ts:167-170`): `importing_done` fires once extraction and symlink linking are complete, and the fresh-lockfile path does not run lifecycle scripts. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Do not flag this omission as a bug; wiring lifecycle scripts into the fresh-lockfile path is tracked as future work separate from perf refactors.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-06-04T14:40:29.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:29.444Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-24T21:11:04.272Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In pacquet (pnpm/pnpm repo), `ResolvedPackage.optional` AND-folding intentionally mirrors pnpm's resolveDependencies.ts:1627-1648 revisit behavior: only the directly-visited package's `optional` flag is updated on revisit, not transitive descendants. pnpm CLI corrects stale optional flags via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`. Pacquet does not yet have this pruner equivalent, so raw `node.optional` flows directly into snapshot/virtual-store via `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up issue to port `copyDependencySubGraph` is planned.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not run lifecycle scripts (`BuildModules` is not invoked, and `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput` is discarded). This is pre-existing behavior documented by an in-code comment mirroring upstream `link.ts:167-170`. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Wiring lifecycle scripts into the fresh-lockfile path is tracked as future work, separate from this file's concern.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-24T21:11:04.272Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Use snapshot tests with `insta` and carefully review diffs when intentional changes alter snapshots; accept with `cargo insta review` only after careful review

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-24T16:07:54.784Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11904
File: pacquet/crates/package-manager/src/install.rs:556-560
Timestamp: 2026-05-24T16:07:54.784Z
Learning: In pacquet's `is_modules_yaml_consistent` (pacquet/crates/package-manager/src/install.rs), `enableGlobalVirtualStore` is intentionally NOT checked as a separate field. Upstream pnpm's `validateModules.ts` does not persist or check `enableGlobalVirtualStore` in `.modules.yaml` either. Drift on this setting is caught indirectly: toggling `enableGlobalVirtualStore` changes `config.effective_virtual_store_dir()` (GVS-on → `<store>/v11/links`, GVS-off → `<project>/node_modules/.pnpm`), so the existing `modules.virtual_store_dir == config.effective_virtual_store_dir()` comparison in `is_modules_yaml_consistent` already detects the mismatch and prevents the short-circuit. Do not flag the absence of an explicit `enableGlobalVirtualStore` field as a bug.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
📚 Learning: 2026-05-20T23:23:43.636Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11788
File: pacquet/crates/lockfile/src/snapshot_dep_ref.rs:52-56
Timestamp: 2026-05-20T23:23:43.636Z
Learning: In the pacquet lockfile crate, both `SnapshotDepRef::Link` and `ImporterDepVersion::Link` intentionally use raw `Link(String)` (not a newtype) for the link target. Upstream pnpm stores `link:` targets verbatim without validation (`refToRelative` only checks for the literal `link:` prefix). A `LinkTarget` newtype should not be suggested for either variant; if branding ever makes sense it should be applied to both sites together in a dedicated refactor PR, not in a bug-fix PR.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-06-02T13:18:30.659Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12134
File: pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs:311-325
Timestamp: 2026-06-02T13:18:30.659Z
Learning: In pacquet's lockfile resolution verifier (`pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs`), URL-keyed tarball dependencies do NOT need a separate `non_semver_version` field in `VerifyCtx`. Unlike the TypeScript side (which derives `version` from `snapshot.version` and threads `nonSemverVersion` separately), pacquet's `collect_candidates` takes `version` from the lockfile key suffix. For a URL-keyed dep the key is `name@<url>`, so `ctx.version` is the URL string, which fails `node_semver::Version::parse(ctx.version)` and the existing guard `if node_semver::Version::parse(ctx.version).is_err() { return ResolutionVerification::Ok; }` already skips the registry lookup correctly. Adding a `non_semver_version` field to `VerifyCtx` for this purpose would be inert.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
📚 Learning: 2026-05-19T19:23:00.981Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11752
File: pacquet/crates/config/src/lib.rs:1062-1073
Timestamp: 2026-05-19T19:23:00.981Z
Learning: In `pacquet/crates/config/src/lib.rs`, `modules_dir` does not need a `!virtual_store_dir_explicit` guard on its workspace re-anchor because `modules_dir` is in pnpm's `excludedPnpmKeys` (filtered out by `WorkspaceSettings::clear_workspace_only_fields`) and therefore can only be set by workspace yaml (applied immediately after) or env vars (applied later in the cascade) — not by global `config.yaml`. `virtual_store_dir`, by contrast, IS settable from global config and requires the `if !virtual_store_dir_explicit` guard to survive the workspace-root re-anchor.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-06-06T18:58:37.156Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12243
File: pacquet/crates/package-manager/src/install_package_by_snapshot.rs:319-322
Timestamp: 2026-06-06T18:58:37.156Z
Learning: When reviewing Rust code, do not assume `matches!(expr, Pattern(_))` will move out of `expr` if `Pattern(_)` contains no by-value bindings. `matches!` desugars to a `match` that auto-borrows the scrutinee for discrimination, so even if `expr` is a non-`Copy` value behind a shared reference (e.g., `&T`), the macro should not move-out of the borrowed data purely due to `matches!`. Treat `matches!(&expr, Pattern(_))` as a readability/clarity improvement, not a correctness requirement. Only flag potential move-out-of-borrow risks when the pattern includes by-value bindings that would require moving the matched value.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-23T16:55:36.507Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: pacquet/crates/cli/tests/lockfile_verification.rs:158-162
Timestamp: 2026-05-23T16:55:36.507Z
Learning: In `pacquet/crates/cli/tests/lockfile_verification.rs`, the `trust_lockfile_skips_verification` and `trust_lockfile_cli_flag_skips_verification` tests intentionally do NOT assert `output.status.success()`. The hand-rolled fixture lockfile uses a placeholder integrity hash (`sha512-AAA…`), so the install always fails the downstream tarball integrity check regardless of the supply-chain gate. The contract being tested is "gate-skipped, not install-succeeded"; asserting success would require generating a real lockfile via the `generate_lockfile` pattern (see `hoist.rs`) which is considered not worth the extra wiring for an opt-out smoke test.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-05-20T21:18:56.391Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11778
File: pacquet/crates/resolving-local-resolver/tests/resolve.rs:365-372
Timestamp: 2026-05-20T21:18:56.391Z
Learning: In `pacquet/crates/resolving-local-resolver/tests/resolve.rs`, the test `fail_when_resolving_from_not_existing_directory_an_injected_dependency` intentionally uses `injected: false`. The test is a verbatim port of the upstream pnpm TypeScript test (resolving/local-resolver/test/index.ts at ef87f3ccff). The `injected` flag only affects the file/link protocol choice for plain directory paths; when the `file:` scheme is explicit in the bare specifier, the flag has no effect on the resolution code path. The misleading test name is inherited from upstream.

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Prefer real fixtures over dependency-injection seams — use `tempfile::TempDir`, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Tests that need the mocked registry should start `pnpr` through `pacquet-testing-utils`; `cargo test` / `cargo nextest run` should not require a separate `just registry-mock launch` step

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/*.rs : Tests are documentation — do not duplicate test scenarios, edge cases, failure modes, or worked examples in prose when they are already captured by tests

Applied to files:

  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
📚 Learning: 2026-05-25T14:58:11.105Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11931
File: pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs:560-589
Timestamp: 2026-05-25T14:58:11.105Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs`, all per-`(registry, name[, version])` caches in `NpmResolutionVerifier` (`published_at`, `full_meta`, `full_meta_for_trust`, `abbreviated_meta`, `local_meta`) intentionally use the same pattern: lock → miss-check → release lock → await fetch/load → re-acquire lock → insert. This uniform pattern is deliberate; do not flag individual caches for using it. The known follow-up improvement (replacing the pattern with `tokio::sync::OnceCell` per key inside a `Mutex<HashMap<…>>`) is tracked as a future structural change to cover all five caches simultaneously.

Applied to files:

  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-06-04T20:24:32.096Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12198
File: pnpr/crates/pnpr/src/storage.rs:469-477
Timestamp: 2026-06-04T20:24:32.096Z
Learning: In `pnpr/crates/pnpr/src/storage.rs` (pnpm/pnpm repo, Rust), `Store::list_package_names` intentionally uses `fs::try_exists(...).await.unwrap_or(false)` and `if let Ok(mut inner) = fs::read_dir(...)` — NOT `?`-propagation — for per-entry checks. This is deliberate best-effort / verdaccio-style search behavior: (1) `try_exists(stray_file/package.json)` returns `ENOTDIR` (not `NotFound`) for a stray non-package file in the store root, so `?` would fail the entire search; (2) the `@`-scope `read_dir` would fail on a non-directory `@`-named entry; (3) switching to `DirEntry::file_type()` would stop following symlinked package dirs. Failures that DO propagate are preserved: opening the store root itself, and `next_entry()` during the walk. Do not suggest blanket `?`-propagation for these per-entry checks.

Applied to files:

  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-20T01:42:44.958Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11755
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:370-380
Timestamp: 2026-05-20T01:42:44.958Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/pick_package.rs`, the fetch-error fallback that returns `Ok(PickPackageResult { meta: disk, picked_package: picked })` — even when `picked` is `None` — is intentional upstream parity with pnpm's `pickPackage.ts` catch block (https://github.com/pnpm/pnpm/blob/f657b5cb44/resolving/npm-resolver/src/pickPackage.ts#L420-L431). When a fetch fails and a disk mirror exists, the stale-mirror pick (including null/None) is returned verbatim; the transport error is logged via `tracing::debug!`. Do not flag this as a bug.

Applied to files:

  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-05-04T17:01:30.322Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11452
File: exec/commands/src/dlx.ts:298-305
Timestamp: 2026-05-04T17:01:30.322Z
Learning: In `exec/commands/src/dlx.ts`, when `promptApproveDlxBuilds` exits early (no `approve-builds` command or non-interactive stdin), the `cachedDir` is intentionally still linked into the shared dlx cache even if some build scripts were skipped. This is by design: (1) it mirrors `pnpm add -g` behavior which also commits the install with skipped builds in non-interactive mode; (2) the cache key already encodes `allowBuild`, so users can recover by re-invoking with `--allow-build=<pkg>` which generates a fresh cache entry; (3) re-installing on every non-interactive invocation would be a perf regression for CI. Do not suggest preventing the symlink in this code path.

Applied to files:

  • pacquet/crates/package-manager/src/create_virtual_store.rs
📚 Learning: 2026-06-04T06:04:05.107Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12181
File: worker/src/start.ts:504-520
Timestamp: 2026-06-04T06:04:05.107Z
Learning: In pnpm/pnpm's pnpr install accelerator, the `/v1/install` response has a two-level framing structure:
1. **Outer layer** (full HTTP body): `[u32 outer header length][outer header JSON][files payload]` — `fetchFromPnpmRegistry` (pnpr/client/src/fetchFromPnpmRegistry.ts) strips the outer layer with `body.subarray(4 + headerLength)` and passes the remaining bytes to `writeCafsFiles`.
2. **Inner layer** (files payload): the files payload itself starts with its own `[u32 inner json length][inner header JSON]` prefix (built by the server's `build_files_payload` / `empty_files_payload_prefix`), followed by `[64-byte digest][u32 size][1-byte exec][content]` frames and a 64-zero-byte end marker.

`writeCafsFiles` in `worker/src/start.ts` is correct to read `jsonLen = payload.readUInt32BE(0)` and start frames at `offset = 4 + jsonLen` — this skips the inner header. The same two-level structure is mirrored in the Rust reference client (`parse_inline_response` + `write_files_payload`). Do not fla...

Applied to files:

  • pacquet/crates/package-manager/src/create_virtual_store.rs
🔇 Additional comments (7)
pacquet/crates/package-manager/src/install_package_by_snapshot.rs (2)

117-126: LGTM!


261-262: LGTM!

Also applies to: 551-565

pacquet/crates/package-manager/src/create_virtual_store.rs (4)

756-764: LGTM!


794-802: LGTM!


846-893: LGTM!


924-936: LGTM!

pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs (1)

404-404: LGTM!

Also applies to: 473-473, 542-542


📝 Walkthrough

Walkthrough

InstallPackageBySnapshot adds a defer_link flag to skip virtual-store slot materialization. CreateVirtualStore uses this flag to defer cold-batch slot linking to a dedicated isolated parallel pass after fetch completion, while hoisted mode and error handling remain unchanged.

Changes

Cold-batch slot linking deferral

Layer / File(s) Summary
defer_link configuration flag
pacquet/crates/package-manager/src/install_package_by_snapshot.rs
Adds public defer_link: bool field to InstallPackageBySnapshot with documentation describing per-snapshot virtual-store slot deferral behavior.
Virtual-store slot deferral in install
pacquet/crates/package-manager/src/install_package_by_snapshot.rs
Plumbs defer_link through struct destructuring in ::run and skips CreateVirtualDirBySnapshot invocation when the flag is enabled.
Cold-batch data collection and invocation
pacquet/crates/package-manager/src/create_virtual_store.rs
Introduces cold-results container shape storing (PackageKey, SnapshotEntry, cas_paths) tuples; modifies cold-batch InstallPackageBySnapshot call to set defer_link: true and capture extended snapshot data.
Cold link pass and hoisted assembly
pacquet/crates/package-manager/src/create_virtual_store.rs
Adds isolated-only parallel "cold link pass" using Rayon with tokio multi-thread detection after cold downloads; updates hoisted CAS index assembly to destructure the new tuple shape (snapshot_key, _snapshot, paths).
Test updates for defer_link
pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
Three integration tests explicitly set defer_link: false in InstallPackageBySnapshot struct literals (cold_batch_reuses_in_flight_prefetch_from_mem_cache, without_mem_cache_skips_coordination_and_downloads, cold_batch_falls_back_when_prefetch_failed).

Sequence Diagram(s)

sequenceDiagram
  participant CreateVirtualStore
  participant ColdInstall as InstallPackageBySnapshot<br/>(defer_link=true)
  participant ColdFetch as Cold fetch/cache
  participant ColdLinkPass as Cold link pass<br/>(Rayon parallel)
  participant CreateVirtualDir as CreateVirtualDirBySnapshot

  CreateVirtualStore->>ColdInstall: install with defer_link=true
  ColdInstall->>ColdFetch: fetch & extract, skip slot
  ColdFetch-->>CreateVirtualStore: return cas_paths
  CreateVirtualStore->>ColdLinkPass: parallel slot linking
  ColdLinkPass->>CreateVirtualDir: materialize slots (isolated)
  CreateVirtualDir-->>ColdLinkPass: slot populated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • pnpm/pnpm#12245: Modifies the same CreateVirtualStore::run and InstallPackageBySnapshot struct wiring points in the cold-batch path (defer_link vs. tarball_mem_cache).
  • pnpm/pnpm#11678: Adds new InstallPackageBySnapshot struct fields and adjusts cold-batch invocation wiring in the same install pipeline.
  • pnpm/pnpm#12236: Adds new per-batch configuration fields passed into InstallPackageBySnapshot::run at the same code points.

Poem

🐰 A rabbit's tail of deferred thrills,
Cold batches fetch, then link on parallel hills.
Slot writes delayed, yet never forgotten—
Tokio stands guard where links had been trotting! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf(package-manager): parallelize cold-batch slot linking' directly and clearly summarizes the main change: parallelizing slot linking for the cold batch in the package manager, which is the core performance optimization described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-parallelize-cold-link

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.02      8.4±0.42ms   515.2 KB/sec    1.00      8.3±0.19ms   524.0 KB/sec

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Each scenario has pacquet rows (direct install) and pnpr rows (the same client through the pnpr install accelerator), so pnpr@HEAD vs pacquet@HEAD is the pnpr-vs-direct ratio. Cold-store scenarios wipe the client store between runs (warm server); hot-store scenarios keep it warm. The pacquet@HEAD rows feed the pacquet Bencher testbed; the pnpr@HEAD rows feed the pnpr testbed.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 10.212 ± 0.162 10.089 10.569 1.99 ± 0.06
pacquet@main 9.909 ± 0.143 9.844 10.312 1.93 ± 0.06
pnpr@HEAD 5.367 ± 0.155 5.261 5.789 1.05 ± 0.04
pnpr@main 5.127 ± 0.141 5.023 5.507 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 10.212459317440002,
      "stddev": 0.16210709804143753,
      "median": 10.13593329954,
      "user": 3.2735745799999996,
      "system": 3.2884268399999996,
      "min": 10.08858325904,
      "max": 10.56932165204,
      "times": [
        10.37342097604,
        10.34087125404,
        10.15279356304,
        10.11900044504,
        10.08858325904,
        10.09103396604,
        10.17843960504,
        10.09205541804,
        10.11907303604,
        10.56932165204
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 9.90931747134,
      "stddev": 0.1425936664770252,
      "median": 9.86791610454,
      "user": 3.54759868,
      "system": 4.300904539999999,
      "min": 9.84366085404,
      "max": 10.31248548004,
      "times": [
        9.88692714604,
        9.84366085404,
        9.84369774904,
        9.87077879704,
        9.85985611504,
        9.86505341204,
        9.84910738104,
        10.31248548004,
        9.89059695904,
        9.87101082004
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 5.366586183940001,
      "stddev": 0.15476657800863963,
      "median": 5.31164598704,
      "user": 2.4440086799999996,
      "system": 2.96750124,
      "min": 5.26085097104,
      "max": 5.78930338904,
      "times": [
        5.78930338904,
        5.29576767704,
        5.42573014504,
        5.29418664704,
        5.26085097104,
        5.30205793204,
        5.31528652304,
        5.33497270104,
        5.33970040304,
        5.30800545104
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 5.126716651440001,
      "stddev": 0.14086727968297946,
      "median": 5.08508243504,
      "user": 2.7235409799999992,
      "system": 3.9439124399999996,
      "min": 5.02323063504,
      "max": 5.50713962404,
      "times": [
        5.12610444904,
        5.04106670804,
        5.13302090604,
        5.50713962404,
        5.02323063504,
        5.05289407004,
        5.06209018304,
        5.15685198504,
        5.05669326704,
        5.10807468704
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 649.3 ± 11.0 631.9 666.5 1.00
pacquet@main 701.6 ± 33.0 673.6 787.2 1.08 ± 0.05
pnpr@HEAD 797.9 ± 63.1 760.6 964.6 1.23 ± 0.10
pnpr@main 781.6 ± 16.3 760.8 811.6 1.20 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6492787357600001,
      "stddev": 0.01100749143187808,
      "median": 0.6471822676600001,
      "user": 0.37601122,
      "system": 1.2943588799999999,
      "min": 0.63189176266,
      "max": 0.6665447226600001,
      "times": [
        0.6665447226600001,
        0.6544696776600001,
        0.64766798966,
        0.66288860266,
        0.64507346966,
        0.63189176266,
        0.6384632786600001,
        0.6577256676600001,
        0.64669654566,
        0.64136564066
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.70164067616,
      "stddev": 0.0330050551836713,
      "median": 0.69087077916,
      "user": 0.39105581999999994,
      "system": 1.3229494800000001,
      "min": 0.67355131566,
      "max": 0.78715875566,
      "times": [
        0.72544138766,
        0.67355131566,
        0.68766836966,
        0.6847879946600001,
        0.68989504266,
        0.78715875566,
        0.69413707566,
        0.69184651566,
        0.69925473266,
        0.6826655716600001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.7979088221599999,
      "stddev": 0.06305522350005904,
      "median": 0.76793710066,
      "user": 0.39382391999999994,
      "system": 1.3141035799999998,
      "min": 0.7605529896600001,
      "max": 0.96459612366,
      "times": [
        0.80702072666,
        0.7653408686600001,
        0.96459612366,
        0.83267479566,
        0.78430876766,
        0.7674035276600001,
        0.76789384766,
        0.76131622066,
        0.7605529896600001,
        0.7679803536600001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7816471971600001,
      "stddev": 0.016306291826892586,
      "median": 0.77500490716,
      "user": 0.41015262,
      "system": 1.3126817799999997,
      "min": 0.76078944666,
      "max": 0.81164446966,
      "times": [
        0.78205740566,
        0.79204231666,
        0.81164446966,
        0.7733678636600001,
        0.80514638266,
        0.76078944666,
        0.7766419506600001,
        0.77102867566,
        0.7713977416600001,
        0.7723557186600001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 5.355 ± 0.048 5.289 5.437 3.02 ± 0.06
pacquet@main 5.368 ± 0.050 5.327 5.501 3.03 ± 0.06
pnpr@HEAD 1.772 ± 0.028 1.730 1.811 1.00
pnpr@main 2.029 ± 0.029 1.989 2.090 1.14 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 5.35499403886,
      "stddev": 0.04771691585975282,
      "median": 5.361685317659999,
      "user": 3.71947504,
      "system": 3.3281736399999993,
      "min": 5.28880688966,
      "max": 5.43715723466,
      "times": [
        5.36599188766,
        5.33051845766,
        5.40323957366,
        5.38867771566,
        5.357378747659999,
        5.36786015566,
        5.30072383966,
        5.30958588666,
        5.43715723466,
        5.28880688966
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 5.3684357382600005,
      "stddev": 0.049909525486640134,
      "median": 5.36137162216,
      "user": 3.7881255400000002,
      "system": 3.33353514,
      "min": 5.32693433366,
      "max": 5.50098405266,
      "times": [
        5.36585538166,
        5.35931089666,
        5.33290394866,
        5.50098405266,
        5.37734052566,
        5.37593488666,
        5.34754359266,
        5.32693433366,
        5.36343234766,
        5.33411741666
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.7724795680600003,
      "stddev": 0.028164753367486778,
      "median": 1.7811691221600001,
      "user": 2.4031990399999996,
      "system": 2.7506211399999994,
      "min": 1.7298924416600001,
      "max": 1.8108298856600002,
      "times": [
        1.7622365476600002,
        1.79691764666,
        1.7943162006600002,
        1.7500980326600002,
        1.7772334476600002,
        1.7304997316600002,
        1.7876669496600002,
        1.78510479666,
        1.7298924416600001,
        1.8108298856600002
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.0293010547600003,
      "stddev": 0.028902669216662114,
      "median": 2.0282530466599997,
      "user": 2.49159284,
      "system": 3.2863611400000003,
      "min": 1.98934768566,
      "max": 2.09044191366,
      "times": [
        2.00674834366,
        2.03331789166,
        1.9992147856600002,
        2.02318820166,
        2.0518348996599998,
        2.03991600266,
        2.0225736096599998,
        2.0364272136599997,
        2.09044191366,
        1.98934768566
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.415 ± 0.023 1.361 1.432 2.08 ± 0.22
pacquet@main 1.418 ± 0.023 1.386 1.463 2.08 ± 0.22
pnpr@HEAD 0.682 ± 0.070 0.645 0.880 1.00
pnpr@main 0.695 ± 0.057 0.661 0.810 1.02 ± 0.13
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.41480123434,
      "stddev": 0.022782186949906365,
      "median": 1.42414865764,
      "user": 1.5848426799999997,
      "system": 1.7562596600000002,
      "min": 1.3614215491400001,
      "max": 1.43196247514,
      "times": [
        1.4299578831400002,
        1.42227660014,
        1.43196247514,
        1.42370714414,
        1.40856440414,
        1.3614215491400001,
        1.38922278914,
        1.42459017114,
        1.42525408314,
        1.4310552441400002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.41777751304,
      "stddev": 0.022547579981766656,
      "median": 1.42197570014,
      "user": 1.5929027799999997,
      "system": 1.7660905599999999,
      "min": 1.38559587314,
      "max": 1.4628841861400002,
      "times": [
        1.4268732191400002,
        1.42202540414,
        1.4628841861400002,
        1.3973995031400002,
        1.4234702621400002,
        1.4219259961400001,
        1.43618972514,
        1.3977309901400001,
        1.38559587314,
        1.40367997114
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6815159390399999,
      "stddev": 0.07013368996278802,
      "median": 0.66236200614,
      "user": 0.34128188,
      "system": 1.2554596600000003,
      "min": 0.64459466914,
      "max": 0.87976921814,
      "times": [
        0.66889696314,
        0.65113203514,
        0.64459466914,
        0.87976921814,
        0.66099548614,
        0.65200971714,
        0.65882153714,
        0.66372852614,
        0.66966949214,
        0.66554174614
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.69488855614,
      "stddev": 0.056728181033914624,
      "median": 0.66980846864,
      "user": 0.35591158,
      "system": 1.27355596,
      "min": 0.66132400914,
      "max": 0.81021430614,
      "times": [
        0.66132400914,
        0.66970166414,
        0.6659152511399999,
        0.66182243214,
        0.67061199314,
        0.81021430614,
        0.66824998714,
        0.67752592914,
        0.66991527314,
        0.7936047161399999
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Resolution-only: cold packument cache (full re-resolve over the registry link) with a hot store (no tarball download), so this isolates pnpr offloading the client resolution to its warm server.

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 5.007 ± 0.036 4.946 5.075 7.58 ± 0.10
pacquet@main 5.061 ± 0.041 5.011 5.118 7.66 ± 0.11
pnpr@HEAD 0.661 ± 0.007 0.652 0.673 1.00
pnpr@main 0.671 ± 0.013 0.650 0.697 1.02 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 5.00716459532,
      "stddev": 0.03560271028702763,
      "median": 5.00775816462,
      "user": 1.7942784599999997,
      "system": 1.8772191799999998,
      "min": 4.94588573112,
      "max": 5.07472811512,
      "times": [
        4.98167067012,
        4.94588573112,
        4.98737537712,
        5.01213191212,
        5.00974720512,
        5.07472811512,
        5.03183452312,
        4.98506777612,
        5.0057691241199995,
        5.03743551912
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 5.060775184919999,
      "stddev": 0.040828993588567807,
      "median": 5.05267591512,
      "user": 1.87439896,
      "system": 1.90634898,
      "min": 5.010615705119999,
      "max": 5.117722307119999,
      "times": [
        5.010615705119999,
        5.03132799412,
        5.032192215119999,
        5.10200987812,
        5.08271029012,
        5.117722307119999,
        5.109882735119999,
        5.07315961512,
        5.01637072412,
        5.03176038512
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.66092987922,
      "stddev": 0.007385852101453611,
      "median": 0.6599400321200001,
      "user": 0.3349162600000001,
      "system": 1.2490763800000002,
      "min": 0.6516556371200001,
      "max": 0.6730238931200001,
      "times": [
        0.6650987621200001,
        0.6605767771200001,
        0.6709843231200001,
        0.6578063591200001,
        0.6730238931200001,
        0.6640203881200001,
        0.6550232021200001,
        0.6593032871200001,
        0.6518061631200001,
        0.6516556371200001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.67127406102,
      "stddev": 0.012927603776531502,
      "median": 0.6715642311200001,
      "user": 0.34681625999999993,
      "system": 1.2581707800000002,
      "min": 0.6502537261200001,
      "max": 0.69708442112,
      "times": [
        0.66445964812,
        0.6502537261200001,
        0.65618028112,
        0.6722694221200001,
        0.6808609881200001,
        0.67532445612,
        0.6707075651200001,
        0.69708442112,
        0.6747410621200001,
        0.6708590401200001
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12249
Testbedpacquet

🚨 2 Alerts

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
5.35 s
(+90.42%)Baseline: 2.81 s
3.37 s
(158.69%)

isolated-linker.fresh-restore.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
10.21 s
(+75.46%)Baseline: 5.82 s
6.98 s
(146.22%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
5,354.99 ms
(+90.42%)Baseline: 2,812.16 ms
3,374.59 ms
(158.69%)

isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
5,007.16 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,414.80 ms
(+7.19%)Baseline: 1,319.88 ms
1,583.86 ms
(89.33%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
10,212.46 ms
(+75.46%)Baseline: 5,820.30 ms
6,984.36 ms
(146.22%)

isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
649.28 ms
(-5.58%)Baseline: 687.63 ms
825.15 ms
(78.69%)
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan force-pushed the perf-parallelize-cold-link branch from 091feeb to 9e80124 Compare June 6, 2026 21:13
The cold batch in `CreateVirtualStore` ran each per-snapshot
`InstallPackageBySnapshot` as a future inside one cooperative
`try_join_all` task, and each future called the *blocking*
`CreateVirtualDirBySnapshot::run` (a `rayon::join` over CAS import +
symlink layout) directly. Because all the futures share a single task,
a blocking call in one prevents the others from being polled — so the
1308 slot-link operations on a fresh install serialized one-at-a-time,
each paying its own `rayon::join` dispatch. The warm batch already
avoids this by linking every slot in a single `rayon` `par_iter`.

This was invisible on warm/restore installs (everything goes through
the warm batch) but dominated the cold path: a pnpr client install,
whose fast offloaded resolution routes every package through the cold
batch, spent ~14.5s in `CreateVirtualStore` where an equivalent local
fresh install — which pre-fills the store during its slower resolution
and so hits the warm batch — spent ~5-7s linking.

Defer the slot link out of the per-snapshot download future (new
`InstallPackageBySnapshot::defer_link`) and run all the cold links in
one parallel `rayon` pass after the downloads, mirroring the warm
batch. On a fresh ~1300-package install through a pnpr server this cut
`CreateVirtualStore` from ~14.5s to ~9.5s and the end-to-end install
from ~18.5s to ~13s — now faster than the equivalent non-pnpr install
(~15.5s), which it should be since the pnpr client offloads resolution
and downloads less metadata. The progress events are unchanged in kind
and order (`fetched`/`found_in_store` still fire during download,
`imported` still fires once the slot is linked).

---
Written by an agent (Claude Code, claude-opus-4-8).
@zkochan zkochan force-pushed the perf-parallelize-cold-link branch from 9e80124 to 7966e73 Compare June 6, 2026 21:15
@zkochan zkochan marked this pull request as ready for review June 6, 2026 21:16
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Parallelize cold-batch slot linking to improve install performance

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Parallelize cold-batch slot linking in rayon pass after downloads
• Defer slot link from per-snapshot futures to avoid serialization
• Add defer_link flag to InstallPackageBySnapshot struct
• Reduces CreateVirtualStore time by ~34% on fresh installs
Diagram
flowchart LR
  A["Cold batch downloads"] -->|defer_link: true| B["Deferred slot links"]
  B -->|rayon par_iter| C["Parallel link pass"]
  C -->|isolated only| D["Virtual store slots"]
  E["Warm batch"] -->|rayon par_iter| D
  style C fill:#90EE90
  style B fill:#FFB6C1

Loading

Grey Divider

File Changes

1. pacquet/crates/package-manager/src/create_virtual_store.rs ✨ Enhancement +61/-8

Add parallel cold-link pass after downloads

• Modified cold_cas_paths to include SnapshotEntry reference alongside PackageKey and
 HashMap
• Updated ColdOutcome type to capture snapshot entry for deferred linking
• Added new parallel rayon pass after cold downloads to link all slots concurrently
• Implemented runtime detection for multi-threaded tokio to safely use block_in_place
• Updated loop to unpack the new snapshot field when building per-pkg CAS index

pacquet/crates/package-manager/src/create_virtual_store.rs


2. pacquet/crates/package-manager/src/install_package_by_snapshot.rs ✨ Enhancement +12/-1

Add defer_link flag to defer slot materialization

• Added defer_link: bool field to InstallPackageBySnapshot struct
• Updated documentation explaining deferred linking prevents serialization in cooperative task
• Modified slot materialization condition to skip when defer_link is true
• Preserves behavior for hoisted linker which never writes virtual-store slots

pacquet/crates/package-manager/src/install_package_by_snapshot.rs


3. pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs 🧪 Tests +3/-0

Update tests with defer_link field

• Added defer_link: false to three test cases exercising cold batch
• Tests use hoisted linker which skips slot materialization regardless
• Ensures test compatibility with new struct field

pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs


Grey Divider

Qodo Logo

@zkochan zkochan merged commit 51839cd into main Jun 6, 2026
27 of 28 checks passed
@zkochan zkochan deleted the perf-parallelize-cold-link branch June 6, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant