fix(launcher): ensure that local browser launch waits for process exit (#489)

This commit is contained in:
Dmitry Gozman 2020-01-16 09:32:58 -08:00 committed by GitHub
parent 6ae6143711
commit 9b46014493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 23 additions and 5 deletions

View file

@ -67,7 +67,10 @@ export class CRBrowserServer {
} }
async connect(): Promise<CRBrowser> { async connect(): Promise<CRBrowser> {
return CRBrowser.connect(this._connectOptions); const browser = await CRBrowser.connect(this._connectOptions);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = this._gracefullyClose;
return browser;
} }
process(): ChildProcess { process(): ChildProcess {

View file

@ -64,7 +64,10 @@ export class FFBrowserServer {
} }
async connect(): Promise<FFBrowser> { async connect(): Promise<FFBrowser> {
return FFBrowser.connect(this._connectOptions); const browser = await FFBrowser.connect(this._connectOptions);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = this._gracefullyClose;
return browser;
} }
process(): ChildProcess { process(): ChildProcess {

View file

@ -23,6 +23,7 @@ import * as readline from 'readline';
import { TimeoutError } from '../errors'; import { TimeoutError } from '../errors';
import * as platform from '../platform'; import * as platform from '../platform';
const debugLauncher = platform.debug('pw:launcher');
const removeFolderAsync = platform.promisify(removeFolder); const removeFolderAsync = platform.promisify(removeFolder);
export type LaunchProcessOptions = { export type LaunchProcessOptions = {
@ -43,7 +44,10 @@ export type LaunchProcessOptions = {
type LaunchResult = { launchedProcess: childProcess.ChildProcess, gracefullyClose: () => Promise<void> }; type LaunchResult = { launchedProcess: childProcess.ChildProcess, gracefullyClose: () => Promise<void> };
let lastLaunchedId = 0;
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> { export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
const id = ++lastLaunchedId;
let stdio: ('ignore' | 'pipe')[] = ['pipe', 'pipe', 'pipe']; let stdio: ('ignore' | 'pipe')[] = ['pipe', 'pipe', 'pipe'];
if (options.pipe) { if (options.pipe) {
if (options.dumpio) if (options.dumpio)
@ -63,6 +67,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
stdio stdio
} }
); );
debugLauncher(`[${id}] <launching> ${options.executablePath} ${options.args.join(' ')}`);
if (!spawnedProcess.pid) { if (!spawnedProcess.pid) {
let reject: (e: Error) => void; let reject: (e: Error) => void;
@ -81,13 +86,14 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
let processClosed = false; let processClosed = false;
const waitForProcessToClose = new Promise((fulfill, reject) => { const waitForProcessToClose = new Promise((fulfill, reject) => {
spawnedProcess.once('exit', () => { spawnedProcess.once('exit', () => {
debugLauncher(`[${id}] <process did exit>`);
processClosed = true; processClosed = true;
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
// Cleanup as processes exit. // Cleanup as processes exit.
if (options.tempDir) { if (options.tempDir) {
removeFolderAsync(options.tempDir) removeFolderAsync(options.tempDir)
.then(() => fulfill()) .catch((err: Error) => console.error(err))
.catch((err: Error) => console.error(err)); .then(fulfill);
} else { } else {
fulfill(); fulfill();
} }
@ -111,14 +117,17 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
if (gracefullyClosing) if (gracefullyClosing)
return; return;
gracefullyClosing = true; gracefullyClosing = true;
debugLauncher(`[${id}] <gracefully close start>`);
options.attemptToGracefullyClose().catch(() => killProcess()); options.attemptToGracefullyClose().catch(() => killProcess());
// TODO: forcefully kill the process after some timeout. // TODO: forcefully kill the process after some timeout.
await waitForProcessToClose; await waitForProcessToClose;
debugLauncher(`[${id}] <gracefully close end>`);
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
} }
// This method has to be sync to be used as 'exit' event handler. // This method has to be sync to be used as 'exit' event handler.
function killProcess() { function killProcess() {
debugLauncher(`[${id}] <kill>`);
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) { if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {
// Force kill chrome. // Force kill chrome.

View file

@ -57,7 +57,10 @@ export class WKBrowserServer {
} }
async connect(): Promise<WKBrowser> { async connect(): Promise<WKBrowser> {
return WKBrowser.connect(this._connectOptions); const browser = await WKBrowser.connect(this._connectOptions);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = this._gracefullyClose;
return browser;
} }
process(): ChildProcess { process(): ChildProcess {