From 84ecba13cab66bfd878485f17eace4537728f2ac Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 11 Jun 2026 18:21:51 +0800 Subject: [PATCH 1/3] perf(pm): force HTTP/1.1 for tarball downloads The tarball downloader client had no http1_only(): ALPN negotiated h2 and the registry CDN multiplexed every in-flight tarball over one TCP connection (single congestion window + flow-control cap, loss on one stream stalls siblings). The manifest client already forces h1 for the same pcap-backed reason (#2874) - the downloader never got the fix. Same-runner ablation (same binary, UTOO_TARBALL_HTTP=h2 vs default) on ant-design p3 cold install: h2 is +9.8% slower on npmjs (sigma 0.01) and +35.4% slower on npmmirror. p1/p4 (no tarballs) show +-5% noise. Also .no_gzip() on the tarball client (tarballs are already gzip; the crate-level gzip feature was inviting a second Content-Encoding layer), and declare the gzip feature explicitly on ruborist's native reqwest deps - it was already active in the utoo binary via feature unification from utoo-pm, so this is documentation, not behavior. Co-Authored-By: Claude Fable 5 --- crates/pm/src/util/retry.rs | 16 +++++++++++++++- crates/ruborist/Cargo.toml | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/pm/src/util/retry.rs b/crates/pm/src/util/retry.rs index d47378cf7e..6b5263e359 100644 --- a/crates/pm/src/util/retry.rs +++ b/crates/pm/src/util/retry.rs @@ -25,9 +25,23 @@ impl std::error::Error for RetryableError {} /// Build a reqwest::Client with timeout config. /// Connection pool is unlimited - concurrency is controlled by semaphore instead. pub fn build_dns_cached_client() -> reqwest::Client { - client_builder() + let mut builder = client_builder() .connect_timeout(std::time::Duration::from_secs(5)) // TLS + TCP handshake .read_timeout(std::time::Duration::from_secs(30)) // Timeout for individual read operations + // Tarballs are already gzip; don't invite a second Content-Encoding + // layer that would just burn CPU on decode. + .no_gzip(); + // HTTP/1.1 by default so each in-flight tarball gets its own TCP + // connection. With ALPN h2 the registry CDN multiplexes every stream + // over one connection (single congestion window + flow-control cap, + // loss on one stream stalls siblings) — same rationale as the manifest + // client in ruborist/src/service/http.rs. `UTOO_TARBALL_HTTP=h2` keeps + // ALPN negotiation so CI can A/B the protocols with one binary. + let h2 = std::env::var("UTOO_TARBALL_HTTP").is_ok_and(|v| v.eq_ignore_ascii_case("h2")); + if !h2 { + builder = builder.http1_only(); + } + builder // No total timeout - large files (e.g. node binary ~100MB) need longer download time // No pool_max_idle_per_host - let reqwest manage connections freely // Concurrency is bounded by the resolver's in-flight fetch cap diff --git a/crates/ruborist/Cargo.toml b/crates/ruborist/Cargo.toml index 4b23ba32c2..cee6b29dd1 100644 --- a/crates/ruborist/Cargo.toml +++ b/crates/ruborist/Cargo.toml @@ -53,8 +53,11 @@ workspace = true tokio = { workspace = true, features = ["macros", "rt"] } # Native (non-macOS) targets: reqwest's default rustls + ring. +# `gzip` lets the manifest client send Accept-Encoding and transparently +# decompress packuments (~2.5x smaller on registry.npmjs.org); brotli is +# only ~1% smaller than gzip there but decodes slower, so it stays off. [target.'cfg(not(any(target_arch = "wasm32", target_os = "macos")))'.dependencies] -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots"] } +reqwest = { version = "0.12", default-features = false, features = ["gzip", "rustls-tls-native-roots"] } # Native-only dependencies (not compiled for WASM) [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -73,7 +76,10 @@ js-sys = "0.3" # resolves on M-series ARM. CI bench-phases regresses on Linux/x86_64 # with this provider, so it's gated to macOS only. See service/http.rs. [target.'cfg(target_os = "macos")'.dependencies] -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots-no-provider"] } +reqwest = { version = "0.12", default-features = false, features = [ + "gzip", + "rustls-tls-native-roots-no-provider" +] } rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "logging", "std", "tls12"] } rustls-native-certs = "0.8" From e480ab1516298b6ee0d19f4950b2c066ba654284 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 11 Jun 2026 20:31:01 +0800 Subject: [PATCH 2/3] chore: re-trigger benchmark on the interleaved paired bench pipeline From efcd7597bfc0b860cbdc0731a9ffec08d8a2831d Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 11 Jun 2026 22:44:31 +0800 Subject: [PATCH 3/3] drop the UTOO_TARBALL_HTTP escape hatch - h1 is settled The same-runner A/B already settled the question (h2 +9.8% on p3 against npmjs, +35% against npmmirror), so the runtime toggle carries no remaining value - hard-force http1_only and keep the measured numbers in the comment. Co-Authored-By: Claude Fable 5 --- crates/pm/src/util/retry.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/pm/src/util/retry.rs b/crates/pm/src/util/retry.rs index 6b5263e359..870d9d7266 100644 --- a/crates/pm/src/util/retry.rs +++ b/crates/pm/src/util/retry.rs @@ -25,23 +25,20 @@ impl std::error::Error for RetryableError {} /// Build a reqwest::Client with timeout config. /// Connection pool is unlimited - concurrency is controlled by semaphore instead. pub fn build_dns_cached_client() -> reqwest::Client { - let mut builder = client_builder() + client_builder() .connect_timeout(std::time::Duration::from_secs(5)) // TLS + TCP handshake .read_timeout(std::time::Duration::from_secs(30)) // Timeout for individual read operations // Tarballs are already gzip; don't invite a second Content-Encoding // layer that would just burn CPU on decode. - .no_gzip(); - // HTTP/1.1 by default so each in-flight tarball gets its own TCP - // connection. With ALPN h2 the registry CDN multiplexes every stream - // over one connection (single congestion window + flow-control cap, - // loss on one stream stalls siblings) — same rationale as the manifest - // client in ruborist/src/service/http.rs. `UTOO_TARBALL_HTTP=h2` keeps - // ALPN negotiation so CI can A/B the protocols with one binary. - let h2 = std::env::var("UTOO_TARBALL_HTTP").is_ok_and(|v| v.eq_ignore_ascii_case("h2")); - if !h2 { - builder = builder.http1_only(); - } - builder + .no_gzip() + // HTTP/1.1 so each in-flight tarball gets its own TCP connection. + // With ALPN h2 the registry CDN multiplexes every stream over one + // connection (single congestion window + flow-control cap, loss on + // one stream stalls siblings) — same rationale as the manifest + // client in ruborist/src/service/http.rs. Same-runner A/B measured + // h2 at +9.8% on p3 cold install against npmjs and +35% against + // npmmirror. + .http1_only() // No total timeout - large files (e.g. node binary ~100MB) need longer download time // No pool_max_idle_per_host - let reqwest manage connections freely // Concurrency is bounded by the resolver's in-flight fetch cap