Skip to content

Commit 0beff82

Browse files
committed
Fix incorrect parser errors from unloaded assemblies
An attempt to fix #5381 where we believe the cause is that the didOpen()/didChange() notifications are being sent before PowerShell has totally finished loading. Since these run the PowerShell parser on the OmniSharp thread pool, they essentially race the analysis of the files by PSScriptAnalyzer on the PowerShell runspace pool. So when they beat it, the return errors. But when they're beaten by it, the PSSA analysis has caused the assemblies (and so custom attributes) to be loaded, no longer erroring. I posited we could gate the notifications instead of duplicating them like in #5402, and if the `Middleware` works as suspected by me (and Claude) this should fix it. Morever, we now also use a proper `Promise` instead of a while loop around a sleep to wait for the LSP server to be running. While all of this could just be an AI hallucination...it seems right.
1 parent f9e4670 commit 0beff82

File tree

1 file changed

+34
-3
lines changed

1 file changed

+34
-3
lines changed

src/session.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ export class SessionManager implements Middleware {
126126
private versionDetails: IPowerShellVersionDetails | undefined;
127127
private traceLogLevelHandler?: vscode.Disposable;
128128

129+
// Promise-based gate resolved when the session reaches Running status.
130+
// Used by waitUntilStarted() and the didOpen()/didChange() notifications.
131+
private startedResolver!: () => void;
132+
private started: Promise<void> = this.createStartedGate();
133+
129134
constructor(
130135
private extensionContext: vscode.ExtensionContext,
131136
private sessionSettings: Settings,
@@ -295,6 +300,7 @@ export class SessionManager implements Middleware {
295300
`Started PowerShell v${this.versionDetails.version}.`,
296301
);
297302
this.setSessionRunningStatus(); // Yay, we made it!
303+
this.startedResolver(); // Release didOpen()/didChange() notifications and waitUntilStarted() gate
298304

299305
await this.writePidIfInDevMode(this.languageServerProcess);
300306

@@ -328,6 +334,7 @@ export class SessionManager implements Middleware {
328334
}
329335

330336
this.languageClient = undefined;
337+
this.started = this.createStartedGate();
331338

332339
// Stop and dispose the PowerShell process(es).
333340
this.debugSessionProcess?.dispose();
@@ -497,9 +504,33 @@ export class SessionManager implements Middleware {
497504
}
498505

499506
public async waitUntilStarted(): Promise<void> {
500-
while (this.sessionStatus !== SessionStatus.Running) {
501-
await utils.sleep(200);
502-
}
507+
await this.started;
508+
}
509+
510+
// Middleware hooks to delay document sync notifications until the server
511+
// is fully initialized. This prevents stale parser diagnostics (e.g.
512+
// unresolved custom attribute types) that would otherwise appear because
513+
// textDocument/didOpen is sent before the server's type resolution is ready.
514+
public async didOpen(
515+
document: vscode.TextDocument,
516+
next: (document: vscode.TextDocument) => Promise<void>,
517+
): Promise<void> {
518+
await this.started;
519+
return next(document);
520+
}
521+
522+
public async didChange(
523+
event: vscode.TextDocumentChangeEvent,
524+
next: (event: vscode.TextDocumentChangeEvent) => Promise<void>,
525+
): Promise<void> {
526+
await this.started;
527+
return next(event);
528+
}
529+
530+
private createStartedGate(): Promise<void> {
531+
return new Promise<void>((resolve) => {
532+
this.startedResolver = resolve;
533+
});
503534
}
504535

505536
// TODO: Is this used by the magic of "Middleware" in the client library?

0 commit comments

Comments
 (0)