Skip to content

Commit c3b17a3

Browse files
authored
wait for terminal reconnection to try to reconnect tasks (microsoft#158391)
1 parent 47bb748 commit c3b17a3

File tree

6 files changed

+120
-46
lines changed

6 files changed

+120
-46
lines changed

src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
88
import { Emitter, Event } from 'vs/base/common/event';
99
import * as glob from 'vs/base/common/glob';
1010
import * as json from 'vs/base/common/json';
11-
import { Disposable, IDisposable, IReference } from 'vs/base/common/lifecycle';
11+
import { Disposable, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
1212
import { LRUCache, Touch } from 'vs/base/common/map';
1313
import * as Objects from 'vs/base/common/objects';
1414
import { ValidationState, ValidationStatus } from 'vs/base/common/parsers';
@@ -53,7 +53,7 @@ import { ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSyst
5353
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
5454

5555
import * as TaskConfig from '../common/taskConfiguration';
56-
import { TerminalTaskSystem } from './terminalTaskSystem';
56+
import { terminalsNotReconnectedExitCode, TerminalTaskSystem } from './terminalTaskSystem';
5757

5858
import { IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickSeparator, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
5959

@@ -181,6 +181,11 @@ class TaskMap {
181181
}
182182
}
183183

184+
interface EventBarrier {
185+
isOpen: boolean;
186+
queuedEvent?: boolean;
187+
}
188+
184189
export abstract class AbstractTaskService extends Disposable implements ITaskService {
185190

186191
// private static autoDetectTelemetryName: string = 'taskServer.autoDetect';
@@ -195,6 +200,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
195200

196201
private static _nextHandle: number = 0;
197202

203+
private _reconnectionBarrier: EventBarrier = { isOpen: true };
204+
198205
private _tasksReconnected: boolean = false;
199206
private _schemaVersion: JsonSchemaVersion | undefined;
200207
private _executionEngine: ExecutionEngine | undefined;
@@ -209,7 +216,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
209216
protected _workspaceTasksPromise?: Promise<Map<string, IWorkspaceFolderTaskResult>>;
210217

211218
protected _taskSystem?: ITaskSystem;
212-
protected _taskSystemListener?: IDisposable;
219+
protected _taskSystemListeners?: IDisposable[] = [];
213220
private _recentlyUsedTasksV1: LRUCache<string, string> | undefined;
214221
private _recentlyUsedTasks: LRUCache<string, string> | undefined;
215222

@@ -219,6 +226,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
219226

220227
protected _outputChannel: IOutputChannel;
221228
protected readonly _onDidStateChange: Emitter<ITaskEvent>;
229+
protected readonly _onDidReconnectToTerminals: Emitter<void> = new Emitter();
222230
private _waitForSupportedExecutions: Promise<void>;
223231
private _onDidRegisterSupportedExecutions: Emitter<void> = new Emitter();
224232
private _onDidChangeTaskSystemInfo: Emitter<void> = new Emitter();
@@ -265,7 +273,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
265273

266274
this._workspaceTasksPromise = undefined;
267275
this._taskSystem = undefined;
268-
this._taskSystemListener = undefined;
276+
this._taskSystemListeners = undefined;
269277
this._outputChannel = this._outputService.getChannel(AbstractTaskService.OutputChannelId)!;
270278
this._providers = new Map<number, ITaskProvider>();
271279
this._providerTypes = new Map<number, string>();
@@ -283,6 +291,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
283291
if (!this._taskSystem && !this._workspaceTasksPromise) {
284292
return;
285293
}
294+
286295
if (!this._taskSystem || this._taskSystem instanceof TerminalTaskSystem) {
287296
this._outputChannel.clear();
288297
}
@@ -328,6 +337,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
328337
this._waitForSupportedExecutions = new Promise(resolve => {
329338
once(this._onDidRegisterSupportedExecutions.event)(() => resolve());
330339
});
340+
this._register(this.onDidReconnectToTerminals(async () => await this._attemptTaskReconnection()));
341+
this._register(this._onDidRegisterSupportedExecutions.event(async () => await this._attemptTaskReconnection()));
331342
this._upgrade();
332343
}
333344

@@ -346,43 +357,65 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
346357
processContext.set(process && !isVirtual);
347358
}
348359
this._onDidRegisterSupportedExecutions.fire();
349-
if (this._configurationService.getValue(TaskSettingId.Reconnection) === true && this._jsonTasksSupported && !this._tasksReconnected) {
350-
this._reconnectTasks();
360+
}
361+
362+
private async _attemptTaskReconnection(): Promise<void> {
363+
if (!this._reconnectionBarrier.isOpen) {
364+
this._reconnectionBarrier.queuedEvent = true;
365+
return;
366+
}
367+
this._reconnectionBarrier.isOpen = false;
368+
if (!this._taskSystem) {
369+
this._logService.info('getting task system before reconnection');
370+
await this._getTaskSystem();
371+
}
372+
this._logService.info(`attempting task reconnection, jsonTasksSupported: ${this._jsonTasksSupported}, reconnection pending ${!this._tasksReconnected}`);
373+
if (this._configurationService.getValue(TaskSettingId.Reconnection) === true && !this._tasksReconnected) {
374+
this._tasksReconnected = await this._reconnectTasks();
375+
}
376+
this._reconnectionBarrier.isOpen = true;
377+
if (this._reconnectionBarrier.queuedEvent) {
378+
this._reconnectionBarrier.queuedEvent = undefined;
379+
await this._attemptTaskReconnection();
351380
}
352381
}
353382

354-
private async _reconnectTasks(): Promise<void> {
383+
private async _reconnectTasks(): Promise<boolean> {
355384
if (this._lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
356385
this._tasksReconnected = true;
357386
this._storageService.remove(AbstractTaskService.PersistentTasks_Key, StorageScope.WORKSPACE);
358-
return;
387+
return true;
359388
}
360389
const tasks = await this.getSavedTasks('persistent');
361-
if (!this._taskSystem) {
362-
await this._getTaskSystem();
363-
}
364390
if (!tasks.length) {
365-
this._tasksReconnected = true;
366-
return;
391+
return true;
367392
}
368-
369393
for (const task of tasks) {
394+
let result;
370395
if (ConfiguringTask.is(task)) {
371396
const resolved = await this.tryResolveTask(task);
372397
if (resolved) {
373-
this.run(resolved, undefined, TaskRunSource.Reconnect);
398+
result = await this.run(resolved, undefined, TaskRunSource.Reconnect);
374399
}
375400
} else {
376-
this.run(task, undefined, TaskRunSource.Reconnect);
401+
result = await this.run(task, undefined, TaskRunSource.Reconnect);
402+
}
403+
if (result?.exitCode === terminalsNotReconnectedExitCode) {
404+
this._logService.trace('Terminals were not reconnected');
405+
return false;
377406
}
378407
}
379-
this._tasksReconnected = true;
408+
return true;
380409
}
381410

382411
public get onDidStateChange(): Event<ITaskEvent> {
383412
return this._onDidStateChange.event;
384413
}
385414

415+
public get onDidReconnectToTerminals(): Event<void> {
416+
return this._onDidReconnectToTerminals.event;
417+
}
418+
386419
public get supportsMultipleTaskExecutions(): boolean {
387420
return this.inTerminal();
388421
}
@@ -607,7 +640,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
607640
}
608641

609642
protected _disposeTaskSystemListeners(): void {
610-
this._taskSystemListener?.dispose();
643+
if (this._taskSystemListeners) {
644+
dispose(this._taskSystemListeners);
645+
this._taskSystemListeners = undefined;
646+
}
611647
}
612648

613649
public registerTaskProvider(provider: ITaskProvider, type: string): IDisposable {

src/vs/workbench/contrib/tasks/browser/taskService.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ export class TaskService extends AbstractTaskService {
2323
} else {
2424
throw new Error(TaskService.ProcessTaskSystemSupportMessage);
2525
}
26-
this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => {
27-
if (this._taskSystem) {
28-
this._taskRunningState.set(this._taskSystem.isActiveSync());
29-
}
30-
this._onDidStateChange.fire(event);
31-
});
32-
return this._taskSystem!;
26+
const taskSystem = this._createTerminalTaskSystem();
27+
this._taskSystem = taskSystem;
28+
this._taskSystemListeners =
29+
[
30+
taskSystem.onDidStateChange((event) => {
31+
this._taskRunningState.set(taskSystem.isActiveSync());
32+
this._onDidStateChange.fire(event);
33+
}),
34+
taskSystem.onDidReconnectToTerminals((event) => {
35+
this._taskRunningState.set(taskSystem.isActiveSync());
36+
this._onDidReconnectToTerminals.fire(event);
37+
})
38+
];
39+
return this._taskSystem;
3340
}
3441

3542
protected _computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<IWorkspaceFolderConfigurationResult> {

src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { Codicon } from 'vs/base/common/codicons';
2828
import { Schemas } from 'vs/base/common/network';
2929
import { URI } from 'vs/base/common/uri';
3030
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
31-
import { ILogService } from 'vs/platform/log/common/log';
31+
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
3232
import { INotificationService } from 'vs/platform/notification/common/notification';
3333
import { IShellLaunchConfig, TerminalLocation, TerminalSettingId, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
3434
import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
@@ -119,6 +119,8 @@ class VariableResolver {
119119
}
120120
}
121121

122+
export const terminalsNotReconnectedExitCode = 7777;
123+
122124
export class VerifiedTask {
123125
readonly task: Task;
124126
readonly resolver: ITaskResolver;
@@ -210,6 +212,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
210212
private _terminalCreationQueue: Promise<ITerminalInstance | void> = Promise.resolve();
211213
private _hasReconnected: boolean = false;
212214
private readonly _onDidStateChange: Emitter<ITaskEvent>;
215+
private readonly _onDidReconnectToTerminals: Emitter<void> = new Emitter();
213216
private _reconnectedTerminals: ITerminalInstance[] | undefined;
214217

215218
get taskShellIntegrationStartSequence(): string {
@@ -252,15 +255,17 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
252255
this._onDidStateChange = new Emitter();
253256
this._taskSystemInfoResolver = taskSystemInfoResolver;
254257
this._register(this._terminalStatusManager = new TaskTerminalStatus(taskService));
255-
// connection state changes before this is created sometimes
256-
this._reconnectToTerminals();
257258
this._register(this._terminalService.onDidChangeConnectionState(() => this._reconnectToTerminals()));
258259
}
259260

260261
public get onDidStateChange(): Event<ITaskEvent> {
261262
return this._onDidStateChange.event;
262263
}
263264

265+
public get onDidReconnectToTerminals(): Event<void> {
266+
return this._onDidReconnectToTerminals.event;
267+
}
268+
264269
private _log(value: string): void {
265270
this._appendOutput(value + '\n');
266271
}
@@ -270,8 +275,15 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
270275
}
271276

272277
public reconnect(task: Task, resolver: ITaskResolver): ITaskExecuteResult {
273-
if (!this._reconnectedTerminals?.length) {
274-
throw new Error('Persistent tasks were not updated correctly - no terminals for reconnection');
278+
if (!this._reconnectedTerminals) {
279+
// terminalService.onDidChangeConnectionState might have already fired
280+
// before this gets created
281+
this._logService.trace('Reconnecting to terminals before running');
282+
this._reconnectToTerminals();
283+
if (!this._reconnectedTerminals) {
284+
this._logService.trace('Returning, terminals have not been reconnected yet');
285+
return { kind: TaskExecuteKind.Started, promise: Promise.resolve({ exitCode: terminalsNotReconnectedExitCode }), task } as ITaskExecuteResult;
286+
}
275287
}
276288
return this.run(task, resolver, Triggers.reconnect);
277289
}
@@ -1327,21 +1339,32 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
13271339

13281340
private _reconnectToTerminals(): void {
13291341
if (this._hasReconnected) {
1342+
this._logService.trace(`Already reconnected, to ${this._reconnectedTerminals?.length} terminals so returning`);
13301343
return;
13311344
}
13321345
this._reconnectedTerminals = this._terminalService.getReconnectedTerminals(ReconnectionType)?.filter(t => !t.isDisposed);
1333-
if (!this._reconnectedTerminals?.length) {
1346+
this._logService.trace(`Attempting reconnection of ${this._reconnectedTerminals?.length} terminals`);
1347+
if (!this._reconnectedTerminals) {
1348+
this._hasReconnected = false;
1349+
} else if (!this._reconnectedTerminals?.length) {
1350+
this._logService.trace(`No terminals to reconnect to so returning`);
1351+
this._hasReconnected = true;
13341352
return;
1335-
}
1336-
for (const terminal of this._reconnectedTerminals) {
1337-
const task = terminal.shellLaunchConfig.attachPersistentProcess?.reconnectionProperties?.data as IReconnectionTaskData;
1338-
if (!task) {
1339-
continue;
1353+
} else {
1354+
for (const terminal of this._reconnectedTerminals) {
1355+
const task = terminal.shellLaunchConfig.attachPersistentProcess?.reconnectionProperties?.data as IReconnectionTaskData;
1356+
if (this._logService.getLevel() <= LogLevel.Trace) {
1357+
this._logService.trace(`Reconnecting to task: ${JSON.stringify(task)}`);
1358+
}
1359+
if (!task) {
1360+
continue;
1361+
}
1362+
const terminalData = { lastTask: task.lastTask, group: task.group, terminal };
1363+
this._terminals[terminal.instanceId] = terminalData;
13401364
}
1341-
const terminalData = { lastTask: task.lastTask, group: task.group, terminal };
1342-
this._terminals[terminal.instanceId] = terminalData;
1365+
this._hasReconnected = true;
1366+
this._onDidReconnectToTerminals.fire();
13431367
}
1344-
this._hasReconnected = true;
13451368
}
13461369

13471370
private _deleteTaskAndTerminal(terminal: ITerminalInstance, terminalData: ITerminalData): void {

src/vs/workbench/contrib/tasks/common/taskSystem.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export interface ITaskSystemInfoResolver {
102102

103103
export interface ITaskSystem {
104104
onDidStateChange: Event<ITaskEvent>;
105+
onDidReconnectToTerminals: Event<void>;
105106
reconnect(task: Task, resolver: ITaskResolver): ITaskExecuteResult;
106107
run(task: Task, resolver: ITaskResolver): ITaskExecuteResult;
107108
rerun(): ITaskExecuteResult | undefined;

src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,19 @@ export class TaskService extends AbstractTaskService {
131131
if (this._taskSystem) {
132132
return this._taskSystem;
133133
}
134-
this._taskSystem = this._createTerminalTaskSystem();
135-
this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => {
136-
if (this._taskSystem) {
137-
this._taskRunningState.set(this._taskSystem.isActiveSync());
138-
}
139-
this._onDidStateChange.fire(event);
140-
});
134+
const taskSystem = this._createTerminalTaskSystem();
135+
this._taskSystem = taskSystem;
136+
this._taskSystemListeners =
137+
[
138+
this._taskSystem.onDidStateChange((event) => {
139+
this._taskRunningState.set(this._taskSystem!.isActiveSync());
140+
this._onDidStateChange.fire(event);
141+
}),
142+
this._taskSystem.onDidReconnectToTerminals((event) => {
143+
this._taskRunningState.set(this._taskSystem!.isActiveSync());
144+
this._onDidReconnectToTerminals.fire(event);
145+
})
146+
];
141147
return this._taskSystem;
142148
}
143149

src/vs/workbench/contrib/terminal/browser/terminalService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ export class TerminalService implements ITerminalService {
383383
private _setConnected() {
384384
this._connectionState = TerminalConnectionState.Connected;
385385
this._onDidChangeConnectionState.fire();
386+
this._logService.trace('Reconnected to terminals');
386387
}
387388

388389
private async _reconnectToRemoteTerminals(): Promise<void> {

0 commit comments

Comments
 (0)