fix(browser): raise protocolTimeout to 10min for heavy pages#2010
fix(browser): raise protocolTimeout to 10min for heavy pages#2010triuzzi wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
@googlebot I signed it! |
a52a18a to
38a09eb
Compare
Heavy pages (e.g. dev bundles >100MB) cannot ack `Network.enable` and other auto-attached CDP domain calls within puppeteer's default 180s. Once the timeout fires, puppeteer marks the connection dead and every subsequent call throws `Network.enable timed out` — only daemon restart recovers. Set `protocolTimeout: 600000` on both `puppeteer.connect()` and `puppeteer.launch()`. Env-overridable via `CHROME_DEVTOOLS_PROTOCOL_TIMEOUT_MS` for power users. Hit in real workloads against a Brightcove Studio dev bundle (~160MB JS) where the default timeout fired before `list_pages` could complete, leaving the daemon permanently wedged.
38a09eb to
5348bf7
Compare
|
Ciao! The original issue arose with a private frontend project, a ~70 MB Studio dev bundle I sadly cannot share, but the cause is engine- and site-agnostic, so here's a minimal deterministic repro. Root cause. On Test page — pegs the renderer main thread for 200s (> the 180s default): <!doctype html><meta charset=utf-8><title>main-thread blocker</title>
<h1>pegging the renderer main thread for 200s</h1>
<script>const end=Date.now()+200000;while(Date.now()<end){}</script>Faithful repro via chrome-devtools-mcp (~180s): launch Chrome with Mechanism proof (~3s) — identical connect → auto-attach → import puppeteer from 'puppeteer-core';
const launchTarget = process.env.BROWSER_PATH
? {executablePath: process.env.BROWSER_PATH}
: {channel: 'chrome'};
const BLOCK_MS = parseInt(process.env.BLOCK_MS ?? '30000', 10);
const CLIENT_PROTOCOL_TIMEOUT_MS = parseInt(
process.env.CLIENT_PROTOCOL_TIMEOUT_MS ?? '3000',
10,
);
const blockingHtml = `<!doctype html><meta charset="utf-8">
<title>main-thread blocker</title>
<script>
// Stand-in for "parse/compile a 160MB JS bundle": occupy the main
// thread synchronously so the renderer cannot service CDP messages.
const end = Date.now() + ${BLOCK_MS};
while (Date.now() < end) {}
</script>`;
const blockingUrl = 'data:text/html,' + encodeURIComponent(blockingHtml);
const fmt = e => (e?.message ?? String(e)).split('\n')[0];
const server = await puppeteer.launch({
...launchTarget,
headless: true,
args: ['--no-first-run', '--no-default-browser-check'],
});
try {
const page = await server.newPage();
page.goto(blockingUrl).catch(() => {}); // hangs — main thread is pegged
await new Promise(r => setTimeout(r, 1500));
const t0 = Date.now();
try {
// What chrome-devtools-mcp does on --browserUrl / list_pages:
const client = await puppeteer.connect({
browserWSEndpoint: server.wsEndpoint(),
protocolTimeout: CLIENT_PROTOCOL_TIMEOUT_MS,
});
for (const p of await client.pages()) await p.title();
console.log('no timeout — list_pages succeeded');
} catch (e) {
console.log(`REPRODUCED after ${Date.now() - t0}ms: ${fmt(e)}`);
}
} finally {
await server.close().catch(() => server.process()?.kill('SIGKILL'));
}Verified on Chrome 149 and Brave, puppeteer-core 25.1.0: The second line is exactly what this PR buys: once Thank you in advance! |
|
Thanks! If main thread is blocked indefinitely, increasing time out would not help. So do you have an actual website that takes 180 seconds to load blocking the main thread for that long? Is it a public app we can test with? How do users use it if it takes 3 minutes to load? |
Summary
Heavy pages (e.g. dev bundles >100MB) cannot ack
Network.enableand other auto-attached CDP domain calls within puppeteer's default 180s. Once the timeout fires, puppeteer marks the connection dead and every subsequent call throwsNetwork.enable timed out— only daemon restart recovers.Set
protocolTimeout: 600000on bothpuppeteer.connect()andpuppeteer.launch(). Env-overridable viaCHROME_DEVTOOLS_PROTOCOL_TIMEOUT_MSfor power users.Repro
Hit during real-world testing against a Brightcove Studio dev bundle (~160MB JS):
After the failure, every subsequent call returned the same timeout. The daemon process was alive (
process.kill(pid, 0)returned true), so client retries reused the wedged connection until manually killed.Why 10min
Puppeteer's 180s default is calibrated for small pages and CI runners. For real-world dev work (HMR-enabled webpack bundles, large React apps), 10min is comfortable headroom while still bounded enough to surface genuine deadlocks. Env-overridable so power users with even-larger workloads aren't blocked.
Testing
npm run buildcleanNetwork.enable timed outno longer surfacesNote
A previous PR (#2009) was opened from a downstream fork and accidentally pulled in 9 fork-specific commits — closed and re-submitted as this clean single-commit PR branched from
main.🤖 Generated with Claude Code