-
Notifications
You must be signed in to change notification settings - Fork 354
Fix race condition when killing Chrome on Windows when disconnecting #703
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -312,32 +312,17 @@ export class ChromeDebugAdapter extends CoreDebugAdapter { | |
super.onResumed(); | ||
} | ||
|
||
public disconnect(args: DebugProtocol.DisconnectArguments): void { | ||
public async disconnect(args: DebugProtocol.DisconnectArguments): Promise<void> { | ||
const hadTerminated = this._hasTerminated; | ||
|
||
// Disconnect before killing Chrome, because running "taskkill" when it's paused sometimes doesn't kill it | ||
super.disconnect(args); | ||
|
||
if ( (this._chromeProc || (!this._chromeProc && this._chromePID)) && !hadTerminated) { | ||
if ( (this._chromeProc || this._chromePID) && !hadTerminated) { | ||
// Only kill Chrome if the 'disconnect' originated from vscode. If we previously terminated | ||
// due to Chrome shutting down, or devtools taking over, don't kill Chrome. | ||
if (coreUtils.getPlatform() === coreUtils.Platform.Windows && this._chromePID) { | ||
let taskkillCmd = `taskkill /PID ${this._chromePID}`; | ||
logger.log(`Killing Chrome process by pid: ${taskkillCmd}`); | ||
try { | ||
// Run synchronously because this process may be killed before exec() would run | ||
execSync(taskkillCmd); | ||
} catch (e) { | ||
// Can fail if Chrome was already open, and the process with _chromePID is gone. | ||
// Or if it already shut down for some reason. | ||
} | ||
// execSync above may succeed, but Chrome still might not shut down, for example if the web page promts the user about unsaved changes. | ||
// In that case, we need to use /F to force shutdown, but we risk Chrome not shutting down correctly. | ||
taskkillCmd = `taskkill /F /PID ${this._chromePID}`; | ||
logger.log(`Killing Chrome process by pid (using force in case the first attempt failed): ${taskkillCmd}`); | ||
try { | ||
execSync(taskkillCmd); | ||
} catch (e) {} | ||
await this.killChromeOnWindows(this._chromePID); | ||
} else if (this._chromeProc) { | ||
logger.log('Killing Chrome process'); | ||
this._chromeProc.kill('SIGINT'); | ||
|
@@ -347,6 +332,37 @@ export class ChromeDebugAdapter extends CoreDebugAdapter { | |
this._chromeProc = null; | ||
} | ||
|
||
private async killChromeOnWindows(chromePID: number): Promise<void> { | ||
let taskkillCmd = `taskkill /PID ${chromePID}`; | ||
logger.log(`Killing Chrome process by pid: ${taskkillCmd}`); | ||
try { | ||
execSync(taskkillCmd); | ||
} catch (e) { | ||
// The command will fail if process was not found. This can be safely ignored. | ||
} | ||
|
||
for (let i = 0 ; i < 10; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the use for this for loop? How is this better than using a timeout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This follows the pattern in I think it's readable but I am open to changing it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine but you could also use the |
||
// Check to see if the process is still running | ||
let tasklistOutput = execSync(`tasklist /FI "PID eq ${chromePID}"`).toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we log this execution too? |
||
if (!tasklistOutput.includes(chromePID.toString())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible that the chromePID is a substring of another process PID? Is it possible that the output of the command is not in the format that we expect? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When tasklist finds the process it will return output that looks like When tasklist doesn't find the process it outputs In both cases the exit code is 0 Because the tasklist is run with a PID filter it will only return one or zero processes. Any process that with a PID that is a superstring will be filtered out. I implemented with a search of PID in the output because it seemed like the most robust way to differentiate between success and failure outputs. Searching for the presence of the PID is independent of the format. Let me know if you have any better suggestions. I can add comments as documentation for what is going on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this to use csv output which is slightly better, but still not perfect. |
||
return; | ||
} | ||
|
||
// Give the process some time to close gracefully | ||
logger.log(`Chrome process with pid ${chromePID} is still alive, waiting...`) | ||
await new Promise<void>((resolve) => { | ||
setTimeout(resolve, 200); | ||
}); | ||
} | ||
|
||
// At this point we can assume the process won't close on its own, so force kill it | ||
let taskkillForceCmd = `taskkill /F /PID ${chromePID}`; | ||
logger.log(`Killing Chrome process timed out. Killing again using force: ${taskkillForceCmd}`); | ||
try { | ||
execSync(taskkillForceCmd); | ||
} catch (e) {} | ||
} | ||
|
||
/** | ||
* Opt-in event called when the 'reload' button in the debug widget is pressed | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have enough telemetry to figure out if this method/logic is working as expected?