Skip to content

Commit ea9b376

Browse files
Make sessionManager.start() idempotent (#4599)
So the debugger can just call `start()` to ensure the session has started. Also make a bunch of public methods private.
1 parent 6a28ee0 commit ea9b376

File tree

3 files changed

+52
-24
lines changed

3 files changed

+52
-24
lines changed

src/features/DebugSession.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { LanguageClientConsumer } from "../languageClientConsumer";
3030
import { ILogger } from "../logging";
3131
import { OperatingSystem, getPlatformDetails } from "../platform";
3232
import { PowerShellProcess } from "../process";
33-
import { IEditorServicesSessionDetails, SessionManager, SessionStatus } from "../session";
33+
import { IEditorServicesSessionDetails, SessionManager } from "../session";
3434
import { getSettings } from "../settings";
3535
import path from "path";
3636
import { checkIfFileExists } from "../utils";
@@ -281,9 +281,7 @@ export class DebugSessionFeature extends LanguageClientConsumer
281281
_executable: DebugAdapterExecutable | undefined): Promise<DebugAdapterDescriptor | undefined> {
282282
// NOTE: A Promise meets the shape of a ProviderResult, which allows us to make this method async.
283283

284-
if (this.sessionManager.getSessionStatus() !== SessionStatus.Running) {
285-
await this.sessionManager.start();
286-
}
284+
await this.sessionManager.start();
287285

288286
const sessionDetails = session.configuration.createTemporaryIntegratedConsole
289287
? await this.createTemporaryIntegratedConsole(session)

src/session.ts

+49-18
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import { LanguageClientConsumer } from "./languageClientConsumer";
2727
import { SemVer, satisfies } from "semver";
2828

29-
export enum SessionStatus {
29+
enum SessionStatus {
3030
NotStarted = "Not Started",
3131
Starting = "Starting",
3232
Running = "Running",
@@ -146,11 +146,34 @@ export class SessionManager implements Middleware {
146146
}
147147

148148
// The `exeNameOverride` is used by `restartSession` to override ANY other setting.
149+
// We've made this function idempotent, so it can used to ensure the session has started.
149150
public async start(exeNameOverride?: string): Promise<void> {
150-
// A simple lock because this function isn't re-entrant.
151-
if (this.sessionStatus === SessionStatus.Starting) {
151+
switch (this.sessionStatus) {
152+
case SessionStatus.NotStarted:
153+
// Go ahead and start.
154+
break;
155+
case SessionStatus.Starting:
156+
// A simple lock because this function isn't re-entrant.
152157
this.logger.writeWarning("Re-entered 'start' so waiting...");
153-
return await this.waitUntilStarted();
158+
return await this.waitWhileStarting();
159+
case SessionStatus.Running:
160+
// We're started, just return.
161+
this.logger.writeVerbose("Already started.");
162+
return;
163+
case SessionStatus.Busy:
164+
// We're started but busy so notify and return.
165+
// TODO: Make a proper notification for this and when IntelliSense is blocked.
166+
this.logger.write("The Extension Terminal is currently busy, please wait for your task to finish!");
167+
return;
168+
case SessionStatus.Stopping:
169+
// Wait until done stopping, then start.
170+
this.logger.writeVerbose("Still stopping.");
171+
await this.waitWhileStopping();
172+
break;
173+
case SessionStatus.Failed:
174+
// Try to start again.
175+
this.logger.writeVerbose("Previously failed, starting again.");
176+
break;
154177
}
155178

156179
this.setSessionStatus("Starting...", SessionStatus.Starting);
@@ -220,7 +243,7 @@ export class SessionManager implements Middleware {
220243
}
221244
}
222245

223-
public async stop(): Promise<void> {
246+
private async stop(): Promise<void> {
224247
this.setSessionStatus("Stopping...", SessionStatus.Stopping);
225248
// Cancel start-up if we're still waiting.
226249
this.startCancellationTokenSource?.cancel();
@@ -255,7 +278,7 @@ export class SessionManager implements Middleware {
255278
this.setSessionStatus("Not Started", SessionStatus.NotStarted);
256279
}
257280

258-
public async restartSession(exeNameOverride?: string): Promise<void> {
281+
private async restartSession(exeNameOverride?: string): Promise<void> {
259282
this.logger.write("Restarting session...");
260283
await this.stop();
261284

@@ -267,22 +290,18 @@ export class SessionManager implements Middleware {
267290
}
268291

269292
public getSessionDetails(): IEditorServicesSessionDetails | undefined {
270-
// TODO: This is used solely by the debugger and should actually just wait (with a timeout).
293+
// This is used by the debugger which should have already called `start`.
271294
if (this.sessionDetails === undefined) {
272295
void this.logger.writeAndShowError("PowerShell session unavailable for debugging!");
273296
}
274297
return this.sessionDetails;
275298
}
276299

277-
public getSessionStatus(): SessionStatus {
278-
return this.sessionStatus;
279-
}
280-
281300
public getPowerShellVersionDetails(): IPowerShellVersionDetails | undefined {
282301
return this.versionDetails;
283302
}
284303

285-
public getNewSessionFilePath(): vscode.Uri {
304+
private getNewSessionFilePath(): vscode.Uri {
286305
const uniqueId: number = Math.floor(100000 + Math.random() * 900000);
287306
return vscode.Uri.joinPath(this.sessionsFolder, `PSES-VSCode-${process.env.VSCODE_PID}-${uniqueId}.json`);
288307
}
@@ -334,14 +353,12 @@ export class SessionManager implements Middleware {
334353
}
335354

336355
public async waitUntilStarted(): Promise<void> {
337-
while (this.sessionStatus === SessionStatus.Starting) {
338-
if (this.startCancellationTokenSource?.token.isCancellationRequested) {
339-
return;
340-
}
356+
while (this.sessionStatus !== SessionStatus.Running) {
341357
await utils.sleep(300);
342358
}
343359
}
344360

361+
// TODO: Is this used by the magic of "Middleware" in the client library?
345362
public resolveCodeLens(
346363
codeLens: vscode.CodeLens,
347364
token: vscode.CancellationToken,
@@ -803,6 +820,21 @@ Type 'help' to get help.
803820
return languageStatusItem;
804821
}
805822

823+
private async waitWhileStarting(): Promise<void> {
824+
while (this.sessionStatus === SessionStatus.Starting) {
825+
if (this.startCancellationTokenSource?.token.isCancellationRequested) {
826+
return;
827+
}
828+
await utils.sleep(300);
829+
}
830+
}
831+
832+
private async waitWhileStopping(): Promise<void> {
833+
while (this.sessionStatus === SessionStatus.Stopping) {
834+
await utils.sleep(300);
835+
}
836+
}
837+
806838
private setSessionStatus(detail: string, status: SessionStatus): void {
807839
this.logger.writeVerbose(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
808840
this.sessionStatus = status;
@@ -842,7 +874,6 @@ Type 'help' to get help.
842874
this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Error;
843875
break;
844876
}
845-
846877
}
847878

848879
private setSessionRunningStatus(): void {
@@ -910,7 +941,7 @@ Type 'help' to get help.
910941
}
911942

912943
// Always shows the session terminal.
913-
public showSessionTerminal(isExecute?: boolean): void {
944+
private showSessionTerminal(isExecute?: boolean): void {
914945
this.languageServerProcess?.showTerminal(isExecute && !this.sessionSettings.integratedConsole.focusConsoleOnExecute);
915946
}
916947

test/features/DebugSession.test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { DebugConfig, DebugSessionFeature, DebugConfigurations } from "../../src
1010
import { IPowerShellExtensionClient } from "../../src/features/ExternalApi";
1111
import * as platform from "../../src/platform";
1212
import { IPlatformDetails } from "../../src/platform";
13-
import { IEditorServicesSessionDetails, IPowerShellVersionDetails, SessionManager, SessionStatus } from "../../src/session";
13+
import { IEditorServicesSessionDetails, IPowerShellVersionDetails, SessionManager } from "../../src/session";
1414
import * as utils from "../../src/utils";
1515
import { BuildBinaryModuleMock, WaitEvent, ensureEditorServicesIsConnected, stubInterface, testLogger } from "../utils";
1616

@@ -408,7 +408,6 @@ describe("DebugSessionFeature", () => {
408408
it("Creates a named pipe server for the debug adapter", async () => {
409409
const debugSessionFeature = createDebugSessionFeatureStub({
410410
sessionManager: Sinon.createStubInstance(SessionManager, {
411-
getSessionStatus: SessionStatus.Running,
412411
getSessionDetails: stubInterface<IEditorServicesSessionDetails>({
413412
debugServicePipeName: "testPipeName"
414413
})

0 commit comments

Comments
 (0)