Skip to content

Commit cbcaebf

Browse files
authored
Ensure extension does not start multiple instances of LS at a time (#3974)
For #3346
1 parent b84be55 commit cbcaebf

43 files changed

Lines changed: 1860 additions & 878 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/codecov.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ coverage:
22
precision: 0
33
round: up
44
range: "70...90"
5+
status:
6+
project: off
7+
comment:
8+
layout: "diff, flags"
9+
behavior: default

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@
2222
"python.linting.enabled": false,
2323
"python.unitTest.promptToConfigure": false,
2424
"python.workspaceSymbols.enabled": false,
25-
"python.formatting.provider": "none"
25+
"python.formatting.provider": "none",
26+
"typescript.preferences.quoteStyle": "single",
27+
"javascript.preferences.quoteStyle": "single",
28+
"typescriptHero.imports.stringQuoteStyle": "'"
2629
}

news/2 Fixes/3346.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure extension does not start multiple language servers.

package-lock.json

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1542,7 +1542,7 @@
15421542
"type": "string",
15431543
"default": "pipenv",
15441544
"description": "Path to the pipenv executable to use for activation.",
1545-
"scope": "window"
1545+
"scope": "resource"
15461546
},
15471547
"python.sortImports.args": {
15481548
"type": "array",
@@ -1929,6 +1929,7 @@
19291929
"@types/semver": "^5.5.0",
19301930
"@types/shortid": "^0.0.29",
19311931
"@types/sinon": "^4.3.0",
1932+
"@types/stack-trace": "0.0.29",
19321933
"@types/temp": "^0.8.32",
19331934
"@types/tmp": "0.0.33",
19341935
"@types/untildify": "^3.0.0",
@@ -1987,6 +1988,7 @@
19871988
"relative": "^3.0.2",
19881989
"remap-istanbul": "^0.10.1",
19891990
"retyped-diff-match-patch-tsd-ambient": "^1.0.0-0",
1991+
"rewiremock": "^3.13.0",
19901992
"shortid": "^2.2.8",
19911993
"source-map-support": "^0.5.9",
19921994
"style-loader": "^0.23.1",

src/client/activation/activationService.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class ExtensionActivationService implements IExtensionActivationService,
5858
if (!jedi) {
5959
const diagnostic = await this.lsNotSupportedDiagnosticService.diagnose();
6060
this.lsNotSupportedDiagnosticService.handle(diagnostic).ignoreErrors();
61-
if (diagnostic.length){
61+
if (diagnostic.length) {
6262
sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_PLATFORM_NOT_SUPPORTED);
6363
jedi = true;
6464
}
@@ -70,8 +70,13 @@ export class ExtensionActivationService implements IExtensionActivationService,
7070
let activator = this.serviceContainer.get<IExtensionActivator>(IExtensionActivator, activatorName);
7171
this.currentActivator = { jedi, activator };
7272

73-
const success = await activator.activate();
74-
if (!success && !jedi) {
73+
try {
74+
await activator.activate();
75+
return;
76+
} catch (ex) {
77+
if (jedi) {
78+
return;
79+
}
7580
//Language server fails, reverting to jedi
7681
jedi = true;
7782
await this.logStartup(jedi);
@@ -84,7 +89,7 @@ export class ExtensionActivationService implements IExtensionActivationService,
8489

8590
public dispose() {
8691
if (this.currentActivator) {
87-
this.currentActivator.activator.deactivate().ignoreErrors();
92+
this.currentActivator.activator.dispose();
8893
}
8994
}
9095

src/client/activation/downloader.ts

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,42 @@
33

44
'use strict';
55

6+
import { inject, injectable, named } from 'inversify';
67
import * as path from 'path';
78
import { ProgressLocation, window } from 'vscode';
89
import { IApplicationShell } from '../common/application/types';
910
import { STANDARD_OUTPUT_CHANNEL } from '../common/constants';
1011
import { IFileSystem } from '../common/platform/types';
11-
import { IExtensionContext, IOutputChannel } from '../common/types';
12+
import { IOutputChannel } from '../common/types';
1213
import { createDeferred } from '../common/utils/async';
1314
import { LanguageService } from '../common/utils/localize';
1415
import { StopWatch } from '../common/utils/stopWatch';
15-
import { IServiceContainer } from '../ioc/types';
1616
import { sendTelemetryEvent } from '../telemetry';
1717
import {
1818
PYTHON_LANGUAGE_SERVER_DOWNLOADED,
1919
PYTHON_LANGUAGE_SERVER_ERROR,
2020
PYTHON_LANGUAGE_SERVER_EXTRACTED
2121
} from '../telemetry/constants';
22-
import {
23-
IHttpClient, ILanguageServerDownloader, ILanguageServerFolderService,
24-
ILanguageServerPlatformData
25-
} from './types';
22+
import { IHttpClient, ILanguageServerDownloader, ILanguageServerFolderService, IPlatformData } from './types';
2623

2724
const downloadFileExtension = '.nupkg';
2825

26+
@injectable()
2927
export class LanguageServerDownloader implements ILanguageServerDownloader {
30-
private readonly output: IOutputChannel;
31-
private readonly fs: IFileSystem;
32-
private readonly appShell: IApplicationShell;
3328
constructor(
34-
private readonly platformData: ILanguageServerPlatformData,
35-
private readonly engineFolder: string,
36-
private readonly serviceContainer: IServiceContainer
29+
@inject(IPlatformData) private readonly platformData: IPlatformData,
30+
@inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel,
31+
@inject(IHttpClient) private readonly httpClient: IHttpClient,
32+
@inject(ILanguageServerFolderService) private readonly lsFolderService: ILanguageServerFolderService,
33+
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
34+
@inject(IFileSystem) private readonly fs: IFileSystem
3735
) {
38-
this.output = this.serviceContainer.get<IOutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
39-
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
40-
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
41-
4236
}
4337

4438
public async getDownloadInfo() {
45-
const lsFolderService = this.serviceContainer.get<ILanguageServerFolderService>(ILanguageServerFolderService);
46-
return lsFolderService.getLatestLanguageServerVersion().then(item => item!);
39+
return this.lsFolderService.getLatestLanguageServerVersion().then(item => item!);
4740
}
48-
49-
public async downloadLanguageServer(context: IExtensionContext): Promise<void> {
41+
public async downloadLanguageServer(destinationFolder: string): Promise<void> {
5042
const downloadInfo = await this.getDownloadInfo();
5143
const downloadUri = downloadInfo.uri;
5244
const lsVersion = downloadInfo.version.raw;
@@ -61,7 +53,7 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
6153
this.output.appendLine(err);
6254
success = false;
6355
this.appShell.showErrorMessage(LanguageService.lsFailedToDownload());
64-
sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ERROR, undefined, { error: 'Failed to download (platform)' });
56+
sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ERROR, undefined, { error: 'Failed to download (platform)' }, err);
6557
throw new Error(err);
6658
} finally {
6759
sendTelemetryEvent(
@@ -73,13 +65,13 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
7365

7466
timer.reset();
7567
try {
76-
await this.unpackArchive(context.extensionPath, localTempFilePath);
68+
await this.unpackArchive(destinationFolder, localTempFilePath);
7769
} catch (err) {
7870
this.output.appendLine('extraction failed.');
7971
this.output.appendLine(err);
8072
success = false;
8173
this.appShell.showErrorMessage(LanguageService.lsFailedToExtract());
82-
sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ERROR, undefined, { error: 'Failed to extract (platform)' });
74+
sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ERROR, undefined, { error: 'Failed to extract (platform)' }, err);
8375
throw new Error(err);
8476
} finally {
8577
sendTelemetryEvent(
@@ -107,11 +99,12 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
10799
await window.withProgress({
108100
location: ProgressLocation.Window
109101
}, async (progress) => {
110-
const httpClient = this.serviceContainer.get<IHttpClient>(IHttpClient);
111-
const req = await httpClient.downloadFile(uri);
102+
const req = await this.httpClient.downloadFile(uri);
112103
req.on('response', (response) => {
113104
if (response.statusCode !== 200) {
114-
throw new Error(`Failed with status ${response.statusCode}, ${response.statusMessage}, Uri ${uri}`);
105+
const error = new Error(`Failed with status ${response.statusCode}, ${response.statusMessage}, Uri ${uri}`);
106+
deferred.reject(error);
107+
throw error;
115108
}
116109
});
117110
const requestProgress = await import('request-progress');
@@ -139,10 +132,9 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
139132
return tempFile.filePath;
140133
}
141134

142-
protected async unpackArchive(extensionPath: string, tempFilePath: string): Promise<void> {
135+
protected async unpackArchive(destinationFolder: string, tempFilePath: string): Promise<void> {
143136
this.output.append('Unpacking archive... ');
144137

145-
const installFolder = path.join(extensionPath, this.engineFolder);
146138
const deferred = createDeferred();
147139

148140
const title = 'Extracting files... ';
@@ -160,10 +152,10 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
160152
let extractedFiles = 0;
161153
zip.on('ready', async () => {
162154
totalFiles = zip.entriesCount;
163-
if (!await this.fs.directoryExists(installFolder)) {
164-
await this.fs.createDirectory(installFolder);
155+
if (!await this.fs.directoryExists(destinationFolder)) {
156+
await this.fs.createDirectory(destinationFolder);
165157
}
166-
zip.extract(null, installFolder, (err) => {
158+
zip.extract(null, destinationFolder, (err) => {
167159
if (err) {
168160
deferred.reject(err);
169161
} else {
@@ -181,7 +173,7 @@ export class LanguageServerDownloader implements ILanguageServerDownloader {
181173
});
182174

183175
// Set file to executable (nothing happens in Windows, as chmod has no definition there)
184-
const executablePath = path.join(installFolder, this.platformData.getEngineExecutableName());
176+
const executablePath = path.join(destinationFolder, this.platformData.engineExecutableName);
185177
await this.fs.chmod(executablePath, '0764'); // -rwxrw-r--
186178

187179
this.output.appendLine('done.');

src/client/activation/hashVerifier.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)