Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revise taskkill procedure on windows #267

Merged
merged 37 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8059485
add lh and smoke ci
paulirish May 28, 2022
059ca08
ci on branches
paulirish May 28, 2022
ad6e637
tweaks
paulirish May 28, 2022
9346117
extra logging
paulirish May 28, 2022
16364c3
log stderrout
paulirish May 28, 2022
262dd6a
link
paulirish May 28, 2022
8a9c768
more logging
paulirish May 28, 2022
4728b80
Revert "more logging"
paulirish May 28, 2022
8898ba3
bring back reject
paulirish May 28, 2022
59829a8
extra kill
paulirish May 28, 2022
8b723c4
hmm
paulirish May 28, 2022
35a8fe7
Revert "Revert "more logging""
paulirish May 28, 2022
31c1a16
log it out
paulirish May 28, 2022
6674664
k
paulirish May 28, 2022
4a2679f
trace
paulirish May 28, 2022
b7b7b6c
kill catch
paulirish May 28, 2022
210c7e0
encoding
paulirish May 28, 2022
0bea7c7
drop cl changes
paulirish May 28, 2022
08e8ef1
just pr
paulirish May 28, 2022
2a4f4e7
spawn chrome non-detached on windows
paulirish May 30, 2022
210f2bf
rename chrome => chromeProc
paulirish May 30, 2022
9d5b507
adopt much of playwright's kill approach
paulirish May 30, 2022
4771eaf
headless thing
paulirish May 30, 2022
bde6f3a
take lh github master out of lockfile
paulirish May 30, 2022
d84ba2b
install and add -D lh at same time
paulirish May 30, 2022
d395385
drop diff
paulirish May 30, 2022
8353ed6
upload artifacts path
paulirish May 30, 2022
537054f
Merge branch 'lighthouse-smoke' into adopt-pw-kill
paulirish May 30, 2022
402f785
fixlogs
paulirish May 30, 2022
85034bd
other windows smoketests beyond just lantern
paulirish May 30, 2022
0f3786d
formatting
paulirish May 30, 2022
8994710
Merge branch 'lighthouse-smoke' into adopt-pw-kill
paulirish May 30, 2022
39b7d6f
no retries. hardmode
paulirish May 31, 2022
c32034d
utf8 encoding
paulirish May 31, 2022
c1a9b54
less
paulirish May 31, 2022
05a9c54
Merge branch 'master' into adopt-pw-kill
paulirish May 31, 2022
3f7b664
chromeProc -> chromeProcess
paulirish May 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lh-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

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

- name: Upload failures
if: failure()
Expand Down
61 changes: 35 additions & 26 deletions src/chrome-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import {getRandomPort} from './random-port';
import {DEFAULT_FLAGS} from './flags';
import {makeTmpDir, defaults, delay, getPlatform, toWin32Path, InvalidUserDataDirectoryError, UnsupportedPlatformError, ChromeNotInstalledError} from './utils';
import {ChildProcess} from 'child_process';
import {spawn, spawnSync} from 'child_process';
const log = require('lighthouse-logger');
const spawn = childProcess.spawn;
const execSync = childProcess.execSync;
const isWsl = getPlatform() === 'wsl';
const isWindows = getPlatform() === 'win32';
const _SIGINT = 'SIGINT';
Expand Down Expand Up @@ -81,7 +80,7 @@ async function launch(opts: Options = {}): Promise<LaunchedChrome> {
return instance.kill();
};

return {pid: instance.pid!, port: instance.port!, kill, process: instance.chrome!};
return {pid: instance.pid!, port: instance.port!, kill, process: instance.chromeProcess!};
}

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

chrome?: childProcess.ChildProcess;
chromeProcess?: childProcess.ChildProcess;
userDataDir?: string;
port?: number;
pid?: number;
Expand Down Expand Up @@ -175,6 +174,8 @@ class Launcher {
flags.push(`--user-data-dir=${isWsl ? toWin32Path(this.userDataDir) : this.userDataDir}`);
}

if (process.env.HEADLESS) flags.push('--headless');

flags.push(...this.chromeFlags);
flags.push(this.startingUrl);

Expand Down Expand Up @@ -276,9 +277,9 @@ class Launcher {

private async spawnProcess(execPath: string) {
const spawnPromise = (async () => {
if (this.chrome) {
log.log('ChromeLauncher', `Chrome already running with pid ${this.chrome.pid}.`);
return this.chrome.pid;
if (this.chromeProcess) {
log.log('ChromeLauncher', `Chrome already running with pid ${this.chromeProcess.pid}.`);
return this.chromeProcess.pid;
}


Expand All @@ -292,17 +293,23 @@ class Launcher {

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

if (chrome.pid) {
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());
if (this.chromeProcess.pid) {
this.fs.writeFileSync(this.pidFile, this.chromeProcess.pid.toString());
}

log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
return chrome.pid;
log.verbose(
'ChromeLauncher',
`Chrome running with pid ${this.chromeProcess.pid} on port ${this.port}.`);
return this.chromeProcess.pid;
})();

const pid = await spawnPromise;
Expand Down Expand Up @@ -373,28 +380,30 @@ class Launcher {
}

kill() {
return new Promise<void>((resolve, reject) => {
if (this.chrome) {
this.chrome.on('close', () => {
delete this.chrome;
return new Promise<void>((resolve) => {
if (this.chromeProcess) {
this.chromeProcess.on('close', () => {
delete this.chromeProcess;
this.destroyTmp().then(resolve);
});

log.log('ChromeLauncher', `Killing Chrome instance ${this.chrome.pid}`);
log.log('ChromeLauncher', `Killing Chrome instance ${this.chromeProcess.pid}`);
try {
if (isWindows) {
// While pipe is the default, stderr also gets printed to process.stderr
// if you don't explicitly set `stdio`
execSync(`taskkill /pid ${this.chrome.pid} /T /F`, {stdio: 'pipe'});
// https://github.com/GoogleChrome/chrome-launcher/issues/266
const taskkillProc = spawnSync(
`taskkill /pid ${this.chromeProcess.pid} /T /F`, {shell: true, encoding: 'utf-8'});

const {stderr} = taskkillProc;
if (stderr) log.error('ChromeLauncher', `taskkill stderr`, stderr);
} else {
if (this.chrome.pid) {
process.kill(-this.chrome.pid);
if (this.chromeProcess.pid) {
process.kill(-this.chromeProcess.pid, 'SIGKILL');
}
}
} catch (err) {
const message = `Chrome could not be killed ${err.message}`;
log.warn('ChromeLauncher', message);
reject(new Error(message));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The smoke tests still pass even with this rejection right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no they don't.

removing this rejection is deliberate actually. as mentioned in #266, pptr and pw deliberately do not reject in this case.

and i've been convinced that this is reasonable. there appears to be a race condition and taskkill is attempting to kill processes that are already gone. what's interesting is that when this happens. the processes that fail to be killed (cuz they're not found) are all child proc of the primary chrome proc. the chrome proc is always successfully killed.

take a peek at the SO item linked in microsoft/playwright#7500 .... PW used this MEMUSAGE hack for a while but then determined it wasnt needed. so i'm following their latest pattern.

i think it's still important we have some visibility on when taskkill returns an error, but it's not a fatal situation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They removed it because

Since then, we no longer inherit stdout/stderr, so the error message
should not appear anymore.

Which we also do not do so lgtm.

}
} else {
// fail silently as we did not start chrome
Expand Down