Skip to content

Commit 4f32c50

Browse files
author
Kartik Raj
committed
Translate progress events to progress promises
1 parent fb3a4c2 commit 4f32c50

File tree

1 file changed

+70
-20
lines changed

1 file changed

+70
-20
lines changed

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

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
import { Event, EventEmitter } from 'vscode';
55
import '../../../../common/extensions';
6-
import { createDeferred } from '../../../../common/utils/async';
6+
import { createDeferred, Deferred } from '../../../../common/utils/async';
77
import { traceError } from '../../../../logging';
88
import { normalizePath } from '../../../common/externalDependencies';
99
import { PythonEnvInfo } from '../../info';
1010
import {
11+
GetRefreshEnvironmentsOptions,
1112
IDiscoveryAPI,
1213
IResolvingLocator,
1314
isProgressEvent,
@@ -24,21 +25,22 @@ import { IEnvsCollectionCache } from './envsCollectionCache';
2425
*/
2526
export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollectionChangedEvent> implements IDiscoveryAPI {
2627
/** Keeps track of ongoing refreshes for various queries. */
27-
private refreshPromises = new Map<PythonLocatorQuery | undefined, Promise<void>>();
28+
private refreshDeferreds = new Map<PythonLocatorQuery | undefined, Deferred<void>>();
2829

2930
/** Keeps track of scheduled refreshes other than the ongoing one for various queries. */
3031
private scheduledRefreshes = new Map<PythonLocatorQuery | undefined, Promise<void>>();
3132

33+
private refreshStageDeferreds = new Map<ProgressReportStage, Deferred<void>>();
34+
3235
private readonly progress = new EventEmitter<ProgressNotificationEvent>();
3336

3437
public get onProgress(): Event<ProgressNotificationEvent> {
3538
return this.progress.event;
3639
}
3740

38-
public getRefreshPromise(): Promise<void> | undefined {
39-
return this.refreshPromises.size > 0
40-
? Promise.all(Array.from(this.refreshPromises.values())).then()
41-
: undefined;
41+
public getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise<void> | undefined {
42+
const stage = options?.stage ?? ProgressReportStage.discoveryFinished;
43+
return this.refreshStageDeferreds.get(stage)?.promise;
4244
}
4345

4446
constructor(private readonly cache: IEnvsCollectionCache, private readonly locator: IResolvingLocator) {
@@ -58,6 +60,10 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
5860
this.cache.onChanged((e) => {
5961
this.fire(e);
6062
});
63+
this.onProgress((event) => {
64+
this.refreshStageDeferreds.get(event.stage)?.resolve();
65+
this.refreshStageDeferreds.delete(event.stage);
66+
});
6167
}
6268

6369
public async resolveEnv(path: string): Promise<PythonEnvInfo | undefined> {
@@ -81,7 +87,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
8187

8288
public getEnvs(query?: PythonLocatorQuery): PythonEnvInfo[] {
8389
const cachedEnvs = this.cache.getAllEnvs();
84-
if (cachedEnvs.length === 0 && this.refreshPromises.size === 0) {
90+
if (cachedEnvs.length === 0 && this.refreshDeferreds.size === 0) {
8591
// We expect a refresh to already be triggered when activating discovery component.
8692
traceError('No python is installed or a refresh has not already been triggered');
8793
this.triggerRefresh().ignoreErrors();
@@ -98,20 +104,18 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
98104
}
99105

100106
private startRefresh(query: (PythonLocatorQuery & { clearCache?: boolean }) | undefined): Promise<void> {
101-
const deferred = createDeferred<void>();
102107
if (query?.clearCache) {
103108
this.cache.clearCache();
104109
}
105-
// Ensure we set this before we trigger the promise to accurately track when a refresh has started.
106-
this.refreshPromises.set(query, deferred.promise);
110+
this.createProgressStates(query);
107111
const promise = this.addEnvsToCacheForQuery(query);
108112
return promise
109113
.then(async () => {
110-
// Ensure we delete this before we resolve the promise to accurately track when a refresh finishes.
111-
this.refreshPromises.delete(query);
112-
deferred.resolve();
114+
this.resolveProgressStates(query);
113115
})
114-
.catch((ex) => deferred.reject(ex));
116+
.catch((ex) => {
117+
this.rejectProgressStates(query, ex);
118+
});
115119
}
116120

117121
private async addEnvsToCacheForQuery(query: PythonLocatorQuery | undefined) {
@@ -126,11 +130,19 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
126130
if (iterator.onUpdated !== undefined) {
127131
const listener = iterator.onUpdated(async (event) => {
128132
if (isProgressEvent(event)) {
129-
if (event.stage === ProgressReportStage.discoveryFinished) {
130-
state.done = true;
131-
listener.dispose();
132-
} else if (event.stage === ProgressReportStage.allPathsDiscovered && !query) {
133-
this.progress.fire(event);
133+
switch (event.stage) {
134+
case ProgressReportStage.discoveryFinished:
135+
state.done = true;
136+
listener.dispose();
137+
break;
138+
case ProgressReportStage.allPathsDiscovered:
139+
if (!query) {
140+
// Only mark as all paths discovered when querying for all envs.
141+
this.progress.fire(event);
142+
}
143+
break;
144+
default:
145+
this.progress.fire(event);
134146
}
135147
} else {
136148
state.pending += 1;
@@ -166,7 +178,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
166178
// Even if no refresh is running for this exact query, there might be other
167179
// refreshes running for a superset of this query. For eg. the `undefined` query
168180
// is a superset for every other query, only consider that for simplicity.
169-
return this.refreshPromises.get(query) ?? this.refreshPromises.get(undefined);
181+
return this.refreshDeferreds.get(query)?.promise ?? this.refreshDeferreds.get(undefined)?.promise;
170182
}
171183

172184
/**
@@ -187,4 +199,42 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
187199
}
188200
return nextRefreshPromise;
189201
}
202+
203+
private createProgressStates(query: PythonLocatorQuery | undefined) {
204+
this.refreshDeferreds.set(query, createDeferred<void>());
205+
Object.values(ProgressReportStage).forEach((stage) => {
206+
const deferred = createDeferred<void>();
207+
this.refreshStageDeferreds.set(stage, deferred);
208+
});
209+
if (ProgressReportStage.allPathsDiscovered && query) {
210+
// This stage is only applicable when no scope is provided.
211+
this.refreshStageDeferreds.delete(ProgressReportStage.allPathsDiscovered);
212+
}
213+
}
214+
215+
private rejectProgressStates(query: PythonLocatorQuery | undefined, ex: Error) {
216+
this.refreshDeferreds.get(query)?.reject(ex);
217+
this.refreshDeferreds.delete(query);
218+
Object.values(ProgressReportStage).forEach((stage) => {
219+
this.refreshStageDeferreds.get(stage)?.reject(ex);
220+
this.refreshStageDeferreds.delete(stage);
221+
});
222+
}
223+
224+
private resolveProgressStates(query: PythonLocatorQuery | undefined) {
225+
this.refreshDeferreds.get(query)?.resolve();
226+
this.refreshDeferreds.delete(query);
227+
Object.values(ProgressReportStage).forEach((stage) => {
228+
this.refreshStageDeferreds.get(stage)?.resolve();
229+
this.refreshStageDeferreds.delete(stage);
230+
});
231+
this.checkIfFinishedAndNotify();
232+
}
233+
234+
private checkIfFinishedAndNotify() {
235+
const isRefreshComplete = Array.from(this.refreshDeferreds.values()).every((d) => d.completed);
236+
if (isRefreshComplete) {
237+
this.progress.fire({ stage: ProgressReportStage.discoveryFinished });
238+
}
239+
}
190240
}

0 commit comments

Comments
 (0)