Skip to content

Commit b51aac1

Browse files
Kartik Rajwesm
Kartik Raj
authored andcommitted
Make environments.known API faster (microsoft/vscode-python#23010)
Closes microsoft/vscode-python#22994 closes microsoft/vscode-python#22993 Temporarily build our own known cache from which we return envs instead.
1 parent e9c1543 commit b51aac1

File tree

4 files changed

+79
-17
lines changed

4 files changed

+79
-17
lines changed

extensions/positron-python/src/client/environmentApi.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
Resource,
3333
} from './api/types';
3434
import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi';
35+
import { EnvironmentKnownCache } from './environmentKnownCache';
3536

3637
type ActiveEnvironmentChangeEvent = {
3738
resource: WorkspaceFolder | undefined;
@@ -120,6 +121,15 @@ export function buildEnvironmentApi(
120121
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
121122
const extensions = serviceContainer.get<IExtensions>(IExtensions);
122123
const envVarsProvider = serviceContainer.get<IEnvironmentVariablesProvider>(IEnvironmentVariablesProvider);
124+
let knownCache: EnvironmentKnownCache;
125+
126+
function initKnownCache() {
127+
const knownEnvs = discoveryApi
128+
.getEnvs()
129+
.filter((e) => filterUsingVSCodeContext(e))
130+
.map((e) => updateReference(e));
131+
return new EnvironmentKnownCache(knownEnvs);
132+
}
123133
function sendApiTelemetry(apiName: string, args?: unknown) {
124134
extensions
125135
.determineExtensionFromCallStack()
@@ -139,19 +149,26 @@ export function buildEnvironmentApi(
139149
// Filter out environments that are not in the current workspace.
140150
return;
141151
}
152+
if (!knownCache) {
153+
knownCache = initKnownCache();
154+
}
142155
if (e.old) {
143156
if (e.new) {
157+
const newEnv = updateReference(e.new);
158+
knownCache.updateEnv(convertEnvInfo(e.old), newEnv);
144159
traceVerbose('Python API env change detected', env.id, 'update');
145-
onEnvironmentsChanged.fire({ type: 'update', env: convertEnvInfoAndGetReference(e.new) });
160+
onEnvironmentsChanged.fire({ type: 'update', env: newEnv });
146161
reportInterpretersChanged([
147162
{
148163
path: getEnvPath(e.new.executable.filename, e.new.location).path,
149164
type: 'update',
150165
},
151166
]);
152167
} else {
168+
const oldEnv = updateReference(e.old);
169+
knownCache.updateEnv(oldEnv, undefined);
153170
traceVerbose('Python API env change detected', env.id, 'remove');
154-
onEnvironmentsChanged.fire({ type: 'remove', env: convertEnvInfoAndGetReference(e.old) });
171+
onEnvironmentsChanged.fire({ type: 'remove', env: oldEnv });
155172
reportInterpretersChanged([
156173
{
157174
path: getEnvPath(e.old.executable.filename, e.old.location).path,
@@ -160,8 +177,10 @@ export function buildEnvironmentApi(
160177
]);
161178
}
162179
} else if (e.new) {
180+
const newEnv = updateReference(e.new);
181+
knownCache.addEnv(newEnv);
163182
traceVerbose('Python API env change detected', env.id, 'add');
164-
onEnvironmentsChanged.fire({ type: 'add', env: convertEnvInfoAndGetReference(e.new) });
183+
onEnvironmentsChanged.fire({ type: 'add', env: newEnv });
165184
reportInterpretersChanged([
166185
{
167186
path: getEnvPath(e.new.executable.filename, e.new.location).path,
@@ -179,6 +198,9 @@ export function buildEnvironmentApi(
179198
onEnvironmentsChanged,
180199
onEnvironmentVariablesChanged,
181200
);
201+
if (!knownCache!) {
202+
knownCache = initKnownCache();
203+
}
182204

183205
const environmentApi: PythonExtension['environments'] = {
184206
getEnvironmentVariables: (resource?: Resource) => {
@@ -234,11 +256,9 @@ export function buildEnvironmentApi(
234256
return resolveEnvironment(path, discoveryApi);
235257
},
236258
get known(): Environment[] {
237-
sendApiTelemetry('known');
238-
return discoveryApi
239-
.getEnvs()
240-
.filter((e) => filterUsingVSCodeContext(e))
241-
.map((e) => convertEnvInfoAndGetReference(e));
259+
// Do not send telemetry for "known", as this may be called 1000s of times so it can significant:
260+
// sendApiTelemetry('known');
261+
return knownCache.envs;
242262
},
243263
async refreshEnvironments(options?: RefreshOptions) {
244264
if (!workspace.isTrusted) {
@@ -351,7 +371,7 @@ export function convertEnvInfo(env: PythonEnvInfo): Environment {
351371
return convertedEnv as Environment;
352372
}
353373

354-
function convertEnvInfoAndGetReference(env: PythonEnvInfo): Environment {
374+
function updateReference(env: PythonEnvInfo): Environment {
355375
return getEnvReference(convertEnvInfo(env));
356376
}
357377

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { Environment } from './api/types';
5+
6+
/**
7+
* Workaround temp cache until types are consolidated.
8+
*/
9+
export class EnvironmentKnownCache {
10+
private _envs: Environment[] = [];
11+
12+
constructor(envs: Environment[]) {
13+
this._envs = envs;
14+
}
15+
16+
public get envs(): Environment[] {
17+
return this._envs;
18+
}
19+
20+
public addEnv(env: Environment): void {
21+
const found = this._envs.find((e) => env.id === e.id);
22+
if (!found) {
23+
this._envs.push(env);
24+
}
25+
}
26+
27+
public updateEnv(oldValue: Environment, newValue: Environment | undefined): void {
28+
const index = this._envs.findIndex((e) => oldValue.id === e.id);
29+
if (index !== -1) {
30+
if (newValue === undefined) {
31+
this._envs.splice(index, 1);
32+
} else {
33+
this._envs[index] = newValue;
34+
}
35+
}
36+
}
37+
}

extensions/positron-python/src/test/api.functional.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ suite('Extension API', () => {
3737
interpreterService = mock(InterpreterService);
3838
environmentVariablesProvider = mock<IEnvironmentVariablesProvider>();
3939
discoverAPI = mock<IDiscoveryAPI>();
40+
when(discoverAPI.getEnvs()).thenReturn([]);
4041

4142
when(serviceContainer.get<IConfigurationService>(IConfigurationService)).thenReturn(
4243
instance(configurationService),

extensions/positron-python/src/test/environmentApi.unit.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ suite('Python Environment API', () => {
7474
envVarsProvider = typemoq.Mock.ofType<IEnvironmentVariablesProvider>();
7575
extensions
7676
.setup((e) => e.determineExtensionFromCallStack())
77-
.returns(() => Promise.resolve({ extensionId: 'id', displayName: 'displayName', apiName: 'apiName' }))
78-
.verifiable(typemoq.Times.atLeastOnce());
77+
.returns(() => Promise.resolve({ extensionId: 'id', displayName: 'displayName', apiName: 'apiName' }));
7978
interpreterPathService = typemoq.Mock.ofType<IInterpreterPathService>();
8079
configService = typemoq.Mock.ofType<IConfigurationService>();
8180
onDidChangeRefreshState = new EventEmitter();
@@ -94,13 +93,12 @@ suite('Python Environment API', () => {
9493

9594
discoverAPI.setup((d) => d.onProgress).returns(() => onDidChangeRefreshState.event);
9695
discoverAPI.setup((d) => d.onChanged).returns(() => onDidChangeEnvironments.event);
96+
discoverAPI.setup((d) => d.getEnvs()).returns(() => []);
9797

9898
environmentApi = buildEnvironmentApi(discoverAPI.object, serviceContainer.object);
9999
});
100100

101101
teardown(() => {
102-
// Verify each API method sends telemetry regarding who called the API.
103-
extensions.verifyAll();
104102
sinon.restore();
105103
});
106104

@@ -325,6 +323,7 @@ suite('Python Environment API', () => {
325323
},
326324
];
327325
discoverAPI.setup((d) => d.getEnvs()).returns(() => envs);
326+
environmentApi = buildEnvironmentApi(discoverAPI.object, serviceContainer.object);
328327
const actual = environmentApi.known;
329328
const actualEnvs = actual?.map((a) => (a as EnvironmentReference).internal);
330329
assert.deepEqual(
@@ -409,10 +408,10 @@ suite('Python Environment API', () => {
409408
// Update events
410409
events = [];
411410
expectedEvents = [];
412-
const updatedEnv = cloneDeep(envs[0]);
413-
updatedEnv.arch = Architecture.x86;
414-
onDidChangeEnvironments.fire({ old: envs[0], new: updatedEnv });
415-
expectedEvents.push({ env: convertEnvInfo(updatedEnv), type: 'update' });
411+
const updatedEnv0 = cloneDeep(envs[0]);
412+
updatedEnv0.arch = Architecture.x86;
413+
onDidChangeEnvironments.fire({ old: envs[0], new: updatedEnv0 });
414+
expectedEvents.push({ env: convertEnvInfo(updatedEnv0), type: 'update' });
416415
eventValues = events.map((e) => ({ env: (e.env as EnvironmentReference).internal, type: e.type }));
417416
assert.deepEqual(eventValues, expectedEvents);
418417

@@ -423,6 +422,11 @@ suite('Python Environment API', () => {
423422
expectedEvents.push({ env: convertEnvInfo(envs[2]), type: 'remove' });
424423
eventValues = events.map((e) => ({ env: (e.env as EnvironmentReference).internal, type: e.type }));
425424
assert.deepEqual(eventValues, expectedEvents);
425+
426+
const expectedEnvs = [convertEnvInfo(updatedEnv0), convertEnvInfo(envs[1])].sort();
427+
const knownEnvs = environmentApi.known.map((e) => (e as EnvironmentReference).internal).sort();
428+
429+
assert.deepEqual(expectedEnvs, knownEnvs);
426430
});
427431

428432
test('updateActiveEnvironmentPath: no resource', async () => {

0 commit comments

Comments
 (0)