fix(test-runner): don't double emit SIGINT in long running test (#24265)

https://github.com/microsoft/playwright/issues/23907

This fixes the following scenario. You press Command + C during the
test-execution, then a global teardown will execute, which takes a long
time and before this on macOS did sometimes end up in an unwanted SIGINT
received inside the globalTeardown handler. After this change this won't
happen anymore.
This commit is contained in:
Max Schmitt 2023-07-18 18:46:18 +02:00 коммит произвёл GitHub
Родитель 0b2516d71a
Коммит d2f581a19c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
1 изменённых файлов: 50 добавлений и 17 удалений

Просмотреть файл

@ -22,25 +22,11 @@ export class SigIntWatcher {
let sigintCallback: () => void; let sigintCallback: () => void;
this._sigintPromise = new Promise<void>(f => sigintCallback = f); this._sigintPromise = new Promise<void>(f => sigintCallback = f);
this._sigintHandler = () => { this._sigintHandler = () => {
// We remove the handler so that second Ctrl+C immediately kills the runner FixedNodeSIGINTHandler.off(this._sigintHandler);
// via the default sigint handler. This is handy in the case where our shutdown
// takes a lot of time or is buggy.
//
// When running through NPM we might get multiple SIGINT signals
// for a single Ctrl+C - this is an NPM bug present since at least NPM v6.
// https://github.com/npm/cli/issues/1591
// https://github.com/npm/cli/issues/2124
//
// Therefore, removing the handler too soon will just kill the process
// with default handler without printing the results.
// We work around this by giving NPM 1000ms to send us duplicate signals.
// The side effect is that slow shutdown or bug in our runner will force
// the user to hit Ctrl+C again after at least a second.
setTimeout(() => process.off('SIGINT', this._sigintHandler), 1000);
this._hadSignal = true; this._hadSignal = true;
sigintCallback(); sigintCallback();
}; };
process.on('SIGINT', this._sigintHandler); FixedNodeSIGINTHandler.on(this._sigintHandler);
} }
promise(): Promise<void> { promise(): Promise<void> {
@ -52,6 +38,53 @@ export class SigIntWatcher {
} }
disarm() { disarm() {
process.off('SIGINT', this._sigintHandler); FixedNodeSIGINTHandler.off(this._sigintHandler);
}
}
// NPM/NPX will send us duplicate SIGINT signals, so we need to ignore them.
class FixedNodeSIGINTHandler {
private static _handlers: (() => void)[] = [];
private static _ignoreNextSIGINTs = false;
static _dispatch = () => {
if (this._ignoreNextSIGINTs)
return;
this._ignoreNextSIGINTs = true;
setTimeout(() => {
this._ignoreNextSIGINTs = false;
// We remove the handler so that second Ctrl+C immediately kills the process
// via the default sigint handler. This is handy in the case where our shutdown
// takes a lot of time or is buggy.
//
// When running through NPM we might get multiple SIGINT signals
// for a single Ctrl+C - this is an NPM bug present since NPM v6+.
// https://github.com/npm/cli/issues/1591
// https://github.com/npm/cli/issues/2124
// https://github.com/npm/cli/issues/5021
//
// Therefore, removing the handler too soon will just kill the process
// with default handler without printing the results.
// We work around this by giving NPM 1000ms to send us duplicate signals.
// The side effect is that slow shutdown or bug in our process will force
// the user to hit Ctrl+C again after at least a second.
if (!this._handlers.length)
process.off('SIGINT', this._dispatch);
}, 1000);
for (const handler of this._handlers)
handler();
};
static on(handler: () => void) {
this._handlers.push(handler);
if (this._handlers.length === 1)
process.on('SIGINT', this._dispatch);
}
static off(handler: () => void) {
this._handlers = this._handlers.filter(h => h !== handler);
if (!this._ignoreNextSIGINTs && !this._handlers.length)
process.off('SIGINT', this._dispatch);
} }
} }