Skip to content

Commit 3561350

Browse files
authored
revise taskkill procedure on windows (#267)
1 parent 690ae98 commit 3561350

File tree

2 files changed

+36
-27
lines changed

2 files changed

+36
-27
lines changed

.github/workflows/lh-smoke.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737

3838
- name: Run smoke tests
3939
# Windows bots are slow, so only run enough tests to verify matching behavior.
40-
run: yarn --cwd node_modules/lighthouse/ smoke --debug -j=2 --retries=5 --shard=${{ matrix.smoke-test-shard }}/2 dbw oopif offline lantern metrics
40+
run: yarn --cwd node_modules/lighthouse/ smoke --debug -j=2 --retries=0 --shard=${{ matrix.smoke-test-shard }}/2 dbw oopif offline lantern metrics
4141

4242
- name: Upload failures
4343
if: failure()

src/chrome-launcher.ts

+35-26
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ import {getRandomPort} from './random-port';
1313
import {DEFAULT_FLAGS} from './flags';
1414
import {makeTmpDir, defaults, delay, getPlatform, toWin32Path, InvalidUserDataDirectoryError, UnsupportedPlatformError, ChromeNotInstalledError} from './utils';
1515
import {ChildProcess} from 'child_process';
16+
import {spawn, spawnSync} from 'child_process';
1617
const log = require('lighthouse-logger');
17-
const spawn = childProcess.spawn;
18-
const execSync = childProcess.execSync;
1918
const isWsl = getPlatform() === 'wsl';
2019
const isWindows = getPlatform() === 'win32';
2120
const _SIGINT = 'SIGINT';
@@ -81,7 +80,7 @@ async function launch(opts: Options = {}): Promise<LaunchedChrome> {
8180
return instance.kill();
8281
};
8382

84-
return {pid: instance.pid!, port: instance.port!, kill, process: instance.chrome!};
83+
return {pid: instance.pid!, port: instance.port!, kill, process: instance.chromeProcess!};
8584
}
8685

8786
/** Returns Chrome installation path that chrome-launcher will launch by default. */
@@ -126,7 +125,7 @@ class Launcher {
126125
private useDefaultProfile: boolean;
127126
private envVars: {[key: string]: string|undefined};
128127

129-
chrome?: childProcess.ChildProcess;
128+
chromeProcess?: childProcess.ChildProcess;
130129
userDataDir?: string;
131130
port?: number;
132131
pid?: number;
@@ -175,6 +174,8 @@ class Launcher {
175174
flags.push(`--user-data-dir=${isWsl ? toWin32Path(this.userDataDir) : this.userDataDir}`);
176175
}
177176

177+
if (process.env.HEADLESS) flags.push('--headless');
178+
178179
flags.push(...this.chromeFlags);
179180
flags.push(this.startingUrl);
180181

@@ -276,9 +277,9 @@ class Launcher {
276277

277278
private async spawnProcess(execPath: string) {
278279
const spawnPromise = (async () => {
279-
if (this.chrome) {
280-
log.log('ChromeLauncher', `Chrome already running with pid ${this.chrome.pid}.`);
281-
return this.chrome.pid;
280+
if (this.chromeProcess) {
281+
log.log('ChromeLauncher', `Chrome already running with pid ${this.chromeProcess.pid}.`);
282+
return this.chromeProcess.pid;
282283
}
283284

284285

@@ -292,17 +293,23 @@ class Launcher {
292293

293294
log.verbose(
294295
'ChromeLauncher', `Launching with command:\n"${execPath}" ${this.flags.join(' ')}`);
295-
const chrome = this.spawn(
296-
execPath, this.flags,
297-
{detached: true, stdio: ['ignore', this.outFile, this.errFile], env: this.envVars});
298-
this.chrome = chrome;
296+
this.chromeProcess = this.spawn(execPath, this.flags, {
297+
// On non-windows platforms, `detached: true` makes child process a leader of a new
298+
// process group, making it possible to kill child process tree with `.kill(-pid)` command.
299+
// @see https://nodejs.org/api/child_process.html#child_process_options_detached
300+
detached: process.platform !== 'win32',
301+
stdio: ['ignore', this.outFile, this.errFile],
302+
env: this.envVars
303+
});
299304

300-
if (chrome.pid) {
301-
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());
305+
if (this.chromeProcess.pid) {
306+
this.fs.writeFileSync(this.pidFile, this.chromeProcess.pid.toString());
302307
}
303308

304-
log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
305-
return chrome.pid;
309+
log.verbose(
310+
'ChromeLauncher',
311+
`Chrome running with pid ${this.chromeProcess.pid} on port ${this.port}.`);
312+
return this.chromeProcess.pid;
306313
})();
307314

308315
const pid = await spawnPromise;
@@ -373,28 +380,30 @@ class Launcher {
373380
}
374381

375382
kill() {
376-
return new Promise<void>((resolve, reject) => {
377-
if (this.chrome) {
378-
this.chrome.on('close', () => {
379-
delete this.chrome;
383+
return new Promise<void>((resolve) => {
384+
if (this.chromeProcess) {
385+
this.chromeProcess.on('close', () => {
386+
delete this.chromeProcess;
380387
this.destroyTmp().then(resolve);
381388
});
382389

383-
log.log('ChromeLauncher', `Killing Chrome instance ${this.chrome.pid}`);
390+
log.log('ChromeLauncher', `Killing Chrome instance ${this.chromeProcess.pid}`);
384391
try {
385392
if (isWindows) {
386-
// While pipe is the default, stderr also gets printed to process.stderr
387-
// if you don't explicitly set `stdio`
388-
execSync(`taskkill /pid ${this.chrome.pid} /T /F`, {stdio: 'pipe'});
393+
// https://github.com/GoogleChrome/chrome-launcher/issues/266
394+
const taskkillProc = spawnSync(
395+
`taskkill /pid ${this.chromeProcess.pid} /T /F`, {shell: true, encoding: 'utf-8'});
396+
397+
const {stderr} = taskkillProc;
398+
if (stderr) log.error('ChromeLauncher', `taskkill stderr`, stderr);
389399
} else {
390-
if (this.chrome.pid) {
391-
process.kill(-this.chrome.pid);
400+
if (this.chromeProcess.pid) {
401+
process.kill(-this.chromeProcess.pid, 'SIGKILL');
392402
}
393403
}
394404
} catch (err) {
395405
const message = `Chrome could not be killed ${err.message}`;
396406
log.warn('ChromeLauncher', message);
397-
reject(new Error(message));
398407
}
399408
} else {
400409
// fail silently as we did not start chrome

0 commit comments

Comments
 (0)