Skip to content

Commit 8dad2cf

Browse files
committed
Make sessionManager.start() idempotent
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 8dad2cf

File tree

3 files changed

+51
-24
lines changed

3 files changed

+51
-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

+48-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,33 @@ 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+
void this.logger.writeAndShowInformation("The Extension Terminal is currently busy, please wait for your task to finish!");
166+
return;
167+
case SessionStatus.Stopping:
168+
// Wait until done stopping, then start.
169+
this.logger.writeVerbose("Still stopping.");
170+
await this.waitWhileStopping();
171+
break;
172+
case SessionStatus.Failed:
173+
// Try to start again.
174+
this.logger.writeVerbose("Previously failed, starting again.");
175+
break;
154176
}
155177

156178
this.setSessionStatus("Starting...", SessionStatus.Starting);
@@ -220,7 +242,7 @@ export class SessionManager implements Middleware {
220242
}
221243
}
222244

223-
public async stop(): Promise<void> {
245+
private async stop(): Promise<void> {
224246
this.setSessionStatus("Stopping...", SessionStatus.Stopping);
225247
// Cancel start-up if we're still waiting.
226248
this.startCancellationTokenSource?.cancel();
@@ -255,7 +277,7 @@ export class SessionManager implements Middleware {
255277
this.setSessionStatus("Not Started", SessionStatus.NotStarted);
256278
}
257279

258-
public async restartSession(exeNameOverride?: string): Promise<void> {
280+
private async restartSession(exeNameOverride?: string): Promise<void> {
259281
this.logger.write("Restarting session...");
260282
await this.stop();
261283

@@ -267,22 +289,18 @@ export class SessionManager implements Middleware {
267289
}
268290

269291
public getSessionDetails(): IEditorServicesSessionDetails | undefined {
270-
// TODO: This is used solely by the debugger and should actually just wait (with a timeout).
292+
// This is used by the debugger which should have already called `start`.
271293
if (this.sessionDetails === undefined) {
272294
void this.logger.writeAndShowError("PowerShell session unavailable for debugging!");
273295
}
274296
return this.sessionDetails;
275297
}
276298

277-
public getSessionStatus(): SessionStatus {
278-
return this.sessionStatus;
279-
}
280-
281299
public getPowerShellVersionDetails(): IPowerShellVersionDetails | undefined {
282300
return this.versionDetails;
283301
}
284302

285-
public getNewSessionFilePath(): vscode.Uri {
303+
private getNewSessionFilePath(): vscode.Uri {
286304
const uniqueId: number = Math.floor(100000 + Math.random() * 900000);
287305
return vscode.Uri.joinPath(this.sessionsFolder, `PSES-VSCode-${process.env.VSCODE_PID}-${uniqueId}.json`);
288306
}
@@ -334,14 +352,12 @@ export class SessionManager implements Middleware {
334352
}
335353

336354
public async waitUntilStarted(): Promise<void> {
337-
while (this.sessionStatus === SessionStatus.Starting) {
338-
if (this.startCancellationTokenSource?.token.isCancellationRequested) {
339-
return;
340-
}
355+
while (this.sessionStatus !== SessionStatus.Running) {
341356
await utils.sleep(300);
342357
}
343358
}
344359

360+
// TODO: Is this used by the magic of "Middleware" in the client library?
345361
public resolveCodeLens(
346362
codeLens: vscode.CodeLens,
347363
token: vscode.CancellationToken,
@@ -803,6 +819,21 @@ Type 'help' to get help.
803819
return languageStatusItem;
804820
}
805821

822+
private async waitWhileStarting(): Promise<void> {
823+
while (this.sessionStatus === SessionStatus.Starting) {
824+
if (this.startCancellationTokenSource?.token.isCancellationRequested) {
825+
return;
826+
}
827+
await utils.sleep(300);
828+
}
829+
}
830+
831+
private async waitWhileStopping(): Promise<void> {
832+
while (this.sessionStatus === SessionStatus.Stopping) {
833+
await utils.sleep(300);
834+
}
835+
}
836+
806837
private setSessionStatus(detail: string, status: SessionStatus): void {
807838
this.logger.writeVerbose(`Session status changing from '${this.sessionStatus}' to '${status}'.`);
808839
this.sessionStatus = status;
@@ -842,7 +873,6 @@ Type 'help' to get help.
842873
this.languageStatusItem.severity = vscode.LanguageStatusSeverity.Error;
843874
break;
844875
}
845-
846876
}
847877

848878
private setSessionRunningStatus(): void {
@@ -910,7 +940,7 @@ Type 'help' to get help.
910940
}
911941

912942
// Always shows the session terminal.
913-
public showSessionTerminal(isExecute?: boolean): void {
943+
private showSessionTerminal(isExecute?: boolean): void {
914944
this.languageServerProcess?.showTerminal(isExecute && !this.sessionSettings.integratedConsole.focusConsoleOnExecute);
915945
}
916946

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)