debug: fix compound integrated terminal launches breaking

Fixes https://github.com/microsoft/vscode/issues/71850

Adds more resilient handling of multiple debug terminals, reusing
terminals and adding a 1-second "lock out" before a terminal is
candidate for reuse.
This commit is contained in:
Connor Peet
2020-11-12 15:22:25 -08:00
parent cb7297a10a
commit 591039e34c
3 changed files with 145 additions and 17 deletions

View File

@@ -23,13 +23,14 @@ import { SignService } from 'vs/platform/sign/node/signService';
import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals';
import { IDisposable } from 'vs/base/common/lifecycle';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
import { createCancelablePromise, firstParallel } from 'vs/base/common/async';
export class ExtHostDebugService extends ExtHostDebugServiceBase {
readonly _serviceBrand: undefined;
private _integratedTerminalInstance?: vscode.Terminal;
private _integratedTerminalInstances = new DebugTerminalCollection();
private _terminalDisposedListener: IDisposable | undefined;
constructor(
@@ -74,25 +75,17 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
if (!this._terminalDisposedListener) {
// React on terminal disposed and check if that is the debug terminal #12956
this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => {
if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) {
this._integratedTerminalInstance = undefined;
}
this._integratedTerminalInstances.onTerminalClosed(terminal);
});
}
let needNewTerminal = true; // be pessimistic
if (this._integratedTerminalInstance) {
const pid = await this._integratedTerminalInstance.processId;
needNewTerminal = await hasChildProcesses(pid); // if no processes running in terminal reuse terminal
}
let terminal = await this._integratedTerminalInstances.checkout();
const configProvider = await this._configurationService.getConfigProvider();
const shell = this._terminalService.getDefaultShell(true, configProvider);
let cwdForPrepareCommand: string | undefined;
let giveShellTimeToInitialize = false;
if (needNewTerminal || !this._integratedTerminalInstance) {
if (!terminal) {
const options: vscode.TerminalOptions = {
shellPath: shell,
// shellArgs: this._terminalService._getDefaultShellArgs(configProvider),
@@ -100,17 +93,16 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
name: args.title || nls.localize('debug.terminal.title', "debuggee"),
};
giveShellTimeToInitialize = true;
this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options, true);
terminal = this._terminalService.createTerminalFromOptions(options, true);
this._integratedTerminalInstances.insert(terminal);
} else {
cwdForPrepareCommand = args.cwd;
}
const terminal = this._integratedTerminalInstance;
terminal.show();
const shellProcessId = await this._integratedTerminalInstance.processId;
const shellProcessId = await terminal.processId;
if (giveShellTimeToInitialize) {
// give a new terminal some time to initialize the shell
@@ -134,3 +126,41 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
}
}
class DebugTerminalCollection {
/**
* Delay before a new terminal is a candidate for reuse. See #71850
*/
private static minUseDelay = 1000;
private _terminalInstances = new Map<vscode.Terminal, number /* last used at */>();
public async checkout() {
const entries = [...this._terminalInstances.keys()];
const promises = entries.map((terminal) => createCancelablePromise(async ct => {
const pid = await terminal.processId;
if (await hasChildProcesses(pid)) {
return null;
}
// important: date check and map operations must be synchronous
const now = Date.now();
const usedAt = this._terminalInstances.get(terminal);
if (!usedAt || usedAt + DebugTerminalCollection.minUseDelay > now || ct.isCancellationRequested) {
return null;
}
this._terminalInstances.set(terminal, now);
return terminal;
}));
return await firstParallel(promises, (t): t is vscode.Terminal => !!t);
}
public insert(terminal: vscode.Terminal) {
this._terminalInstances.set(terminal, Date.now());
}
public onTerminalClosed(terminal: vscode.Terminal) {
this._terminalInstances.delete(terminal);
}
}