* perf(cdp): TCP_NODELAY + zero-buffer WS writes shave 130ms off newPage
CDP traffic during browser.newPage() and Page.navigate is many small
(~100-byte) request/response frames. Two pieces of buffering were
adding ~10-15ms latency per WS frame on localhost, which on the
~10 round-trips Puppeteer makes during newPage compounded to a
~130ms penalty.
1. TCP_NODELAY on every accepted WS stream. With Nagle on, each small
write either waits for an ACK from the previous one or hits the
40ms delayed-ACK timer, even on localhost.
2. WebSocketConfig.write_buffer_size = 0 on accept_async. tokio-
tungstenite's default 128 KiB write BufWriter copies every frame
into the buffer before flushing; setting it to 0 makes every
small CDP reply hit the socket directly.
Measured impact on localhost (puppeteer-core 24, lower is better):
operation before after speedup
browser.newPage() ~190ms ~50ms ~4x
goto(data: URL) ~60ms ~22ms ~3x
goto(repeat) ~60ms ~22ms ~3x
Shared-page CDP scrape bench (9 scenarios): ~4000ms -> ~2900ms.
28/28 integration tests pass.
* perf(cdp): bypass v8_lock for read-only / setup CDP methods
Every CDP request unconditionally acquired the process-wide V8 lock,
which serializes all CDP traffic behind any in-flight V8 work on a
sibling page. During Puppeteer's browser.newPage() the ~8 setup
calls (Page.enable, Runtime.enable, Page.getFrameTree,
Page.addScriptToEvaluateOnNewDocument, Page.createIsolatedWorld, etc.)
all queued behind a concurrent nav, turning a sub-millisecond setup
into a multi-hundred-millisecond wait.
is_v8_free_method now allowlists methods that demonstrably never
touch a JsRuntime, either they don't touch any Page at all, or they
use only the immutable get_session_page accessor and Rust-side field
reads. get_session_page_mut (which can trigger suspend_js/resume_js
on other pages) stays behind the lock, as does Target.createTarget
(which calls page.navigate).
Allowlisted: most of Target/Browser/Storage, plus Page.enable,
Page.getFrameTree, Page.addScriptToEvaluateOnNewDocument,
Page.createIsolatedWorld, Runtime.enable, Network.* cookie ops,
Fetch.continue/fulfill/fail.
Measured on the puppeteer-core 24 CDP scrape bench:
browser.newPage() warm: ~50ms -> 13-32ms
goto() repeat: ~22ms -> 14-48ms (variance)
28/28 integration tests pass. 18/18 realistic scrape scenarios pass.