Skip to content

Commit c2e0430

Browse files
authored
Performance improvements to load times of extension (#931)
* Improve pip env interpreter load times * Remove fs checks (improvements from ~2.5s to under 200ms) Fixes #929
1 parent 2a04ce9 commit c2e0430

File tree

3 files changed

+15
-56
lines changed

3 files changed

+15
-56
lines changed

src/client/interpreter/display/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class InterpreterDisplay implements IInterpreterDisplay {
6767
await Promise.all([
6868
this.fileSystem.fileExistsAsync(pythonPath),
6969
this.versionProvider.getVersion(pythonPath, defaultDisplayName),
70-
this.getVirtualEnvironmentName(pythonPath)
70+
this.getVirtualEnvironmentName(pythonPath).catch(() => '')
7171
])
7272
.then(([interpreterExists, displayName, virtualEnvName]) => {
7373
const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : '';

src/client/interpreter/interpreterService.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as path from 'path';
33
import { ConfigurationTarget, Disposable, Event, EventEmitter, Uri } from 'vscode';
44
import { IDocumentManager, IWorkspaceService } from '../common/application/types';
55
import { PythonSettings } from '../common/configSettings';
6-
import { IFileSystem } from '../common/platform/types';
76
import { IPythonExecutionFactory } from '../common/process/types';
87
import { IConfigurationService, IDisposableRegistry } from '../common/types';
98
import * as utils from '../common/utils';
@@ -21,13 +20,11 @@ export class InterpreterService implements Disposable, IInterpreterService {
2120
private readonly locator: IInterpreterLocatorService;
2221
private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager;
2322
private readonly helper: IInterpreterHelper;
24-
private readonly fs: IFileSystem;
2523
private readonly didChangeInterpreterEmitter = new EventEmitter<void>();
2624

2725
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
2826
this.locator = serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE);
2927
this.helper = serviceContainer.get<IInterpreterHelper>(IInterpreterHelper);
30-
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
3128
this.pythonPathUpdaterService = this.serviceContainer.get<IPythonPathUpdaterServiceManager>(IPythonPathUpdaterServiceManager);
3229
}
3330

@@ -134,21 +131,14 @@ export class InterpreterService implements Disposable, IInterpreterService {
134131
return false;
135132
}
136133
if (activeWorkspace.configTarget === ConfigurationTarget.Workspace) {
137-
return !await this.isPythonPathDefined(pythonPathInConfig!.workspaceValue);
134+
return pythonPathInConfig!.workspaceValue === undefined || pythonPathInConfig!.workspaceValue === 'python';
138135
}
139136
if (activeWorkspace.configTarget === ConfigurationTarget.WorkspaceFolder) {
140-
return !await this.isPythonPathDefined(pythonPathInConfig!.workspaceFolderValue);
137+
return pythonPathInConfig!.workspaceFolderValue === undefined || pythonPathInConfig!.workspaceFolderValue === 'python';
141138
}
142139
return false;
143140
}
144141

145-
private async isPythonPathDefined(pythonPath: string | undefined): Promise<boolean> {
146-
if (pythonPath === undefined || pythonPath === 'python') {
147-
return false;
148-
}
149-
return await this.fs.directoryExistsAsync(pythonPath);
150-
}
151-
152142
private onConfigChanged = () => {
153143
this.didChangeInterpreterEmitter.fire();
154144
const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);

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

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,48 @@ import { inject, injectable } from 'inversify';
55
import * as path from 'path';
66
import { Uri } from 'vscode';
77
import { IApplicationShell, IWorkspaceService } from '../../../common/application/types';
8-
import { createDeferred, Deferred } from '../../../common/helpers';
98
import { IFileSystem } from '../../../common/platform/types';
109
import { IProcessService } from '../../../common/process/types';
1110
import { getPythonExecutable } from '../../../debugger/Common/Utils';
1211
import { IServiceContainer } from '../../../ioc/types';
13-
import { IInterpreterLocatorService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts';
12+
import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts';
13+
import { CacheableLocatorService } from './cacheableLocatorService';
1414

1515
const execName = 'pipenv';
16-
const CACHE_TIMEOUT = 2000;
1716

1817
@injectable()
19-
export class PipEnvService implements IInterpreterLocatorService {
18+
export class PipEnvService extends CacheableLocatorService {
2019
private readonly versionService: IInterpreterVersionService;
2120
private readonly process: IProcessService;
2221
private readonly workspace: IWorkspaceService;
2322
private readonly fs: IFileSystem;
2423

25-
private pendingPromises: Deferred<PythonInterpreter[]>[] = [];
26-
private readonly cachedInterpreters = new Map<string, PythonInterpreter>();
27-
28-
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
24+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
25+
super('PipEnvService', serviceContainer);
2926
this.versionService = this.serviceContainer.get<IInterpreterVersionService>(IInterpreterVersionService);
3027
this.process = this.serviceContainer.get<IProcessService>(IProcessService);
3128
this.workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
3229
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
3330
}
34-
35-
public getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {
31+
// tslint:disable-next-line:no-empty
32+
public dispose() { }
33+
protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
3634
const pipenvCwd = this.getPipenvWorkingDirectory(resource);
3735
if (!pipenvCwd) {
3836
return Promise.resolve([]);
3937
}
4038

41-
// Try cache first
42-
const interpreter = this.cachedInterpreters[pipenvCwd];
43-
if (interpreter) {
44-
return Promise.resolve([interpreter]);
45-
}
46-
// We don't want multiple requests executing pipenv
47-
const deferred = createDeferred<PythonInterpreter[]>();
48-
this.pendingPromises.push(deferred);
49-
if (this.pendingPromises.length === 1) {
50-
// First call, start worker
51-
this.getInterpreter(pipenvCwd)
52-
.then(x => this.resolveDeferred(x ? [x] : []))
53-
.catch(e => this.resolveDeferred([]));
54-
}
55-
return deferred.promise;
56-
}
57-
58-
public dispose() {
59-
this.resolveDeferred([]);
60-
}
61-
62-
private resolveDeferred(result: PythonInterpreter[]) {
63-
this.pendingPromises.forEach(p => p.resolve(result));
64-
this.pendingPromises = [];
65-
}
66-
67-
private async getInterpreter(pipenvCwd: string): Promise<PythonInterpreter | undefined> {
68-
const interpreter = await this.getInterpreterFromPipenv(pipenvCwd);
69-
if (interpreter) {
70-
this.cachedInterpreters[pipenvCwd] = interpreter;
71-
setTimeout(() => this.cachedInterpreters.clear(), CACHE_TIMEOUT);
72-
}
73-
return interpreter;
39+
return this.getInterpreterFromPipenv(pipenvCwd)
40+
.then(item => item ? [item] : [])
41+
.catch(() => []);
7442
}
7543

7644
private async getInterpreterFromPipenv(pipenvCwd: string): Promise<PythonInterpreter | undefined> {
7745
const interpreterPath = await this.getInterpreterPathFromPipenv(pipenvCwd);
7846
if (!interpreterPath) {
7947
return;
8048
}
49+
8150
const pythonExecutablePath = getPythonExecutable(interpreterPath);
8251
const ver = await this.versionService.getVersion(pythonExecutablePath, '');
8352
return {

0 commit comments

Comments
 (0)