Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c0bf1b7

Browse filesBrowse files
author
Kartik Raj
authored
Improve time taken to trigger language server startup once extension activation is triggered (#22514)
For #22146 Improves time taken to trigger language server startup once extension activation is triggered - Do not block discovery on windows registry - Do not blocking auto-selection on validation of all interpreters - Make Windows Path locator faster
1 parent 20c1a10 commit c0bf1b7
Copy full SHA for c0bf1b7

File tree

Expand file treeCollapse file tree

25 files changed

+283
-128
lines changed
Filter options
Expand file treeCollapse file tree

25 files changed

+283
-128
lines changed

‎src/client/activation/activationManager.ts

Copy file name to clipboardExpand all lines: src/client/activation/activationManager.ts
+5-4Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { PYTHON_LANGUAGE } from '../common/constants';
1111
import { IFileSystem } from '../common/platform/types';
1212
import { IDisposable, IInterpreterPathService, Resource } from '../common/types';
1313
import { Deferred } from '../common/utils/async';
14+
import { StopWatch } from '../common/utils/stopWatch';
1415
import { IInterpreterAutoSelectionService } from '../interpreter/autoSelection/types';
1516
import { traceDecoratorError } from '../logging';
1617
import { sendActivationTelemetry } from '../telemetry/envFileTelemetry';
@@ -69,20 +70,20 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
6970
}
7071
}
7172

72-
public async activate(): Promise<void> {
73+
public async activate(startupStopWatch: StopWatch): Promise<void> {
7374
this.filterServices();
7475
await this.initialize();
7576

7677
// Activate all activation services together.
7778

7879
await Promise.all([
7980
...this.singleActivationServices.map((item) => item.activate()),
80-
this.activateWorkspace(this.activeResourceService.getActiveResource()),
81+
this.activateWorkspace(this.activeResourceService.getActiveResource(), startupStopWatch),
8182
]);
8283
}
8384

8485
@traceDecoratorError('Failed to activate a workspace')
85-
public async activateWorkspace(resource: Resource): Promise<void> {
86+
public async activateWorkspace(resource: Resource, startupStopWatch?: StopWatch): Promise<void> {
8687
const folder = this.workspaceService.getWorkspaceFolder(resource);
8788
resource = folder ? folder.uri : undefined;
8889
const key = this.getWorkspaceKey(resource);
@@ -97,7 +98,7 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
9798
await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource);
9899
}
99100
await sendActivationTelemetry(this.fileSystem, this.workspaceService, resource);
100-
await Promise.all(this.activationServices.map((item) => item.activate(resource)));
101+
await Promise.all(this.activationServices.map((item) => item.activate(resource, startupStopWatch)));
101102
await this.appDiagnostics.performPreStartupHealthCheck(resource);
102103
}
103104

‎src/client/activation/types.ts

Copy file name to clipboardExpand all lines: src/client/activation/types.ts
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Event } from 'vscode';
77
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node';
88
import type { IDisposable, ILogOutputChannel, Resource } from '../common/types';
9+
import { StopWatch } from '../common/utils/stopWatch';
910
import { PythonEnvironment } from '../pythonEnvironments/info';
1011

1112
export const IExtensionActivationManager = Symbol('IExtensionActivationManager');
@@ -23,7 +24,7 @@ export interface IExtensionActivationManager extends IDisposable {
2324
* @returns {Promise<void>}
2425
* @memberof IExtensionActivationManager
2526
*/
26-
activate(): Promise<void>;
27+
activate(startupStopWatch: StopWatch): Promise<void>;
2728
/**
2829
* Method invoked when a workspace is loaded.
2930
* This is where we place initialization scripts for each workspace.
@@ -47,7 +48,7 @@ export const IExtensionActivationService = Symbol('IExtensionActivationService')
4748
*/
4849
export interface IExtensionActivationService {
4950
supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean };
50-
activate(resource: Resource): Promise<void>;
51+
activate(resource: Resource, startupStopWatch?: StopWatch): Promise<void>;
5152
}
5253

5354
export enum LanguageServerType {

‎src/client/extension.ts

Copy file name to clipboardExpand all lines: src/client/extension.ts
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ async function activateUnsafe(
110110
const activationDeferred = createDeferred<void>();
111111
displayProgress(activationDeferred.promise);
112112
startupDurations.startActivateTime = startupStopWatch.elapsedTime;
113+
const activationStopWatch = new StopWatch();
113114

114115
//===============================================
115116
// activation starts here
@@ -127,7 +128,7 @@ async function activateUnsafe(
127128
const components = await initializeComponents(ext);
128129

129130
// Then we finish activating.
130-
const componentsActivated = await activateComponents(ext, components);
131+
const componentsActivated = await activateComponents(ext, components, activationStopWatch);
131132
activateFeatures(ext, components);
132133

133134
const nonBlocking = componentsActivated.map((r) => r.fullyReady);

‎src/client/extensionActivation.ts

Copy file name to clipboardExpand all lines: src/client/extensionActivation.ts
+5-3Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ import { registerCreateEnvironmentTriggers } from './pythonEnvironments/creation
5151
import { initializePersistentStateForTriggers } from './common/persistentState';
5252
import { logAndNotifyOnLegacySettings } from './logging/settingLogs';
5353
import { DebuggerTypeName } from './debugger/constants';
54+
import { StopWatch } from './common/utils/stopWatch';
5455

5556
export async function activateComponents(
5657
// `ext` is passed to any extra activation funcs.
5758
ext: ExtensionState,
5859
components: Components,
60+
startupStopWatch: StopWatch,
5961
): Promise<ActivationResult[]> {
6062
// Note that each activation returns a promise that resolves
6163
// when that activation completes. However, it might have started
@@ -73,7 +75,7 @@ export async function activateComponents(
7375
// activate them in parallel with the other components.
7476
// https://github.com/microsoft/vscode-python/issues/15380
7577
// These will go away eventually once everything is refactored into components.
76-
const legacyActivationResult = await activateLegacy(ext);
78+
const legacyActivationResult = await activateLegacy(ext, startupStopWatch);
7779
const workspaceService = new WorkspaceService();
7880
if (!workspaceService.isTrusted) {
7981
return [legacyActivationResult];
@@ -105,7 +107,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
105107
// init and activation: move them to activateComponents().
106108
// See https://github.com/microsoft/vscode-python/issues/10454.
107109

108-
async function activateLegacy(ext: ExtensionState): Promise<ActivationResult> {
110+
async function activateLegacy(ext: ExtensionState, startupStopWatch: StopWatch): Promise<ActivationResult> {
109111
const { legacyIOC } = ext;
110112
const { serviceManager, serviceContainer } = legacyIOC;
111113

@@ -183,7 +185,7 @@ async function activateLegacy(ext: ExtensionState): Promise<ActivationResult> {
183185
const manager = serviceContainer.get<IExtensionActivationManager>(IExtensionActivationManager);
184186
disposables.push(manager);
185187

186-
const activationPromise = manager.activate();
188+
const activationPromise = manager.activate(startupStopWatch);
187189

188190
return { fullyReady: activationPromise };
189191
}

‎src/client/interpreter/autoSelection/index.ts

Copy file name to clipboardExpand all lines: src/client/interpreter/autoSelection/index.ts
+36-11Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import { inject, injectable } from 'inversify';
77
import { Event, EventEmitter, Uri } from 'vscode';
88
import { IWorkspaceService } from '../../common/application/types';
9+
import { DiscoveryUsingWorkers } from '../../common/experiments/groups';
910
import '../../common/extensions';
1011
import { IFileSystem } from '../../common/platform/types';
11-
import { IPersistentState, IPersistentStateFactory, Resource } from '../../common/types';
12+
import { IExperimentService, IPersistentState, IPersistentStateFactory, Resource } from '../../common/types';
1213
import { createDeferred, Deferred } from '../../common/utils/async';
1314
import { compareSemVerLikeVersions } from '../../pythonEnvironments/base/info/pythonVersion';
15+
import { ProgressReportStage } from '../../pythonEnvironments/base/locator';
1416
import { PythonEnvironment } from '../../pythonEnvironments/info';
1517
import { sendTelemetryEvent } from '../../telemetry';
1618
import { EventName } from '../../telemetry/constants';
@@ -44,6 +46,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
4446
@inject(IInterpreterComparer) private readonly envTypeComparer: IInterpreterComparer,
4547
@inject(IInterpreterAutoSelectionProxyService) proxy: IInterpreterAutoSelectionProxyService,
4648
@inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper,
49+
@inject(IExperimentService) private readonly experimentService: IExperimentService,
4750
) {
4851
proxy.registerInstance!(this);
4952
}
@@ -183,7 +186,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
183186

184187
private getAutoSelectionQueriedOnceState(): IPersistentState<boolean | undefined> {
185188
const key = `autoSelectionInterpretersQueriedOnce`;
186-
return this.stateFactory.createWorkspacePersistentState(key, undefined);
189+
return this.stateFactory.createGlobalPersistentState(key, undefined);
187190
}
188191

189192
/**
@@ -199,22 +202,44 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
199202
private async autoselectInterpreterWithLocators(resource: Resource): Promise<void> {
200203
// Do not perform a full interpreter search if we already have cached interpreters for this workspace.
201204
const queriedState = this.getAutoSelectionInterpretersQueryState(resource);
202-
if (queriedState.value !== true && resource) {
205+
const globalQueriedState = this.getAutoSelectionQueriedOnceState();
206+
if (globalQueriedState.value && queriedState.value !== true && resource) {
203207
await this.interpreterService.triggerRefresh({
204208
searchLocations: { roots: [resource], doNotIncludeNonRooted: true },
205209
});
206210
}
207211

208-
const globalQueriedState = this.getAutoSelectionQueriedOnceState();
209-
if (!globalQueriedState.value) {
210-
// Global interpreters are loaded the first time an extension loads, after which we don't need to
211-
// wait on global interpreter promise refresh.
212-
await this.interpreterService.refreshPromise;
213-
}
214-
const interpreters = this.interpreterService.getInterpreters(resource);
212+
const inExperiment = this.experimentService.inExperimentSync(DiscoveryUsingWorkers.experiment);
215213
const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);
214+
let recommendedInterpreter: PythonEnvironment | undefined;
215+
if (inExperiment) {
216+
if (!globalQueriedState.value) {
217+
// Global interpreters are loaded the first time an extension loads, after which we don't need to
218+
// wait on global interpreter promise refresh.
219+
// Do not wait for validation of all interpreters to finish, we only need to validate the recommended interpreter.
220+
await this.interpreterService.getRefreshPromise({ stage: ProgressReportStage.allPathsDiscovered });
221+
}
222+
let interpreters = this.interpreterService.getInterpreters(resource);
223+
224+
recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri);
225+
const details = recommendedInterpreter
226+
? await this.interpreterService.getInterpreterDetails(recommendedInterpreter.path)
227+
: undefined;
228+
if (!details || !recommendedInterpreter) {
229+
await this.interpreterService.refreshPromise; // Interpreter is invalid, wait for all of validation to finish.
230+
interpreters = this.interpreterService.getInterpreters(resource);
231+
recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri);
232+
}
233+
} else {
234+
if (!globalQueriedState.value) {
235+
// Global interpreters are loaded the first time an extension loads, after which we don't need to
236+
// wait on global interpreter promise refresh.
237+
await this.interpreterService.refreshPromise;
238+
}
239+
const interpreters = this.interpreterService.getInterpreters(resource);
216240

217-
const recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri);
241+
recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri);
242+
}
218243
if (!recommendedInterpreter) {
219244
return;
220245
}

‎src/client/interpreter/contracts.ts

Copy file name to clipboardExpand all lines: src/client/interpreter/contracts.ts
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FileChangeType } from '../common/platform/fileSystemWatcher';
44
import { Resource } from '../common/types';
55
import { PythonEnvSource } from '../pythonEnvironments/base/info';
66
import {
7+
GetRefreshEnvironmentsOptions,
78
ProgressNotificationEvent,
89
PythonLocatorQuery,
910
TriggerRefreshOptions,
@@ -22,7 +23,7 @@ export const IComponentAdapter = Symbol('IComponentAdapter');
2223
export interface IComponentAdapter {
2324
readonly onProgress: Event<ProgressNotificationEvent>;
2425
triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise<void>;
25-
getRefreshPromise(): Promise<void> | undefined;
26+
getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise<void> | undefined;
2627
readonly onChanged: Event<PythonEnvironmentsChangedEvent>;
2728
// VirtualEnvPrompt
2829
onDidCreate(resource: Resource, callback: () => void): Disposable;
@@ -74,6 +75,7 @@ export const IInterpreterService = Symbol('IInterpreterService');
7475
export interface IInterpreterService {
7576
triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise<void>;
7677
readonly refreshPromise: Promise<void> | undefined;
78+
getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise<void> | undefined;
7779
readonly onDidChangeInterpreters: Event<PythonEnvironmentsChangedEvent>;
7880
onDidChangeInterpreterConfiguration: Event<Uri | undefined>;
7981
onDidChangeInterpreter: Event<Uri | undefined>;

‎src/client/interpreter/interpreterService.ts

Copy file name to clipboardExpand all lines: src/client/interpreter/interpreterService.ts
+9-1Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ import { Interpreters } from '../common/utils/localize';
3838
import { sendTelemetryEvent } from '../telemetry';
3939
import { EventName } from '../telemetry/constants';
4040
import { cache } from '../common/utils/decorators';
41-
import { PythonLocatorQuery, TriggerRefreshOptions } from '../pythonEnvironments/base/locator';
41+
import {
42+
GetRefreshEnvironmentsOptions,
43+
PythonLocatorQuery,
44+
TriggerRefreshOptions,
45+
} from '../pythonEnvironments/base/locator';
4246
import { sleep } from '../common/utils/async';
4347

4448
type StoredPythonEnvironment = PythonEnvironment & { store?: boolean };
@@ -59,6 +63,10 @@ export class InterpreterService implements Disposable, IInterpreterService {
5963
return this.pyenvs.getRefreshPromise();
6064
}
6165

66+
public getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise<void> | undefined {
67+
return this.pyenvs.getRefreshPromise(options);
68+
}
69+
6270
public get onDidChangeInterpreter(): Event<Uri | undefined> {
6371
return this.didChangeInterpreterEmitter.event;
6472
}

‎src/client/languageServer/watcher.ts

Copy file name to clipboardExpand all lines: src/client/languageServer/watcher.ts
+14-4Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { PylanceLSExtensionManager } from './pylanceLSExtensionManager';
2929
import { ILanguageServerExtensionManager, ILanguageServerWatcher } from './types';
3030
import { sendTelemetryEvent } from '../telemetry';
3131
import { EventName } from '../telemetry/constants';
32+
import { StopWatch } from '../common/utils/stopWatch';
3233

3334
@injectable()
3435
/**
@@ -73,14 +74,18 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang
7374

7475
// IExtensionActivationService
7576

76-
public async activate(resource?: Resource): Promise<void> {
77+
public async activate(resource?: Resource, startupStopWatch?: StopWatch): Promise<void> {
7778
this.register();
78-
await this.startLanguageServer(this.languageServerType, resource);
79+
await this.startLanguageServer(this.languageServerType, resource, startupStopWatch);
7980
}
8081

8182
// ILanguageServerWatcher
82-
public async startLanguageServer(languageServerType: LanguageServerType, resource?: Resource): Promise<void> {
83-
await this.startAndGetLanguageServer(languageServerType, resource);
83+
public async startLanguageServer(
84+
languageServerType: LanguageServerType,
85+
resource?: Resource,
86+
startupStopWatch?: StopWatch,
87+
): Promise<void> {
88+
await this.startAndGetLanguageServer(languageServerType, resource, startupStopWatch);
8489
}
8590

8691
public register(): void {
@@ -124,6 +129,7 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang
124129
private async startAndGetLanguageServer(
125130
languageServerType: LanguageServerType,
126131
resource?: Resource,
132+
startupStopWatch?: StopWatch,
127133
): Promise<ILanguageServerExtensionManager> {
128134
const lsResource = this.getWorkspaceUri(resource);
129135
const currentInterpreter = this.workspaceInterpreters.get(lsResource.fsPath);
@@ -170,6 +176,10 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang
170176

171177
if (languageServerExtensionManager.canStartLanguageServer(interpreter)) {
172178
// Start the language server.
179+
if (startupStopWatch) {
180+
// It means that startup is triggering this code, track time it takes since startup to activate this code.
181+
sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRIGGER_DURATION, startupStopWatch.elapsedTime);
182+
}
173183
await languageServerExtensionManager.startLanguageServer(lsResource, interpreter);
174184

175185
logStartup(languageServerType, lsResource);

‎src/client/pythonEnvironments/base/locator.ts

Copy file name to clipboardExpand all lines: src/client/pythonEnvironments/base/locator.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type PythonEnvUpdatedEvent<I = PythonEnvInfo> = {
2020
/**
2121
* The iteration index of The env info that was previously provided.
2222
*/
23-
index: number;
23+
index?: number;
2424
/**
2525
* The env info that was previously provided.
2626
*/

‎src/client/pythonEnvironments/base/locatorUtils.ts

Copy file name to clipboardExpand all lines: src/client/pythonEnvironments/base/locatorUtils.ts
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export async function getEnvs<I = PythonEnvInfo>(iterator: IPythonEnvsIterator<I
8585
}
8686
updatesDone.resolve();
8787
listener.dispose();
88-
} else {
88+
} else if (event.index !== undefined) {
8989
const { index, update } = event;
9090
if (envs[index] === undefined) {
9191
const json = JSON.stringify(update);
@@ -95,6 +95,8 @@ export async function getEnvs<I = PythonEnvInfo>(iterator: IPythonEnvsIterator<I
9595
}
9696
// We don't worry about if envs[index] is set already.
9797
envs[index] = update;
98+
} else if (event.update) {
99+
envs.push(event.update);
98100
}
99101
});
100102
}

‎src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts

Copy file name to clipboardExpand all lines: src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts
+11-2Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,18 @@ export abstract class LazyResourceBasedLocator extends Locator<BasicEnvInfo> imp
4343
await this.disposables.dispose();
4444
}
4545

46-
public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<BasicEnvInfo> {
47-
await this.activate();
46+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<BasicEnvInfo> {
4847
const iterator = this.doIterEnvs(query);
48+
const it = this._iterEnvs(iterator, query);
49+
it.onUpdated = iterator.onUpdated;
50+
return it;
51+
}
52+
53+
private async *_iterEnvs(
54+
iterator: IPythonEnvsIterator<BasicEnvInfo>,
55+
query?: PythonLocatorQuery,
56+
): IPythonEnvsIterator<BasicEnvInfo> {
57+
await this.activate();
4958
if (query?.envPath) {
5059
let result = await iterator.next();
5160
while (!result.done) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.