fix: remove deferred promise timeouts (#8835)

Issue #8832 makes a good point that we should not be making
implicit assumptions about the client's performance
when waiting for internal events. At the same time,
we want to be able to get the debug info if some promises
never resolve because of missing backend events.

This PR adds a variable to turn on timeouts for deferred
promises created using `createDebuggableDeferredPromise`.
We can use it in our tests to catch never-resolving
promises or when reproducing bug reports where Puppeteer
hangs indefinitely.

Closes #8832
This commit is contained in:
Alex Rudenko 2022-08-24 12:05:32 +02:00 committed by GitHub
parent 341b669a5e
commit 202ffce0aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 37 additions and 14 deletions

View File

@ -30,11 +30,11 @@
"test": "c8 --check-coverage --lines 93 run-s test:chrome:* test:firefox", "test": "c8 --check-coverage --lines 93 run-s test:chrome:* test:firefox",
"test:types": "tsd", "test:types": "tsd",
"test:install": "scripts/test-install.sh", "test:install": "scripts/test-install.sh",
"test:firefox": "cross-env PUPPETEER_PRODUCT=firefox MOZ_WEBRENDER=0 mocha", "test:firefox": "cross-env PUPPETEER_PRODUCT=firefox MOZ_WEBRENDER=0 PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
"test:chrome": "run-s test:chrome:*", "test:chrome": "run-s test:chrome:*",
"test:chrome:headless": "cross-env HEADLESS=true mocha", "test:chrome:headless": "cross-env HEADLESS=true PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
"test:chrome:headless-chrome": "cross-env HEADLESS=chrome mocha", "test:chrome:headless-chrome": "cross-env HEADLESS=chrome PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
"test:chrome:headful": "cross-env HEADLESS=false mocha", "test:chrome:headful": "cross-env HEADLESS=false PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
"prepare": "node typescript-if-required.js && husky install", "prepare": "node typescript-if-required.js && husky install",
"lint": "run-s lint:prettier lint:eslint", "lint": "run-s lint:prettier lint:eslint",

View File

@ -17,7 +17,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import { import {
createDeferredPromiseWithTimer, createDebuggableDeferredPromise,
DeferredPromise, DeferredPromise,
} from '../util/DeferredPromise.js'; } from '../util/DeferredPromise.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
@ -150,7 +150,7 @@ export class FrameManager extends EventEmitter {
if (!this.#framesPendingTargetInit.has(targetId)) { if (!this.#framesPendingTargetInit.has(targetId)) {
this.#framesPendingTargetInit.set( this.#framesPendingTargetInit.set(
targetId, targetId,
createDeferredPromiseWithTimer( createDebuggableDeferredPromise(
`Waiting for target frame ${targetId} failed` `Waiting for target frame ${targetId} failed`
) )
); );
@ -318,7 +318,7 @@ export class FrameManager extends EventEmitter {
if (!this.#framesPendingAttachment.has(frameId)) { if (!this.#framesPendingAttachment.has(frameId)) {
this.#framesPendingAttachment.set( this.#framesPendingAttachment.set(
frameId, frameId,
createDeferredPromiseWithTimer( createDebuggableDeferredPromise(
`Waiting for frame ${frameId} to attach failed` `Waiting for frame ${frameId} to attach failed`
) )
); );

View File

@ -24,7 +24,7 @@ import {HTTPResponse} from './HTTPResponse.js';
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js'; import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
import {debugError, isString} from './util.js'; import {debugError, isString} from './util.js';
import { import {
createDeferredPromiseWithTimer, createDebuggableDeferredPromise,
DeferredPromise, DeferredPromise,
} from '../util/DeferredPromise.js'; } from '../util/DeferredPromise.js';
@ -144,9 +144,8 @@ export class NetworkManager extends EventEmitter {
if (this.#deferredInitPromise) { if (this.#deferredInitPromise) {
return this.#deferredInitPromise; return this.#deferredInitPromise;
} }
this.#deferredInitPromise = createDeferredPromiseWithTimer<void>( this.#deferredInitPromise = createDebuggableDeferredPromise<void>(
'NetworkManager initialization timed out', 'NetworkManager initialization timed out'
30000
); );
const init = Promise.all([ const init = Promise.all([
this.#ignoreHTTPSErrors this.#ignoreHTTPSErrors

View File

@ -18,3 +18,12 @@
* @internal * @internal
*/ */
export const isNode = !!(typeof process !== 'undefined' && process.version); export const isNode = !!(typeof process !== 'undefined' && process.version);
/**
* @internal
*/
export const deferredPromiseDebugTimeout =
typeof process !== 'undefined' &&
typeof process.env['PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT'] !== 'undefined'
? Number(process.env['PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT'])
: -1;

View File

@ -1,15 +1,16 @@
import {TimeoutError} from '../common/Errors.js'; import {TimeoutError} from '../common/Errors.js';
import {deferredPromiseDebugTimeout} from '../environment.js';
/** /**
* @internal * @internal
*/ */
export interface DeferredPromise<T> extends Promise<T> { export interface DeferredPromise<T> extends Promise<T> {
finished: () => boolean; finished: () => boolean;
resolved: () => boolean; resolved: () => boolean;
resolve: (_: T) => void; resolve: (_: T) => void;
reject: (_: Error) => void; reject: (_: Error) => void;
} }
/** /**
* Creates an returns a promise along with the resolve/reject functions. * Creates an returns a promise along with the resolve/reject functions.
* *
@ -18,7 +19,6 @@ export interface DeferredPromise<T> extends Promise<T> {
* *
* @internal * @internal
*/ */
export function createDeferredPromiseWithTimer<T>( export function createDeferredPromiseWithTimer<T>(
timeoutMessage: string, timeoutMessage: string,
timeout = 5000 timeout = 5000
@ -54,12 +54,12 @@ export function createDeferredPromiseWithTimer<T>(
}, },
}); });
} }
/** /**
* Creates an returns a promise along with the resolve/reject functions. * Creates an returns a promise along with the resolve/reject functions.
* *
* @internal * @internal
*/ */
export function createDeferredPromise<T>(): DeferredPromise<T> { export function createDeferredPromise<T>(): DeferredPromise<T> {
let isResolved = false; let isResolved = false;
let isRejected = false; let isRejected = false;
@ -86,3 +86,18 @@ export function createDeferredPromise<T>(): DeferredPromise<T> {
}, },
}); });
} }
/**
* @internal
*/
export function createDebuggableDeferredPromise<T>(
timeoutMessage: string
): DeferredPromise<T> {
if (deferredPromiseDebugTimeout > 0) {
return createDeferredPromiseWithTimer(
timeoutMessage,
deferredPromiseDebugTimeout
);
}
return createDeferredPromise();
}