Skip to content
This repository was archived by the owner on Dec 6, 2022. It is now read-only.

Commit 83decfc

Browse files
authored
Merge pull request #703 from mrcrane/killchromeonwindows
Fix race condition when killing Chrome on Windows when disconnecting
2 parents 734870a + e85978f commit 83decfc

2 files changed

Lines changed: 42 additions & 19 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/chromeDebugAdapter.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -312,32 +312,17 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
312312
super.onResumed();
313313
}
314314

315-
public disconnect(args: DebugProtocol.DisconnectArguments): void {
315+
public async disconnect(args: DebugProtocol.DisconnectArguments): Promise<void> {
316316
const hadTerminated = this._hasTerminated;
317317

318318
// Disconnect before killing Chrome, because running "taskkill" when it's paused sometimes doesn't kill it
319319
super.disconnect(args);
320320

321-
if ( (this._chromeProc || (!this._chromeProc && this._chromePID)) && !hadTerminated) {
321+
if ( (this._chromeProc || this._chromePID) && !hadTerminated) {
322322
// Only kill Chrome if the 'disconnect' originated from vscode. If we previously terminated
323323
// due to Chrome shutting down, or devtools taking over, don't kill Chrome.
324324
if (coreUtils.getPlatform() === coreUtils.Platform.Windows && this._chromePID) {
325-
let taskkillCmd = `taskkill /PID ${this._chromePID}`;
326-
logger.log(`Killing Chrome process by pid: ${taskkillCmd}`);
327-
try {
328-
// Run synchronously because this process may be killed before exec() would run
329-
execSync(taskkillCmd);
330-
} catch (e) {
331-
// Can fail if Chrome was already open, and the process with _chromePID is gone.
332-
// Or if it already shut down for some reason.
333-
}
334-
// execSync above may succeed, but Chrome still might not shut down, for example if the web page promts the user about unsaved changes.
335-
// In that case, we need to use /F to force shutdown, but we risk Chrome not shutting down correctly.
336-
taskkillCmd = `taskkill /F /PID ${this._chromePID}`;
337-
logger.log(`Killing Chrome process by pid (using force in case the first attempt failed): ${taskkillCmd}`);
338-
try {
339-
execSync(taskkillCmd);
340-
} catch (e) {}
325+
await this.killChromeOnWindows(this._chromePID);
341326
} else if (this._chromeProc) {
342327
logger.log('Killing Chrome process');
343328
this._chromeProc.kill('SIGINT');
@@ -347,6 +332,44 @@ export class ChromeDebugAdapter extends CoreDebugAdapter {
347332
this._chromeProc = null;
348333
}
349334

335+
private async killChromeOnWindows(chromePID: number): Promise<void> {
336+
let taskkillCmd = `taskkill /PID ${chromePID}`;
337+
logger.log(`Killing Chrome process by pid: ${taskkillCmd}`);
338+
try {
339+
execSync(taskkillCmd);
340+
} catch (e) {
341+
// The command will fail if process was not found. This can be safely ignored.
342+
}
343+
344+
for (let i = 0 ; i < 10; i++) {
345+
// Check to see if the process is still running, with CSV output format
346+
let tasklistCmd = `tasklist /FI "PID eq ${chromePID}" /FO CSV`;
347+
logger.log(`Looking up process by pid: ${tasklistCmd}`);
348+
let tasklistOutput = execSync(tasklistCmd).toString();
349+
350+
// If the process is found, tasklist will output CSV with one of the values being the PID. Exit code will be 0.
351+
// If the process is not found, tasklist will give a generic "not found" message instead. Exit code will also be 0.
352+
// If we see an entry in the CSV for the PID, then we can assume the process was found.
353+
if (!tasklistOutput.includes(`"${chromePID}"`)) {
354+
logger.log(`Chrome process with pid ${chromePID} is not running`);
355+
return;
356+
}
357+
358+
// Give the process some time to close gracefully
359+
logger.log(`Chrome process with pid ${chromePID} is still alive, waiting...`);
360+
await new Promise<void>((resolve) => {
361+
setTimeout(resolve, 200);
362+
});
363+
}
364+
365+
// At this point we can assume the process won't close on its own, so force kill it
366+
let taskkillForceCmd = `taskkill /F /PID ${chromePID}`;
367+
logger.log(`Killing Chrome process timed out. Killing again using force: ${taskkillForceCmd}`);
368+
try {
369+
execSync(taskkillForceCmd);
370+
} catch (e) {}
371+
}
372+
350373
/**
351374
* Opt-in event called when the 'reload' button in the debug widget is pressed
352375
*/

0 commit comments

Comments
 (0)