Skip to content

perf(pm): probe — WP + OnceMap (no aws-lc-rs) - #2840

Closed
elrrrrrrr wants to merge 9 commits into
nextfrom
perf/strip-tls
Closed

elrrrrrrr wants to merge 9 commits into
nextfrom
perf/strip-tls

Conversation

@elrrrrrrr

Copy link
Copy Markdown
Contributor

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.

elrrrrrrr and others added 8 commits April 27, 2026 21:04
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.
@elrrrrrrr elrrrrrrr added benchmark Run pm-bench on PR bench-phases Run pm-bench-phases workflow labels Apr 27, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +143 to +144
pending.push(dep);
dispatched.fetch_add(1, Ordering::Relaxed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
pending.push(dep);
dispatched.fetch_add(1, Ordering::Relaxed);
dispatched.fetch_add(1, Ordering::Relaxed);
pending.push(dep);

Comment on lines +190 to +196
if processed.insert(key) {
pending.push(tdep);
count += 1;
}
}
if count > 0 {
dispatched.fetch_add(count, Ordering::Release);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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 {

Comment on lines +301 to +307
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()
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 $O(1)$ or $O(S)$ time without any allocations.

    stats.total_processed = processed.len();

Comment on lines +128 to +132
if let Some(entry) = self.map.get(key)
&& matches!(entry.value(), Value::Done(_))
{
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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;
}
}

@github-actions

Copy link
Copy Markdown

📊 pm-bench-phases · 94565c1 · linux (ubuntu-latest)

Workflow run — ant-design

PMs: utoo (this branch) · utoo-npm (latest published) · bun (latest)

npmjs.org

p0_full_cold

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

@github-actions

Copy link
Copy Markdown

📊 pm-bench-phases · 94565c1 · mac (macos-latest)

Workflow run — ant-design

PMs: utoo (this branch) · utoo-npm (latest published) · bun (latest)

npmjs.org

p0_full_cold

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

@elrrrrrrr elrrrrrrr closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bench-phases Run pm-bench-phases workflow benchmark Run pm-bench on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant