Skip to content

Commit b09a421

Browse files
authored
Speed up interpreter locators (#676)
performance improvements (fixes #666)
1 parent d596beb commit b09a421

21 files changed

+192
-89
lines changed

src/client/banner.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export class BannerService {
1717
if (!this.shouldShowBanner.value) {
1818
return;
1919
}
20-
this.shouldShowBanner.value = false;
20+
this.shouldShowBanner.updateValue(false)
21+
.catch(ex => console.error('Python Extension: Failed to update banner value', ex));
2122

2223
const message = 'The Python extension is now published by Microsoft!';
2324
const yesButton = 'Read more';

src/client/common/featureDeprecationManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class FeatureDeprecationManager implements IFeatureDeprecationManager {
112112
break;
113113
}
114114
case doNotShowAgain: {
115-
notificationPromptEnabled.value = false;
115+
await notificationPromptEnabled.updateValue(false);
116116
break;
117117
}
118118
default: {

src/client/common/persistentState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class PersistentState<T> implements IPersistentState<T>{
1414
return this.storage.get<T>(this.key, this.defaultValue);
1515
}
1616

17-
public set value(newValue: T) {
18-
this.storage.update(this.key, newValue);
17+
public async updateValue(newValue: T): Promise<void> {
18+
await this.storage.update(this.key, newValue);
1919
}
2020
}
2121

src/client/common/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
1414
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');
1515

1616
export interface IPersistentState<T> {
17-
value: T;
17+
readonly value: T;
18+
updateValue(value: T): Promise<void>;
1819
}
1920

2021
export const IPersistentStateFactory = Symbol('IPersistentStateFactory');

src/client/interpreter/configuration/interpreterSelector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ export class InterpreterSelector implements Disposable {
4343
if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) {
4444
detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`;
4545
}
46+
const cachedPrefix = suggestion.cachedEntry ? '(cached) ' : '';
4647
return {
4748
// tslint:disable-next-line:no-non-null-assertion
4849
label: suggestion.displayName!,
4950
description: suggestion.companyDisplayName || '',
50-
detail: detail,
51+
detail: `${cachedPrefix}${detail}`,
5152
path: suggestion.path
5253
};
5354
}

src/client/interpreter/contracts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export type PythonInterpreter = {
6262
type: InterpreterType;
6363
envName?: string;
6464
envPath?: string;
65+
cachedEntry?: boolean;
6566
};
6667

6768
export type WorkspacePythonPath = {

src/client/interpreter/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class InterpreterManager implements Disposable, IInterpreterService {
5353
}
5454
const virtualEnvMgr = this.serviceContainer.get<IVirtualEnvironmentManager>(IVirtualEnvironmentManager);
5555
const versionService = this.serviceContainer.get<IInterpreterVersionService>(IInterpreterVersionService);
56-
const virtualEnvInterpreterProvider = new VirtualEnvService([activeWorkspace.folderUri.fsPath], virtualEnvMgr, versionService);
56+
const virtualEnvInterpreterProvider = new VirtualEnvService([activeWorkspace.folderUri.fsPath], virtualEnvMgr, versionService, this.serviceContainer);
5757
const interpreters = await virtualEnvInterpreterProvider.getInterpreters(activeWorkspace.folderUri);
5858
const workspacePathUpper = activeWorkspace.folderUri.fsPath.toUpperCase();
5959

src/client/interpreter/locators/index.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { inject, injectable } from 'inversify';
22
import * as _ from 'lodash';
33
import * as path from 'path';
4-
import { Disposable, Uri, workspace } from 'vscode';
4+
import { Disposable, Uri } from 'vscode';
55
import { IPlatformService } from '../../common/platform/types';
66
import { IDisposableRegistry } from '../../common/types';
77
import { arePathsSame } from '../../common/utils';
@@ -21,35 +21,21 @@ import { fixInterpreterDisplayName } from './helpers';
2121

2222
@injectable()
2323
export class PythonInterpreterLocatorService implements IInterpreterLocatorService {
24-
private interpretersPerResource: Map<string, Promise<PythonInterpreter[]>>;
2524
private disposables: Disposable[] = [];
2625
private platform: IPlatformService;
2726

2827
constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) {
29-
this.interpretersPerResource = new Map<string, Promise<PythonInterpreter[]>>();
3028
serviceContainer.get<Disposable[]>(IDisposableRegistry).push(this);
3129
this.platform = serviceContainer.get<IPlatformService>(IPlatformService);
3230
}
3331
public async getInterpreters(resource?: Uri) {
34-
const resourceKey = this.getResourceKey(resource);
35-
if (!this.interpretersPerResource.has(resourceKey)) {
36-
this.interpretersPerResource.set(resourceKey, this.getInterpretersPerResource(resource));
37-
}
38-
39-
return await this.interpretersPerResource.get(resourceKey)!;
32+
return this.getInterpretersPerResource(resource);
4033
}
4134
public dispose() {
4235
this.disposables.forEach(disposable => disposable.dispose());
4336
}
44-
private getResourceKey(resource?: Uri) {
45-
if (!resource) {
46-
return '';
47-
}
48-
const workspaceFolder = workspace.getWorkspaceFolder(resource);
49-
return workspaceFolder ? workspaceFolder.uri.fsPath : '';
50-
}
5137
private async getInterpretersPerResource(resource?: Uri) {
52-
const locators = this.getLocators(resource);
38+
const locators = this.getLocators();
5339
const promises = locators.map(async provider => provider.getInterpreters(resource));
5440
const listOfInterpreters = await Promise.all(promises);
5541

@@ -73,7 +59,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
7359
return accumulator;
7460
}, []);
7561
}
76-
private getLocators(resource?: Uri) {
62+
private getLocators() {
7763
const locators: IInterpreterLocatorService[] = [];
7864
// The order of the services is important.
7965
if (this.platform.isWindows) {

src/client/interpreter/locators/services/KnownPathsService.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,26 @@ import * as _ from 'lodash';
33
import * as path from 'path';
44
import { Uri } from 'vscode';
55
import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils';
6-
import { IInterpreterLocatorService, IInterpreterVersionService, IKnownSearchPathsForInterpreters, InterpreterType } from '../../contracts';
6+
import { IServiceContainer } from '../../../ioc/types';
7+
import { IInterpreterVersionService, IKnownSearchPathsForInterpreters, InterpreterType, PythonInterpreter } from '../../contracts';
78
import { lookForInterpretersInDirectory } from '../helpers';
9+
import { CacheableLocatorService } from './cacheableLocatorService';
810

911
// tslint:disable-next-line:no-require-imports no-var-requires
1012
const untildify = require('untildify');
1113

1214
@injectable()
13-
export class KnownPathsService implements IInterpreterLocatorService {
15+
export class KnownPathsService extends CacheableLocatorService {
1416
public constructor( @inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: string[],
15-
@inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService) { }
16-
// tslint:disable-next-line:no-shadowed-variable
17-
public getInterpreters(_?: Uri) {
18-
return this.suggestionsFromKnownPaths();
17+
@inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService,
18+
@inject(IServiceContainer) serviceContainer: IServiceContainer) {
19+
super('KnownPathsService', serviceContainer);
1920
}
2021
// tslint:disable-next-line:no-empty
2122
public dispose() { }
23+
protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
24+
return this.suggestionsFromKnownPaths();
25+
}
2226
private suggestionsFromKnownPaths() {
2327
const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir));
2428
return Promise.all<string[]>(promises)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { injectable } from 'inversify';
5+
import { Uri } from 'vscode';
6+
import { createDeferred, Deferred } from '../../../common/helpers';
7+
import { IPersistentStateFactory } from '../../../common/types';
8+
import { IServiceContainer } from '../../../ioc/types';
9+
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
10+
11+
@injectable()
12+
export abstract class CacheableLocatorService implements IInterpreterLocatorService {
13+
private getInterpretersPromise: Deferred<PythonInterpreter[]>;
14+
private readonly cacheKey: string;
15+
constructor(name: string,
16+
protected readonly serviceContainer: IServiceContainer) {
17+
this.cacheKey = `INTERPRETERS_CACHE_${name}`;
18+
}
19+
public abstract dispose();
20+
public async getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {
21+
if (!this.getInterpretersPromise) {
22+
this.getInterpretersPromise = createDeferred<PythonInterpreter[]>();
23+
this.getInterpretersImplementation(resource)
24+
.then(async items => {
25+
await this.cacheInterpreters(items);
26+
this.getInterpretersPromise.resolve(items);
27+
})
28+
.catch(ex => this.getInterpretersPromise.reject(ex));
29+
}
30+
if (this.getInterpretersPromise.completed) {
31+
return this.getInterpretersPromise.promise;
32+
}
33+
34+
const cachedInterpreters = this.getCachedInterpreters();
35+
return Array.isArray(cachedInterpreters) ? cachedInterpreters : this.getInterpretersPromise.promise;
36+
}
37+
38+
protected abstract getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]>;
39+
40+
private getCachedInterpreters() {
41+
const persistentFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
42+
// tslint:disable-next-line:no-any
43+
const globalPersistence = persistentFactory.createGlobalPersistentState<PythonInterpreter[]>(this.cacheKey, undefined as any);
44+
if (!Array.isArray(globalPersistence.value)) {
45+
return;
46+
}
47+
return globalPersistence.value.map(item => {
48+
return {
49+
...item,
50+
cachedEntry: true
51+
};
52+
});
53+
}
54+
private async cacheInterpreters(interpreters: PythonInterpreter[]) {
55+
const persistentFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
56+
const globalPersistence = persistentFactory.createGlobalPersistentState<PythonInterpreter[]>(this.cacheKey, []);
57+
await globalPersistence.updateValue(interpreters);
58+
}
59+
}

0 commit comments

Comments
 (0)