mirror of
https://github.com/hanzoai/playwright.git
synced 2026-09-24 17:01:53 +00:00
fix(runner): do not force-kill worker while its teardown is in progress (#42010)
This commit is contained in:
@@ -105,6 +105,7 @@ export function startProcessRunner(create: (params: any) => ProcessRunner) {
|
||||
}
|
||||
|
||||
const kForceExitTimeout = +(process.env.PWTEST_FORCE_EXIT_TIMEOUT || 30000);
|
||||
const kHeartbeatInterval = 1000;
|
||||
|
||||
async function gracefullyCloseAndExit(forceExit: boolean) {
|
||||
if (forceExit && !forceExitInitiated) {
|
||||
@@ -115,8 +116,13 @@ async function gracefullyCloseAndExit(forceExit: boolean) {
|
||||
}
|
||||
if (!gracefullyCloseCalled) {
|
||||
gracefullyCloseCalled = true;
|
||||
// Heartbeats tell the parent that graceful close is still running, e.g. a fixture
|
||||
// teardown with "timeout: 0", as opposed to a hung process that must be force-killed.
|
||||
const heartbeat = setInterval(() => sendMessageToParent({ method: '__heartbeat__' }), kHeartbeatInterval);
|
||||
heartbeat.unref();
|
||||
// Meanwhile, try to gracefully shutdown.
|
||||
await processRunner?.gracefullyClose().catch(() => {});
|
||||
clearInterval(heartbeat);
|
||||
if (processName)
|
||||
await stopProfiling(processName).catch(() => {});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
|
||||
@@ -170,11 +170,25 @@ export class ProcessHost extends EventEmitter {
|
||||
return;
|
||||
const exitPromise = new Promise<void>(f => this.once('exit', () => f()));
|
||||
const timeout = +(process.env.PWTEST_CHILD_PROCESS_TIMEOUT || 5 * 60 * 1000);
|
||||
const result = await raceAgainstDeadline(() => exitPromise, monotonicTime() + timeout);
|
||||
if (result.timedOut) {
|
||||
this.emit('processError', { message: `Error: ${this._processName} process did not exit within ${timeout}ms after stop, force-killed it` });
|
||||
this._forceKill();
|
||||
await exitPromise;
|
||||
// Child sends heartbeats while gracefully closing, e.g. running a slow fixture
|
||||
// teardown with "timeout: 0". Only force-kill when heartbeats stop coming.
|
||||
let lastHeartbeat = monotonicTime();
|
||||
const onHeartbeat = () => lastHeartbeat = monotonicTime();
|
||||
this.on('__heartbeat__', onHeartbeat);
|
||||
try {
|
||||
while (true) {
|
||||
const result = await raceAgainstDeadline(() => exitPromise, lastHeartbeat + timeout);
|
||||
if (!result.timedOut)
|
||||
return;
|
||||
if (monotonicTime() < lastHeartbeat + timeout)
|
||||
continue;
|
||||
this.emit('processError', { message: `Error: ${this._processName} process did not exit within ${timeout}ms after stop, force-killed it` });
|
||||
this._forceKill();
|
||||
await exitPromise;
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
this.off('__heartbeat__', onHeartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,3 +230,24 @@ test('should force-kill a worker that does not exit on stop', async ({ runInline
|
||||
// Should complete well within a minute thanks to the watchdog.
|
||||
expect(monotonicTime() - now).toBeLessThan(60000);
|
||||
});
|
||||
|
||||
test('should not force-kill a worker that is running a slow fixture teardown', async ({ runInlineTest }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42007' });
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend<{}, { slowTeardown: void }>({
|
||||
slowTeardown: [async ({}, use) => {
|
||||
await use();
|
||||
await new Promise(f => setTimeout(f, 4000));
|
||||
console.log('slow teardown finished');
|
||||
}, { scope: 'worker', timeout: 0 }],
|
||||
});
|
||||
test('passes', async ({ slowTeardown }) => {});
|
||||
`,
|
||||
}, undefined, { PWTEST_CHILD_PROCESS_TIMEOUT: '2000' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).toContain('slow teardown finished');
|
||||
expect(result.output).not.toContain('force-killed');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user