fix: wait for cleanup on double SIGINT (#13411)

This is a speculative fix to leftover tmp directories.
When users issues SIGINT twice, we enter `gracefullyClose()`
twice, and shortcut the second time. It turns out, we do
not wait for directories removal.

Note: it is unknown how often we reach this codepath in practice.
This commit is contained in:
Dmitry Gozman 2022-04-07 19:20:54 -07:00 committed by GitHub
parent cef476b89f
commit 155bb7fcae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 10 additions and 5 deletions

View File

@ -63,6 +63,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
browserServer.close = () => browser.options.browserProcess.close(); browserServer.close = () => browser.options.browserProcess.close();
browserServer.kill = () => browser.options.browserProcess.kill(); browserServer.kill = () => browser.options.browserProcess.kill();
(browserServer as any)._disconnectForTest = () => server.close(); (browserServer as any)._disconnectForTest = () => server.close();
(browserServer as any)._artifactsDirForTest = browser.options.artifactsDir;
browser.options.browserProcess.onclose = async (exitCode, signal) => { browser.options.browserProcess.onclose = async (exitCode, signal) => {
server.close(); server.close();
browserServer.emit('close', exitCode, signal); browserServer.emit('close', exitCode, signal);

View File

@ -110,8 +110,6 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
}); });
let processClosed = false; let processClosed = false;
let fulfillClose = () => {};
const waitForClose = new Promise<void>(f => fulfillClose = f);
let fulfillCleanup = () => {}; let fulfillCleanup = () => {};
const waitForCleanup = new Promise<void>(f => fulfillCleanup = f); const waitForCleanup = new Promise<void>(f => fulfillCleanup = f);
spawnedProcess.once('exit', (exitCode, signal) => { spawnedProcess.once('exit', (exitCode, signal) => {
@ -120,7 +118,6 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
eventsHelper.removeEventListeners(listeners); eventsHelper.removeEventListeners(listeners);
gracefullyCloseSet.delete(gracefullyClose); gracefullyCloseSet.delete(gracefullyClose);
options.onExit(exitCode, signal); options.onExit(exitCode, signal);
fulfillClose();
// Cleanup as process exits. // Cleanup as process exits.
cleanup().then(fulfillCleanup); cleanup().then(fulfillCleanup);
}); });
@ -153,7 +150,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
if (gracefullyClosing) { if (gracefullyClosing) {
options.log(`[pid=${spawnedProcess.pid}] <forecefully close>`); options.log(`[pid=${spawnedProcess.pid}] <forecefully close>`);
killProcess(); killProcess();
await waitForClose; // Ensure the process is dead and we called options.onkill. await waitForCleanup; // Ensure the process is dead and we have cleaned up.
return; return;
} }
gracefullyClosing = true; gracefullyClosing = true;

View File

@ -21,6 +21,7 @@ async function start() {
console.log(`(exitCode=>${exitCode})`); console.log(`(exitCode=>${exitCode})`);
console.log(`(signal=>${signal})`); console.log(`(signal=>${signal})`);
}); });
console.log(`(tempDir=>${browserServer._artifactsDirForTest})`);
console.log(`(pid=>${browserServer.process().pid})`); console.log(`(pid=>${browserServer.process().pid})`);
console.log(`(wsEndpoint=>${browserServer.wsEndpoint()})`); console.log(`(wsEndpoint=>${browserServer.wsEndpoint()})`);
} }

View File

@ -17,6 +17,7 @@
import { playwrightTest as test, expect } from '../config/browserTest'; import { playwrightTest as test, expect } from '../config/browserTest';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import fs from 'fs';
test.slow(); test.slow();
@ -78,14 +79,19 @@ test.describe('signals', () => {
expect(await remoteServer.childExitCode()).toBe(0); expect(await remoteServer.childExitCode()).toBe(0);
}); });
test('should kill the browser on double SIGINT', async ({ startRemoteServer, server }) => { test('should kill the browser on double SIGINT and remove temp dir', async ({ startRemoteServer, server }) => {
const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE });
const tempDir = await remoteServer.out('tempDir');
const before = fs.existsSync(tempDir);
process.kill(remoteServer.child().pid, 'SIGINT'); process.kill(remoteServer.child().pid, 'SIGINT');
await remoteServer.out('stalled'); await remoteServer.out('stalled');
process.kill(remoteServer.child().pid, 'SIGINT'); process.kill(remoteServer.child().pid, 'SIGINT');
expect(await remoteServer.out('exitCode')).toBe('null'); expect(await remoteServer.out('exitCode')).toBe('null');
expect(await remoteServer.out('signal')).toBe('SIGKILL'); expect(await remoteServer.out('signal')).toBe('SIGKILL');
expect(await remoteServer.childExitCode()).toBe(130); expect(await remoteServer.childExitCode()).toBe(130);
const after = fs.existsSync(tempDir);
expect(before).toBe(true);
expect(after).toBe(false);
}); });
test('should kill the browser on SIGINT + SIGTERM', async ({ startRemoteServer, server }) => { test('should kill the browser on SIGINT + SIGTERM', async ({ startRemoteServer, server }) => {