Repository navigation
Conversation
Copy the OnceMap util module from pm/util to ruborist/util so the resolver can use it for per-name dedup of concurrent manifest fetches. Foundation commit — no usage yet, that lands in the worker-pool wiring commit. The pm-side oncemap.rs stays for cloner/downloader users.
CI pcap analysis against npmjs.org revealed TLS CPU as a major
preload bottleneck. Per-handshake timing on CI:
utoo (ring) bun (BoringSSL)
CH → SH (1 RTT) 11 ms 10 ms (network)
CCS → first AppData 78 ms p50 12 ms p50
154 ms max 17 ms max
TOTAL CH → AppData 162 ms 46 ms
The "CCS → AppData" phase is dominated by post-ChangeCipherSpec
client work (Finished MAC verify, state machine transition,
request dispatch). Observed at CI pcap capture:
utoo: Client CCS spread 154ms (first conn done at 0.975s, last
at 1.129s), then first AppData *all* fire within 11ms at
~1.13s — classic CPU-saturation pattern where 128 parallel
TLS handshakes serialise across 4 blocking threads and HTTP
dispatch is starved until crypto drains.
bun: CCS spread 51ms, AppData spread 43ms — dispatch flows
smoothly as each conn completes.
`ring` (rustls' default provider) is pure Rust + hand-tuned
assembly; `aws-lc-rs` wraps BoringSSL's primitives which are more
aggressively optimised for x86_64 AES-NI + SHA-NI.
Reqwest 0.12's `rustls-tls-native-roots` feature pins `__rustls-ring`
via Cargo's feature unification — no way to override. Swap to
`rustls-tls-native-roots-no-provider`, add a direct `rustls` dep
with the `aws-lc-rs` feature, load native root certs via
`rustls-native-certs` and pass the `ClientConfig` into reqwest via
`use_preconfigured_tls`.
Local M2 (ARM, where ring's hand-tuned ARM assembly is already
near-optimal) shows neutral perf (~2.9s both providers). Waiting
on CI to confirm the x86_64 CCS→AppData gap closes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the OnceMap from #2832-foundation into UnifiedRegistry's resolve_full_manifest. When N packages all transitively pull on the same dep (e.g. 50 packages → react), only the first hits the network; the others await the shared Arc<result> on Notify and clone it on resolve. DashMap + tokio::sync::Notify keeps the fast path (cache hit) lock-free. cfg-gated on native; wasm path keeps the direct fetch since dashmap + tokio sync are unnecessary there. FullManifestResult derives Clone so OnceMap waiters can each take owned copies.
N long-lived `tokio::spawn` workers pull work from a shared lock-free `SegQueue`. Each worker is independent on tokio's multi-thread runtime, so when one worker is parsing manifest JSON (CPU-bound, simd_json), other workers continue driving their network IO and other parses run on different cores. Replaces `FuturesUnordered` where the main task owned all preload futures and polled them cooperatively — every per- future await continuation (including parse) ran serialised in that single task. The architectural fix alone (proven nop in PR #2830 with spawn_local) needs the network-layer drivers (aws-lc-rs + OnceMap, earlier in this stack) to actually express its benefit. With them: TLS handshake CPU is fast (aws-lc-rs), duplicate fetches are deduped (OnceMap), so worker-pool's N independent task slots are doing actual unique work. ## MaybeSend / MaybeSync shim `tokio::spawn` requires `Future: Send + 'static`, which forces `RegistryClient` trait surface to grow `+ Send` on its `impl Future` returns + `Self: Sync` / `Self::Error: Send` on default impls. Native uses real Send/Sync; wasm32 uses no-op marker traits because `JsFuture` is `!Send` and tasks run via `wasm_bindgen_futures::spawn_local`. `PreloadRegistry` trait alias bundles `RegistryClient + Clone + MaybeSend + MaybeSync + 'static` so the 5 resolver entry points (`build_deps*`, `resolve*`, `run_preload_phase`) read clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aws-lc-sys cc_builder.rs:692 panics on wasm32 target (no support). Move the explicit rustls + aws-lc-rs dependency to native-only; reqwest's rustls-tls-native-roots-no-provider feature on wasm is a no-op since wasm uses the fetch API and the browser's TLS stack, not rustls.
There was a problem hiding this comment.
Code Review
This pull request refactors the manifest preloading system from a cooperative FuturesUnordered model to a multi-threaded worker pool using a lock-free SegQueue and DashSet. It introduces a MaybeSend/MaybeSync shim for cross-platform compatibility between native multi-threaded runtimes and WASM, along with a OnceMap utility to deduplicate concurrent network fetches. The review feedback identifies critical race conditions in the worker termination logic regarding the order of counter increments and queue pushes, an inefficiency in calculating final statistics, and the use of unstable Rust 'let-chain' syntax in the new utility module.
| pending.push(dep); | ||
| dispatched.fetch_add(1, Ordering::Relaxed); |
There was a problem hiding this comment.
The dispatched counter should be incremented before pushing the item to the pending queue. This ensures that the item is accounted for in the global state before it becomes visible to workers, preventing a race condition where the termination check (done == dispatched) might succeed prematurely if a worker pops and finishes the item before the counter is updated.
| pending.push(dep); | |
| dispatched.fetch_add(1, Ordering::Relaxed); | |
| dispatched.fetch_add(1, Ordering::Relaxed); | |
| pending.push(dep); |
| if processed.insert(key) { | ||
| pending.push(tdep); | ||
| count += 1; | ||
| } | ||
| } | ||
| if count > 0 { | ||
| dispatched.fetch_add(count, Ordering::Release); |
There was a problem hiding this comment.
Similar to the initial seeding, transitive dependencies should be added to the dispatched counter before being pushed to the pending queue to avoid a race condition in the termination logic. Incrementing the counter inside the loop ensures that each item is tracked as soon as it's queued.
| if processed.insert(key) { | |
| pending.push(tdep); | |
| count += 1; | |
| } | |
| } | |
| if count > 0 { | |
| dispatched.fetch_add(count, Ordering::Release); | |
| if processed.insert(key) { | |
| dispatched.fetch_add(1, Ordering::Release); | |
| pending.push(tdep); | |
| count += 1; | |
| } | |
| } | |
| if count > 0 { |
| stats.total_processed = { | ||
| let mut set: HashSet<String> = HashSet::with_capacity(processed.len()); | ||
| for entry in processed.iter() { | ||
| set.insert(entry.key().clone()); | ||
| } | ||
| set.len() | ||
| }; |
There was a problem hiding this comment.
Manually cloning the DashSet into a HashSet just to get its length is inefficient in both time and memory ($O(N)$). Since all workers have finished at this point, processed.len() provides the final count directly in
stats.total_processed = processed.len();| if let Some(entry) = self.map.get(key) | ||
| && matches!(entry.value(), Value::Done(_)) | ||
| { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The use of "let-chains" (e.g., if let ... && ...) is an unstable Rust feature. Unless the project is strictly nightly-only and has the #![feature(let_chains)] attribute enabled, this will cause a compilation error. For better compatibility and to adhere to stable Rust, consider using nested if statements. This pattern occurs in several places in this file.
| if let Some(entry) = self.map.get(key) | |
| && matches!(entry.value(), Value::Done(_)) | |
| { | |
| return; | |
| } | |
| if let Some(entry) = self.map.get(key) { | |
| if matches!(entry.value(), Value::Done(_)) { | |
| return; | |
| } | |
| } |
📊 pm-bench-phases ·
|
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 9.50s | 0.06s | 9.64s | 9.51s | 584M | 284.9K |
| utoo-npm | 10.42s | 0.29s | 11.17s | 12.91s | 1.17G | 156.7K |
| utoo | 12.46s | 0.27s | 11.77s | 13.18s | 1.19G | 153.1K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 21.0K | 15.0K | 1.16G | 6M | 1.83G | 1.72G | 1M |
| utoo-npm | 188.0K | 163.1K | 1.14G | 5M | 1.68G | 1.68G | 2M |
| utoo | 193.3K | 134.1K | 1.13G | 5M | 1.68G | 1.68G | 2M |
p1_resolve
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 3.27s | 0.25s | 3.48s | 1.07s | 474M | 172.8K |
| utoo-npm | 7.31s | 1.85s | 5.10s | 1.79s | 430M | 79.1K |
| utoo | 7.48s | 2.30s | 5.90s | 2.08s | 571M | 71.1K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 15.8K | 2.1K | 201M | 3M | 104M | - | 1M |
| utoo-npm | 71.5K | 2.1K | 212M | 2M | 9M | 5M | 2M |
| utoo | 90.1K | 7.7K | 197M | 2M | 9M | 5M | 2M |
p3_cold_install
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 7.36s | 1.47s | 6.20s | 9.31s | 526M | 184.3K |
| utoo-npm | 9.96s | 0.53s | 5.59s | 11.25s | 764M | 96.2K |
| utoo | 10.67s | 3.34s | 5.53s | 11.50s | 731M | 105.5K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 9.0K | 7.4K | 993M | 4M | 1.73G | 1.73G | 1M |
| utoo-npm | 151.1K | 100.2K | 964M | 4M | 1.67G | 1.67G | 2M |
| utoo | 162.6K | 115.8K | 964M | 4M | 1.67G | 1.67G | 2M |
p4_warm_link
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 3.37s | 0.20s | 0.20s | 2.43s | 136M | 31.5K |
| utoo-npm | 2.33s | 0.08s | 0.54s | 3.74s | 84M | 19.7K |
| utoo | 2.29s | 0.02s | 0.52s | 3.81s | 84M | 19.6K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 197 | 26 | 7M | 28K | 1.88G | 1.72G | 1M |
| utoo-npm | 43.7K | 18.7K | 425K | 19K | 1.67G | 1.67G | 2M |
| utoo | 47.1K | 21.4K | 422K | 12K | 1.68G | 1.67G | 2M |
npmmirror.com
p0_full_cold
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 49.95s | 42.13s | 9.02s | 9.42s | 574M | 377.6K |
| utoo-npm | 27.06s | 7.40s | 7.85s | 13.28s | 700M | 93.3K |
| utoo | 24.41s | 2.57s | 8.04s | 13.25s | 724M | 108.6K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 58.6K | 5.2K | 1.12G | 10M | 1.84G | 1.72G | 2M |
| utoo-npm | 255.0K | 93.8K | 978M | 8M | 1.67G | 1.68G | 2M |
| utoo | 251.3K | 107.9K | 990M | 8M | 1.67G | 1.67G | 2M |
p1_resolve
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 7.39s | 8.19s | 3.66s | 1.17s | 554M | 220.0K |
| utoo-npm | 4.38s | 0.41s | 1.53s | 0.79s | 75M | 15.8K |
| utoo | 4.50s | 0.66s | 1.61s | 0.76s | 77M | 16.3K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 11.1K | 4.2K | 152M | 3M | 106M | - | 2M |
| utoo-npm | 48.3K | 666 | 14M | 2M | - | 4M | 2M |
| utoo | 45.6K | 1.2K | 13M | 2M | - | 4M | 2M |
p3_cold_install
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 21.25s | 2.26s | 5.76s | 8.42s | 240M | 100.4K |
| utoo-npm | 42.69s | 20.32s | 6.14s | 12.04s | 595M | 89.2K |
| utoo | 53.94s | 26.15s | 6.53s | 12.67s | 639M | 91.7K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 36.2K | 3.0K | 985M | 7M | 1.70G | 1.70G | 2M |
| utoo-npm | 201.7K | 94.5K | 965M | 6M | 1.67G | 1.67G | 2M |
| utoo | 203.3K | 88.6K | 965M | 6M | 1.67G | 1.67G | 2M |
p4_warm_link
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 5.26s | 3.53s | 0.18s | 2.37s | 135M | 31.0K |
| utoo-npm | 2.16s | 0.07s | 0.55s | 3.74s | 85M | 19.2K |
| utoo | 2.20s | 0.05s | 0.61s | 3.86s | 84M | 19.4K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 261 | 28 | 3M | 20K | 1.82G | 1.72G | 2M |
| utoo-npm | 47.5K | 20.9K | 464K | 39K | 1.67G | 1.67G | 2M |
| utoo | 46.7K | 20.5K | 472K | 23K | 1.67G | 1.67G | 2M |
📊 pm-bench-phases ·
|
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 26.57s | 1.97s | 9.57s | 28.06s | 804M | 52.2K |
| utoo-npm | 33.75s | 1.00s | 16.76s | 38.07s | 875M | 96.5K |
| utoo | 24.64s | 4.55s | 13.14s | 27.65s | 1.05G | 106.7K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 16.0K | 159.3K | - | - | 1.79G | 1.90G | 1M |
| utoo-npm | 13.4K | 427.9K | - | - | 1.63G | 1.82G | 2M |
| utoo | 9.3K | 348.8K | - | - | 1.63G | 1.88G | 2M |
p1_resolve
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 3.56s | 0.41s | 4.63s | 2.29s | 494M | 32.2K |
| utoo-npm | 8.86s | 0.75s | 7.57s | 4.22s | 544M | 36.8K |
| utoo | 5.84s | 0.57s | 6.96s | 3.33s | 594M | 39.7K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 66 | 25.4K | - | - | 110M | - | 1M |
| utoo-npm | 11 | 75.6K | - | - | 28M | 5M | 2M |
| utoo | 35 | 73.4K | - | - | 28M | 5M | 2M |
p3_cold_install
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 27.38s | 7.75s | 4.67s | 25.92s | 517M | 33.7K |
| utoo-npm | 28.75s | 4.67s | 7.58s | 35.17s | 748M | 82.9K |
| utoo | 23.51s | 3.79s | 6.93s | 33.15s | 792M | 80.0K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 6.0K | 141.4K | - | - | 1.65G | 1.91G | 1M |
| utoo-npm | 1.5K | 270.5K | - | - | 1.61G | 1.86G | 2M |
| utoo | 1.3K | 265.6K | - | - | 1.61G | 1.86G | 2M |
p4_warm_link
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 4.68s | 0.33s | 0.07s | 2.09s | 44M | 3.4K |
| utoo-npm | 4.39s | 0.51s | 0.60s | 3.21s | 86M | 6.3K |
| utoo | 8.21s | 1.23s | 0.66s | 3.87s | 94M | 7.1K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 15.5K | 484 | - | - | 1.75G | 1.91G | 1M |
| utoo-npm | 13.0K | 76.3K | - | - | 1.61G | 1.84G | 2M |
| utoo | 13.6K | 77.9K | - | - | 1.63G | 1.84G | 2M |
npmmirror.com
p0_full_cold
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 62.20s | 30.08s | 6.21s | 16.65s | 535M | 34.8K |
| utoo-npm | 47.69s | 15.37s | 6.55s | 17.88s | 745M | 79.0K |
| utoo | 39.13s | 6.20s | 6.32s | 17.28s | 661M | 72.0K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 13.8K | 171.2K | - | - | 1.79G | 1.90G | 2M |
| utoo-npm | 834 | 476.5K | - | - | 1.61G | 1.86G | 2M |
| utoo | 4.2K | 448.0K | - | - | 1.61G | 1.87G | 2M |
p1_resolve
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 27.52s | 40.55s | 2.88s | 1.75s | 478M | 31.2K |
| utoo-npm | 7.61s | 0.20s | 2.29s | 1.41s | 79M | 5.7K |
| utoo | 16.42s | 14.74s | 2.24s | 1.32s | 78M | 5.7K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 11 | 32.2K | - | - | 111M | - | 2M |
| utoo-npm | 4 | 49.8K | - | - | - | 4M | 2M |
| utoo | 29 | 49.7K | - | - | - | 4M | 2M |
p3_cold_install
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 23.69s | 0.32s | 3.75s | 16.27s | 250M | 16.5K |
| utoo-npm | 57.37s | 32.33s | 5.66s | 18.83s | 717M | 74.2K |
| utoo | 43.67s | 5.26s | 5.29s | 17.83s | 737M | 78.3K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 1.6K | 165.6K | - | - | 1.65G | 1.92G | 2M |
| utoo-npm | 1.5K | 382.9K | - | - | 1.61G | 1.84G | 2M |
| utoo | 1.3K | 379.8K | - | - | 1.61G | 1.84G | 2M |
p4_warm_link
| PM | wall | ±σ | user | sys | RSS | pgMinor |
|---|---|---|---|---|---|---|
| bun | 3.71s | 0.58s | 0.10s | 2.00s | 44M | 3.4K |
| utoo-npm | 3.24s | 0.27s | 0.56s | 2.82s | 88M | 6.5K |
| utoo | 3.18s | 0.44s | 0.50s | 2.83s | 95M | 7.2K |
| PM | vCtx | iCtx | netRX | netTX | cache | node_mod | lock |
|---|---|---|---|---|---|---|---|
| bun | 13.1K | 943 | - | - | 1.78G | 1.90G | 2M |
| utoo-npm | 11.9K | 76.1K | - | - | 1.61G | 1.83G | 2M |
| utoo | 12.2K | 75.4K | - | - | 1.61G | 1.83G | 2M |
Strip aws-lc-rs from #2838's bundle to isolate which of the 3 drivers contribute. Linear stack: baseline → +WP → +WP+OnceMap → +aws-lc-rs.