ITADN

[Detail Bug] Browser respawn failures cause unhandled promise rejections and potential Node.js crashes

#696Opendetail-app[bot] 创建于 2026-03-02
D
detail-app[bot]commented
# Detail Bug Report https://app.detail.dev/org_06887db3-bf54-40ab-976d-46c66ab2b840/bugs/bug_8449ffbe-5be7-4e01-a37e-2f83f9b76fba # Summary - **Context**: The `getBrowser()` function in `packages/browserless/src/index.js` detects disconnected browsers and triggers a respawn to restore connectivity. - **Bug**: When `getBrowser()` calls `respawn()` at line 68, it doesn't await the returned promise, causing unhandled promise rejections if the respawn fails. - **Actual vs. expected**: If `driver.spawn()` fails during respawn, the `Promise.all` in `respawn()` rejects without a handler, creating an unhandled rejection. Expected behavior is that all promise rejections should be caught and handled. - **Impact**: In strict Node.js environments (`--unhandled-rejections=strict`), this causes application crashes. In normal environments, it produces spurious error logs that make debugging difficult. # Code with Bug `packages/browserless/src/index.js`: ```javascript const getBrowser = async () => { if (isClosed) return browserProcessPromise const browserProcess = await lock(async () => { const browserProcess = await browserProcessPromise if (browserProcess.isConnected()) return browserProcess respawn() // <-- BUG 🔴 Promise returned by respawn() is not awaited/caught, so failures become unhandled rejections }) return browserProcess || getBrowser() } ``` ```javascript const respawn = () => !isClosed && Promise.all([ browserProcessPromise.then(driver.close), (browserProcessPromise = spawn({ respawn: true })) ]) ``` Additional unhandled-rejection path (event handler ignores returned promise): ```javascript promise.then(async browser => { if (launchOpts.mode !== 'connect') browser.once('disconnected', getBrowser) // <-- BUG 🔴 returned promise is not handled // ... }) ``` # Explanation - `respawn()` returns a rejecting promise when `spawn({respawn: true})` fails. - `getBrowser()` calls `respawn()` without `await`/`.catch()`, so that rejection is not observed by any handler, producing `unhandledRejection` events (and process crashes under `--unhandled-rejections=strict`). - The issue can trigger multiple times because `respawn()` may be invoked both from explicit `getBrowser()` calls and via the browser `disconnected` event listener, where `getBrowser` is used as a fire-and-forget handler. # Recommended Fix - In `getBrowser()`, `await respawn()` (or explicitly attach `.catch()` if it must be fire-and-forget). - Wrap the `disconnected` handler so the promise returned by `getBrowser()` is caught/logged (instead of passing `getBrowser` directly). # History This bug was introduced in commit f6b34ac7a. The commit refactored the locking mechanism from `mutexify` to `superlock`, and during this refactor, the `await` keyword was accidentally removed from the `respawn()` call inside `getBrowser()`.
0 条评论