Skip to content

Commit 76f7763

Browse files
author
Kartik Raj
authored
Make lookup for new environments detected faster (#19922)
Closes #19900 Trigger a scoped refresh instead of a full refresh when an environment gets created, preferably only looking for the environment which gets created.
1 parent 8470555 commit 76f7763

25 files changed

+132
-644
lines changed

src/client/pythonEnvironments/base/locator.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { Event, Uri } from 'vscode';
77
import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async';
88
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info';
99
import {
10-
BasicPythonEnvsChangedEvent,
1110
IPythonEnvsWatcher,
1211
PythonEnvCollectionChangedEvent,
1312
PythonEnvsChangedEvent,
@@ -128,6 +127,14 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & {
128127
* If provided, results should be limited to within these locations.
129128
*/
130129
searchLocations?: SearchLocations;
130+
/**
131+
* If provided, results should be limited envs provided by these locators.
132+
*/
133+
providerId?: string;
134+
/**
135+
* If provided, results area limited to this env.
136+
*/
137+
envPath?: string;
131138
};
132139

133140
type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
@@ -153,8 +160,8 @@ export type BasicEnvInfo = {
153160
* events emitted via `onChanged` do not need to provide information
154161
* for the specific environments that changed.
155162
*/
156-
export interface ILocator<I = PythonEnvInfo, E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
157-
extends IPythonEnvsWatcher<E> {
163+
export interface ILocator<I = PythonEnvInfo, E = PythonEnvsChangedEvent> extends IPythonEnvsWatcher<E> {
164+
readonly providerId: string;
158165
/**
159166
* Iterate over the enviroments known tos this locator.
160167
*
@@ -174,6 +181,8 @@ export interface ILocator<I = PythonEnvInfo, E extends BasicPythonEnvsChangedEve
174181
iterEnvs(query?: QueryForEvent<E>): IPythonEnvsIterator<I>;
175182
}
176183

184+
export type ICompositeLocator<I = PythonEnvInfo, E = PythonEnvsChangedEvent> = Omit<ILocator<I, E>, 'providerId'>;
185+
177186
interface IResolver {
178187
/**
179188
* Find as much info about the given Python environment as possible.
@@ -184,7 +193,7 @@ interface IResolver {
184193
resolveEnv(path: string): Promise<PythonEnvInfo | undefined>;
185194
}
186195

187-
export interface IResolvingLocator<I = PythonEnvInfo> extends IResolver, ILocator<I> {}
196+
export interface IResolvingLocator<I = PythonEnvInfo> extends IResolver, ICompositeLocator<I> {}
188197

189198
export interface GetRefreshEnvironmentsOptions {
190199
/**
@@ -234,7 +243,7 @@ export interface IDiscoveryAPI {
234243
resolveEnv(path: string): Promise<PythonEnvInfo | undefined>;
235244
}
236245

237-
interface IEmitter<E extends PythonEnvsChangedEvent> {
246+
interface IEmitter<E> {
238247
fire(e: E): void;
239248
}
240249

@@ -250,10 +259,11 @@ interface IEmitter<E extends PythonEnvsChangedEvent> {
250259
* should be used. Only in low-level cases should you consider using
251260
* `BasicPythonEnvsChangedEvent`.
252261
*/
253-
abstract class LocatorBase<I = PythonEnvInfo, E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
254-
implements ILocator<I, E> {
262+
abstract class LocatorBase<I = PythonEnvInfo, E = PythonEnvsChangedEvent> implements ILocator<I, E> {
255263
public readonly onChanged: Event<E>;
256264

265+
public abstract readonly providerId: string;
266+
257267
protected readonly emitter: IEmitter<E>;
258268

259269
constructor(watcher: IPythonEnvsWatcher<E> & IEmitter<E>) {

src/client/pythonEnvironments/base/locators.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { chain } from '../../common/utils/async';
55
import { Disposables } from '../../common/utils/resourceLifecycle';
66
import { PythonEnvInfo } from './info';
77
import {
8+
ICompositeLocator,
89
ILocator,
910
IPythonEnvsIterator,
1011
isProgressEvent,
@@ -59,12 +60,15 @@ export function combineIterators<I>(iterators: IPythonEnvsIterator<I>[]): IPytho
5960
*
6061
* Events and iterator results are combined.
6162
*/
62-
export class Locators<I = PythonEnvInfo> extends PythonEnvsWatchers implements ILocator<I> {
63+
export class Locators<I = PythonEnvInfo> extends PythonEnvsWatchers implements ICompositeLocator<I> {
64+
public readonly providerId: string;
65+
6366
constructor(
6467
// The locators will be watched as well as iterated.
6568
private readonly locators: ReadonlyArray<ILocator<I>>,
6669
) {
6770
super(locators);
71+
this.providerId = locators.map((loc) => loc.providerId).join('+');
6872
}
6973

7074
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<I> {

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

+21-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { IDisposable } from '../../../../common/types';
55
import { createDeferred, Deferred } from '../../../../common/utils/async';
66
import { Disposables } from '../../../../common/utils/resourceLifecycle';
77
import { traceError } from '../../../../logging';
8-
import { PythonEnvInfo } from '../../info';
9-
import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator';
8+
import { arePathsSame } from '../../../common/externalDependencies';
9+
import { getEnvPath } from '../../info/env';
10+
import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator';
1011

1112
/**
1213
* A base locator class that manages the lifecycle of resources.
@@ -20,7 +21,7 @@ import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'
2021
*
2122
* Otherwise it will leak (and we have no leak detection).
2223
*/
23-
export abstract class LazyResourceBasedLocator<I = PythonEnvInfo> extends Locator<I> implements IDisposable {
24+
export abstract class LazyResourceBasedLocator extends Locator<BasicEnvInfo> implements IDisposable {
2425
protected readonly disposables = new Disposables();
2526

2627
// This will be set only once we have to create necessary resources
@@ -42,15 +43,29 @@ export abstract class LazyResourceBasedLocator<I = PythonEnvInfo> extends Locato
4243
await this.disposables.dispose();
4344
}
4445

45-
public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<I> {
46+
public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<BasicEnvInfo> {
4647
await this.activate();
47-
yield* this.doIterEnvs(query);
48+
const iterator = this.doIterEnvs(query);
49+
if (query?.envPath) {
50+
let result = await iterator.next();
51+
while (!result.done) {
52+
const currEnv = result.value;
53+
const { path } = getEnvPath(currEnv.executablePath, currEnv.envPath);
54+
if (arePathsSame(path, query.envPath)) {
55+
yield currEnv;
56+
break;
57+
}
58+
result = await iterator.next();
59+
}
60+
} else {
61+
yield* iterator;
62+
}
4863
}
4964

5065
/**
5166
* The subclass implementation of iterEnvs().
5267
*/
53-
protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<I>;
68+
protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<BasicEnvInfo>;
5469

5570
/**
5671
* This is where subclasses get their resources ready.

src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
5757
constructor(private readonly cache: IEnvsCollectionCache, private readonly locator: IResolvingLocator) {
5858
super();
5959
this.locator.onChanged((event) => {
60-
const query = undefined; // We can also form a query based on the event, but skip that for simplicity.
60+
const query: PythonLocatorQuery | undefined = event.providerId
61+
? { providerId: event.providerId, envPath: event.envPath }
62+
: undefined; // We can also form a query based on the event, but skip that for simplicity.
6163
let scheduledRefresh = this.scheduledRefreshesPerQuery.get(query);
6264
// If there is no refresh scheduled for the query, start a new one.
6365
if (!scheduledRefresh) {

src/client/pythonEnvironments/base/locators/composite/envsReducer.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { areSameEnv } from '../../info/env';
99
import { getPrioritizedEnvKinds } from '../../info/envKind';
1010
import {
1111
BasicEnvInfo,
12+
ICompositeLocator,
1213
ILocator,
1314
IPythonEnvsIterator,
1415
isProgressEvent,
@@ -22,7 +23,7 @@ import { PythonEnvsChangedEvent } from '../../watcher';
2223
/**
2324
* Combines duplicate environments received from the incoming locator into one and passes on unique environments
2425
*/
25-
export class PythonEnvsReducer implements ILocator<BasicEnvInfo> {
26+
export class PythonEnvsReducer implements ICompositeLocator<BasicEnvInfo> {
2627
public get onChanged(): Event<PythonEnvsChangedEvent> {
2728
return this.parentLocator.onChanged;
2829
}

src/client/pythonEnvironments/base/locators/composite/envsResolver.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getEnvPath, setEnvDisplayString } from '../../info/env';
1010
import { InterpreterInformation } from '../../info/interpreter';
1111
import {
1212
BasicEnvInfo,
13-
ILocator,
13+
ICompositeLocator,
1414
IPythonEnvsIterator,
1515
IResolvingLocator,
1616
isProgressEvent,
@@ -35,7 +35,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
3535
}
3636

3737
constructor(
38-
private readonly parentLocator: ILocator<BasicEnvInfo>,
38+
private readonly parentLocator: ICompositeLocator<BasicEnvInfo>,
3939
private readonly environmentInfoService: IEnvironmentInfoService,
4040
) {}
4141

src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManag
77
import { traceError, traceVerbose } from '../../../../logging';
88
import { FSWatchingLocator } from './fsWatchingLocator';
99

10-
export class CondaEnvironmentLocator extends FSWatchingLocator<BasicEnvInfo> {
10+
export class CondaEnvironmentLocator extends FSWatchingLocator {
11+
public readonly providerId: string = 'conda-envs';
12+
1113
public constructor() {
1214
super(
1315
() => getCondaEnvironmentsTxt(),

src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
7878
/**
7979
* Finds and resolves custom virtual environments that users have provided.
8080
*/
81-
export class CustomVirtualEnvironmentLocator extends FSWatchingLocator<BasicEnvInfo> {
81+
export class CustomVirtualEnvironmentLocator extends FSWatchingLocator {
82+
public readonly providerId: string = 'custom-virtual-envs';
83+
8284
constructor() {
8385
super(getCustomVirtualEnvDirs, getVirtualEnvKind, {
8486
// Note detecting kind of virtual env depends on the file structure around the

src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ type GetExecutablesFunc = () => AsyncIterableIterator<string>;
1414
/**
1515
* A naive locator the wraps a function that finds Python executables.
1616
*/
17-
class FoundFilesLocator implements ILocator<BasicEnvInfo> {
17+
abstract class FoundFilesLocator implements ILocator<BasicEnvInfo> {
18+
public abstract readonly providerId: string;
19+
1820
public readonly onChanged: Event<PythonEnvsChangedEvent>;
1921

2022
protected readonly watcher = new PythonEnvsWatcher();
@@ -45,6 +47,8 @@ type GetDirExecutablesFunc = (dir: string) => AsyncIterableIterator<string>;
4547
* A locator for executables in a single directory.
4648
*/
4749
export class DirFilesLocator extends FoundFilesLocator {
50+
public readonly providerId: string;
51+
4852
constructor(
4953
dirname: string,
5054
defaultKind: PythonEnvKind,
@@ -53,6 +57,7 @@ export class DirFilesLocator extends FoundFilesLocator {
5357
source?: PythonEnvSource[],
5458
) {
5559
super(defaultKind, () => getExecutables(dirname), source);
60+
this.providerId = `dir-files-${dirname}`;
5661
}
5762
}
5863

src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
resolvePythonExeGlobs,
1414
watchLocationForPythonBinaries,
1515
} from '../../../common/pythonBinariesWatcher';
16-
import { PythonEnvInfo, PythonEnvKind } from '../../info';
16+
import { PythonEnvKind } from '../../info';
1717
import { LazyResourceBasedLocator } from '../common/resourceBasedLocator';
1818

1919
export enum FSWatcherKind {
@@ -80,7 +80,7 @@ type FileWatchOptions = {
8080
*
8181
* Subclasses can call `this.emitter.fire()` * to emit events.
8282
*/
83-
export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceBasedLocator<I> {
83+
export abstract class FSWatchingLocator extends LazyResourceBasedLocator {
8484
constructor(
8585
/**
8686
* Location(s) to watch for python binaries.
@@ -135,7 +135,7 @@ export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceB
135135
this.disposables.push(
136136
watchLocationForPattern(path.dirname(root), path.basename(root), () => {
137137
traceVerbose('Detected change in file: ', root, 'initiating a refresh');
138-
this.emitter.fire({});
138+
this.emitter.fire({ providerId: this.providerId });
139139
}),
140140
);
141141
return;
@@ -161,7 +161,7 @@ export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceB
161161
// |__ python <--- executable
162162
const searchLocation = Uri.file(opts.searchLocation ?? path.dirname(getEnvironmentDirFromPath(executable)));
163163
traceVerbose('Fired event ', JSON.stringify({ type, kind, searchLocation }), 'from locator');
164-
this.emitter.fire({ type, kind, searchLocation });
164+
this.emitter.fire({ type, kind, searchLocation, providerId: this.providerId, envPath: executable });
165165
};
166166

167167
const globs = resolvePythonExeGlobs(

src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
8282
/**
8383
* Finds and resolves virtual environments created in known global locations.
8484
*/
85-
export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator<BasicEnvInfo> {
85+
export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator {
86+
public readonly providerId: string = 'global-virtual-env';
87+
8688
constructor(private readonly searchDepth?: number) {
8789
super(getGlobalVirtualEnvDirs, getVirtualEnvKind, {
8890
// Note detecting kind of virtual env depends on the file structure around the

src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ export async function getMicrosoftStorePythonExes(): Promise<string[]> {
6565
return [];
6666
}
6767

68-
export class MicrosoftStoreLocator extends FSWatchingLocator<BasicEnvInfo> {
68+
export class MicrosoftStoreLocator extends FSWatchingLocator {
69+
public readonly providerId: string = 'microsoft-store';
70+
6971
private readonly kind: PythonEnvKind = PythonEnvKind.MicrosoftStore;
7072

7173
constructor() {

src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
6060
/**
6161
* Finds and resolves virtual environments created using poetry.
6262
*/
63-
export class PoetryLocator extends FSWatchingLocator<BasicEnvInfo> {
63+
export class PoetryLocator extends FSWatchingLocator {
64+
public readonly providerId: string = 'poetry';
65+
6466
public constructor(private readonly root: string) {
6567
super(
6668
() => getRootVirtualEnvDir(root),

src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { isMacDefaultPythonPath } from './macDefaultLocator';
1212
import { traceError } from '../../../../logging';
1313

1414
export class PosixKnownPathsLocator extends Locator<BasicEnvInfo> {
15+
public readonly providerId = 'posixKnownPaths';
16+
1517
private kind: PythonEnvKind = PythonEnvKind.OtherGlobal;
1618

1719
public iterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {

src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ async function* getPyenvEnvironments(): AsyncIterableIterator<BasicEnvInfo> {
3535
}
3636
}
3737

38-
export class PyenvLocator extends FSWatchingLocator<BasicEnvInfo> {
38+
export class PyenvLocator extends FSWatchingLocator {
39+
public readonly providerId: string = 'pyenv';
40+
3941
constructor() {
4042
super(getPyenvVersionsDir, async () => PythonEnvKind.Pyenv);
4143
}

src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { DirFilesLocator } from './filesLocator';
2424
* it for changes.
2525
*/
2626
export class WindowsPathEnvVarLocator implements ILocator<BasicEnvInfo>, IDisposable {
27+
public readonly providerId: string = 'windows-path-env-var-locator';
28+
2729
public readonly onChanged: Event<PythonEnvsChangedEvent>;
2830

2931
private readonly locators: Locators<BasicEnvInfo>;
@@ -93,6 +95,7 @@ function getDirFilesLocator(
9395
yield* await getEnvs(locator.iterEnvs(query));
9496
}
9597
return {
98+
providerId: locator.providerId,
9699
iterEnvs,
97100
dispose,
98101
onChanged: locator.onChanged,

src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { getRegistryInterpreters } from '../../../common/windowsUtils';
77
import { traceError } from '../../../../logging';
88

99
export class WindowsRegistryLocator extends Locator<BasicEnvInfo> {
10+
public readonly providerId: string = 'windows-registry';
11+
1012
// eslint-disable-next-line class-methods-use-this
1113
public iterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {
1214
const iterator = async function* () {

src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
5050
/**
5151
* Finds and resolves virtual environments created in workspace roots.
5252
*/
53-
export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator<BasicEnvInfo> {
53+
export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator {
54+
public readonly providerId: string = 'workspaceVirtualEnvLocator';
55+
5456
public constructor(private readonly root: string) {
5557
super(
5658
() => getWorkspaceVirtualEnvDirs(this.root),

0 commit comments

Comments
 (0)