Skip to content

Commit 6806513

Browse files
committed
dedup refresh concurrent calls
1 parent f701500 commit 6806513

1 file changed

Lines changed: 32 additions & 8 deletions

File tree

src/managers/common/nativePythonFinder.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ class NativePythonFinderImpl implements NativePythonFinder {
326326
private connection: rpc.MessageConnection;
327327
private readonly pool: WorkerPool<NativePythonEnvironmentKind | Uri[] | undefined, NativeInfo[]>;
328328
private cache: Map<string, NativeInfo[]> = new Map();
329+
/**
330+
* Tracks in-flight hard refreshes by cache key so concurrent callers share a
331+
* single PET scan instead of queueing duplicate work.
332+
*/
333+
private inFlightRefreshes: Map<string, Promise<NativeInfo[]>> = new Map();
329334
private startDisposables: Disposable[] = [];
330335
private proc: ChildProcess | undefined;
331336
private processExited: boolean = false;
@@ -555,20 +560,39 @@ class NativePythonFinderImpl implements NativePythonFinder {
555560

556561
private async handleHardRefresh(options?: NativePythonEnvironmentKind | Uri[]): Promise<NativeInfo[]> {
557562
const key = this.getKey(options);
563+
564+
const inFlight = this.inFlightRefreshes.get(key);
565+
if (inFlight) {
566+
this.outputChannel.debug(`[Finder] Coalescing hard refresh with in-flight request for key: ${key}`);
567+
return inFlight;
568+
}
569+
558570
this.cache.delete(key);
559571
if (!options) {
560572
this.outputChannel.debug('[Finder] Refreshing all environments');
561573
} else {
562574
this.outputChannel.debug(`[Finder] Hard refresh for key: ${key}`);
563575
}
564-
const result = await this.pool.addToQueue(options);
565-
// Validate result from worker pool
566-
if (!result || !Array.isArray(result)) {
567-
this.outputChannel.warn(`[pet] Worker pool returned invalid result type: ${typeof result}`);
568-
return [];
569-
}
570-
this.cache.set(key, result);
571-
return result;
576+
577+
// .finally clears the in-flight slot on both success AND failure paths so
578+
// a rejected refresh does not poison the cache — the next call after a
579+
// failure starts a fresh attempt, matching today's behavior.
580+
const refreshPromise = this.pool
581+
.addToQueue(options)
582+
.then((result) => {
583+
if (!result || !Array.isArray(result)) {
584+
this.outputChannel.warn(`[pet] Worker pool returned invalid result type: ${typeof result}`);
585+
return [] as NativeInfo[];
586+
}
587+
this.cache.set(key, result);
588+
return result;
589+
})
590+
.finally(() => {
591+
this.inFlightRefreshes.delete(key);
592+
});
593+
594+
this.inFlightRefreshes.set(key, refreshPromise);
595+
return refreshPromise;
572596
}
573597

574598
private async handleSoftRefresh(options?: NativePythonEnvironmentKind | Uri[]): Promise<NativeInfo[]> {

0 commit comments

Comments
 (0)