Skip to content

Commit 1dec290

Browse files
committed
Make sure LSP server is waited until stopped.
1 parent 5ce9884 commit 1dec290

11 files changed

+88
-106
lines changed

src/client/activation/jedi/languageServerProxy.ts

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,8 @@ import { traceDecoratorError, traceDecoratorVerbose, traceError } from '../../lo
2323
export class JediLanguageServerProxy implements ILanguageServerProxy {
2424
public languageClient: LanguageClient | undefined;
2525

26-
private languageServerTask: Promise<void> | undefined;
27-
2826
private readonly disposables: Disposable[] = [];
2927

30-
private disposed = false;
31-
3228
private lsVersion: string | undefined;
3329

3430
constructor(
@@ -42,36 +38,9 @@ export class JediLanguageServerProxy implements ILanguageServerProxy {
4238
};
4339
}
4440

45-
@traceDecoratorVerbose('Stopping language server')
41+
@traceDecoratorVerbose('Disposing language server')
4642
public dispose(): void {
47-
if (this.languageClient) {
48-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
49-
const pid: number | undefined = ((this.languageClient as any)._serverProcess as ChildProcess)?.pid;
50-
const killServer = () => {
51-
if (pid) {
52-
killPid(pid);
53-
}
54-
};
55-
56-
// Do not await on this.
57-
this.languageClient.stop().then(
58-
() => killServer(),
59-
(ex) => {
60-
traceError('Stopping language client failed', ex);
61-
killServer();
62-
},
63-
);
64-
65-
this.languageClient = undefined;
66-
this.languageServerTask = undefined;
67-
}
68-
69-
while (this.disposables.length > 0) {
70-
const d = this.disposables.shift()!;
71-
d.dispose();
72-
}
73-
74-
this.disposed = true;
43+
this.stop().ignoreErrors();
7544
}
7645

7746
@traceDecoratorError('Failed to start language server')
@@ -87,19 +56,41 @@ export class JediLanguageServerProxy implements ILanguageServerProxy {
8756
interpreter: PythonEnvironment | undefined,
8857
options: LanguageClientOptions,
8958
): Promise<void> {
90-
if (this.languageServerTask) {
91-
await this.languageServerTask;
92-
return;
93-
}
94-
9559
this.lsVersion =
9660
(options.middleware ? (<LanguageClientMiddleware>options.middleware).serverVersion : undefined) ?? '0.19.3';
9761

9862
this.languageClient = await this.factory.createLanguageClient(resource, interpreter, options);
9963
this.registerHandlers();
10064

101-
this.languageServerTask = this.languageClient.start();
102-
await this.languageServerTask;
65+
await this.languageClient.start();
66+
}
67+
68+
@traceDecoratorVerbose('Stopping language server')
69+
public async stop(): Promise<void> {
70+
if (this.languageClient) {
71+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
72+
const pid: number | undefined = ((this.languageClient as any)._serverProcess as ChildProcess)?.pid;
73+
const killServer = () => {
74+
if (pid) {
75+
killPid(pid);
76+
}
77+
};
78+
79+
try {
80+
await this.languageClient.stop();
81+
killServer();
82+
} catch (ex) {
83+
traceError('Stopping language client failed', ex);
84+
killServer();
85+
}
86+
87+
this.languageClient = undefined;
88+
}
89+
90+
while (this.disposables.length > 0) {
91+
const d = this.disposables.shift()!;
92+
d.dispose();
93+
}
10394
}
10495

10596
// eslint-disable-next-line class-methods-use-this
@@ -115,11 +106,6 @@ export class JediLanguageServerProxy implements ILanguageServerProxy {
115106
JediLanguageServerProxy.versionTelemetryProps,
116107
)
117108
private registerHandlers() {
118-
if (this.disposed) {
119-
// Check if it got disposed in the interim.
120-
return;
121-
}
122-
123109
const progressReporting = new ProgressReporting(this.languageClient!);
124110
this.disposables.push(progressReporting);
125111

src/client/activation/jedi/manager.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ export class JediLanguageServerManager implements ILanguageServerManager {
5959
}
6060

6161
public dispose(): void {
62-
if (this.languageProxy) {
63-
this.languageProxy.dispose();
64-
}
62+
this.stopLanguageServer().ignoreErrors();
6563
JediLanguageServerManager.commandDispose.dispose();
6664
this.disposables.forEach((d) => d.dispose());
6765
}
@@ -120,9 +118,7 @@ export class JediLanguageServerManager implements ILanguageServerManager {
120118
@traceDecoratorError('Failed to restart language server')
121119
@traceDecoratorVerbose('Restarting language server')
122120
protected async restartLanguageServer(): Promise<void> {
123-
if (this.languageProxy) {
124-
this.languageProxy.dispose();
125-
}
121+
await this.stopLanguageServer();
126122
await this.startLanguageServer();
127123
}
128124

@@ -147,4 +143,11 @@ export class JediLanguageServerManager implements ILanguageServerManager {
147143
// Then use this middleware to start a new language client.
148144
await this.languageServerProxy.start(this.resource, this.interpreter, options);
149145
}
146+
147+
@traceDecoratorVerbose('Stopping language server')
148+
protected async stopLanguageServer(): Promise<void> {
149+
if (this.languageServerProxy) {
150+
await this.languageServerProxy.stop();
151+
}
152+
}
150153
}

src/client/activation/node/languageServerProxy.ts

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from 'vscode-languageclient/node';
1111

1212
import { IExperimentService, IExtensions, IInterpreterPathService, Resource } from '../../common/types';
13-
import { noop } from '../../common/utils/misc';
1413
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
1514
import { PythonEnvironment } from '../../pythonEnvironments/info';
1615
import { captureTelemetry } from '../../telemetry';
@@ -51,14 +50,10 @@ namespace GetExperimentValue {
5150
export class NodeLanguageServerProxy implements ILanguageServerProxy {
5251
public languageClient: LanguageClient | undefined;
5352

54-
private languageServerTask: Promise<void> | undefined;
55-
5653
private cancellationStrategy: FileBasedCancellationStrategy | undefined;
5754

5855
private readonly disposables: Disposable[] = [];
5956

60-
private disposed = false;
61-
6257
private lsVersion: string | undefined;
6358

6459
constructor(
@@ -76,24 +71,9 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
7671
};
7772
}
7873

79-
@traceDecoratorVerbose('Stopping language server')
74+
@traceDecoratorVerbose('Disposing language server')
8075
public dispose(): void {
81-
if (this.languageClient) {
82-
// Do not await on this.
83-
this.languageClient.stop().then(noop, (ex) => traceError('Stopping language client failed', ex));
84-
85-
this.languageClient = undefined;
86-
this.languageServerTask = undefined;
87-
}
88-
if (this.cancellationStrategy) {
89-
this.cancellationStrategy.dispose();
90-
this.cancellationStrategy = undefined;
91-
}
92-
while (this.disposables.length > 0) {
93-
const d = this.disposables.shift()!;
94-
d.dispose();
95-
}
96-
this.disposed = true;
76+
this.stop().ignoreErrors();
9777
}
9878

9979
@traceDecoratorError('Failed to start language server')
@@ -109,11 +89,6 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
10989
interpreter: PythonEnvironment | undefined,
11090
options: LanguageClientOptions,
11191
): Promise<void> {
112-
if (this.languageServerTask) {
113-
await this.languageServerTask;
114-
return;
115-
}
116-
11792
const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID);
11893
this.lsVersion = extension?.packageJSON.version || '0';
11994

@@ -129,8 +104,27 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
129104
}),
130105
);
131106

132-
this.languageServerTask = this.languageClient.start();
133-
await this.languageServerTask;
107+
await this.languageClient.start();
108+
}
109+
110+
@traceDecoratorVerbose('Disposing language server')
111+
public async stop(): Promise<void> {
112+
if (this.languageClient) {
113+
try {
114+
await this.languageClient.stop();
115+
} catch (ex) {
116+
traceError('Stopping language client failed', ex);
117+
}
118+
this.languageClient = undefined;
119+
}
120+
if (this.cancellationStrategy) {
121+
this.cancellationStrategy.dispose();
122+
this.cancellationStrategy = undefined;
123+
}
124+
while (this.disposables.length > 0) {
125+
const d = this.disposables.shift()!;
126+
d.dispose();
127+
}
134128
}
135129

136130
// eslint-disable-next-line class-methods-use-this
@@ -146,11 +140,6 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
146140
NodeLanguageServerProxy.versionTelemetryProps,
147141
)
148142
private registerHandlers(_resource: Resource) {
149-
if (this.disposed) {
150-
// Check if it got disposed in the interim.
151-
return;
152-
}
153-
154143
const progressReporting = new ProgressReporting(this.languageClient!);
155144
this.disposables.push(progressReporting);
156145

src/client/activation/node/manager.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ export class NodeLanguageServerManager implements ILanguageServerManager {
5959
}
6060

6161
public dispose(): void {
62-
if (this.languageProxy) {
63-
this.languageProxy.dispose();
64-
}
62+
this.stopLanguageServer().ignoreErrors();
6563
NodeLanguageServerManager.commandDispose.dispose();
6664
this.disposables.forEach((d) => d.dispose());
6765
}
@@ -110,9 +108,7 @@ export class NodeLanguageServerManager implements ILanguageServerManager {
110108
@traceDecoratorError('Failed to restart language server')
111109
@traceDecoratorVerbose('Restarting language server')
112110
protected async restartLanguageServer(): Promise<void> {
113-
if (this.languageProxy) {
114-
this.languageProxy.dispose();
115-
}
111+
await this.stopLanguageServer();
116112
await this.startLanguageServer();
117113
}
118114

@@ -137,4 +133,11 @@ export class NodeLanguageServerManager implements ILanguageServerManager {
137133
// Then use this middleware to start a new language client.
138134
await this.languageServerProxy.start(this.resource, this.interpreter, options);
139135
}
136+
137+
@traceDecoratorVerbose('Stopping language server')
138+
protected async stopLanguageServer(): Promise<void> {
139+
if (this.languageServerProxy) {
140+
await this.languageServerProxy.stop();
141+
}
142+
}
140143
}

src/client/activation/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export interface ILanguageServerProxy extends IDisposable {
138138
interpreter: PythonEnvironment | undefined,
139139
options: LanguageClientOptions,
140140
): Promise<void>;
141+
stop(): Promise<void>;
141142
/**
142143
* Sends a request to LS so as to load other extensions.
143144
* This is used as a plugin loader mechanism.

src/client/languageServer/jediLSExtensionManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ export class JediLSExtensionManager extends LanguageServerCapabilities
7272
this.serverManager.connect();
7373
}
7474

75-
stopLanguageServer(): void {
75+
async stopLanguageServer(): Promise<void> {
7676
this.serverManager.disconnect();
77-
this.serverProxy.dispose();
77+
await this.serverProxy.stop();
7878
}
7979

8080
// eslint-disable-next-line class-methods-use-this

src/client/languageServer/noneLSExtensionManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export class NoneLSExtensionManager implements ILanguageServer, ILanguageServerE
4646
return Promise.resolve();
4747
}
4848

49-
stopLanguageServer(): void {
50-
// Nothing to do here.
49+
stopLanguageServer(): Promise<void> {
50+
return Promise.resolve();
5151
}
5252

5353
canStartLanguageServer(): boolean {

src/client/languageServer/pylanceLSExtensionManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ export class PylanceLSExtensionManager extends LanguageServerCapabilities
8484
this.serverManager.connect();
8585
}
8686

87-
stopLanguageServer(): void {
87+
async stopLanguageServer(): Promise<void> {
8888
this.serverManager.disconnect();
89-
this.serverProxy.dispose();
89+
await this.serverProxy.stop();
9090
}
9191

9292
canStartLanguageServer(): boolean {

src/client/languageServer/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface ILanguageServerCapabilities extends ILanguageServer {
2929
*/
3030
export interface ILanguageServerExtensionManager extends ILanguageServerCapabilities {
3131
startLanguageServer(resource: Resource, interpreter?: PythonEnvironment): Promise<void>;
32-
stopLanguageServer(): void;
32+
stopLanguageServer(): Promise<void>;
3333
canStartLanguageServer(): boolean;
3434
languageServerNotAvailable(): Promise<void>;
3535
dispose(): void;

src/client/languageServer/watcher.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class LanguageServerWatcher
125125

126126
// Destroy the old language server if it's different.
127127
if (currentInterpreter && interpreter !== currentInterpreter) {
128-
this.stopLanguageServer(lsResource);
128+
await this.stopLanguageServer(lsResource);
129129
}
130130

131131
// If the interpreter is Python 2 and the LS setting is explicitly set to Jedi, turn it off.
@@ -190,12 +190,12 @@ export class LanguageServerWatcher
190190

191191
// Private methods
192192

193-
private stopLanguageServer(resource?: Resource): void {
193+
private async stopLanguageServer(resource?: Resource): Promise<void> {
194194
const key = this.getWorkspaceKey(resource, this.languageServerType);
195195
const languageServerExtensionManager = this.workspaceLanguageServers.get(key);
196196

197197
if (languageServerExtensionManager) {
198-
languageServerExtensionManager.stopLanguageServer();
198+
await languageServerExtensionManager.stopLanguageServer();
199199
languageServerExtensionManager.dispose();
200200
this.workspaceLanguageServers.delete(key);
201201
}
@@ -246,7 +246,7 @@ export class LanguageServerWatcher
246246
const languageServerType = this.configurationService.getSettings(lsResource).languageServer;
247247

248248
if (languageServerType !== this.languageServerType) {
249-
this.stopLanguageServer(resource);
249+
await this.stopLanguageServer(resource);
250250
await this.startLanguageServer(languageServerType, lsResource);
251251
}
252252
}
@@ -286,9 +286,9 @@ export class LanguageServerWatcher
286286
// Since Jedi is the only language server type where we instantiate multiple language servers,
287287
// Make sure to dispose of them only in that scenario.
288288
if (event.removed.length && this.languageServerType === LanguageServerType.Jedi) {
289-
event.removed.forEach((workspace) => {
290-
this.stopLanguageServer(workspace.uri);
291-
});
289+
for (const workspace of event.removed) {
290+
await this.stopLanguageServer(workspace.uri);
291+
}
292292
}
293293
}
294294

src/test/languageServer/watcher.unit.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ suite('Language server watcher', () => {
723723
}
724724

725725
const stopLanguageServerStub = sandbox.stub(extensionLSCls.prototype, 'stopLanguageServer');
726-
stopLanguageServerStub.returns();
726+
stopLanguageServerStub.returns(Promise.resolve());
727727

728728
let onDidChangeWorkspaceFoldersListener: (event: WorkspaceFoldersChangeEvent) => Promise<void> = () =>
729729
Promise.resolve();

0 commit comments

Comments
 (0)