@@ -68,15 +68,29 @@ python environments extension begins activation
6868 c. a persisted env path exists in workspace state for this scope (folder Uri)
6969
7070 FAST PATH (run if above three conditions are true):
71- 1. read persisted path
72- 2. `resolve(persistedPath)`
71+ **Race-condition safety (runs before any await):**
72+ 1. if `_initialized` doesn't exist yet:
73+ - create deferred and **register immediately** via `setInitialized()` callback
74+ - this blocks concurrent callers from spawning duplicate background inits
75+ - kick off `startBackgroundInit()` as fire-and-forget
76+ 2. get project fsPath: `getProjectFsPathForScope(api, scope)`
77+ - prefers resolved project path if available, falls back to scope.fsPath
78+ - shared across all managers to avoid lambda duplication
79+ 3. read persisted path
80+ 4. `resolve(persistedPath)`
7381 1. failure → see SLOW PATH
74- 2. successful → return env immediately AND
75- 3. if `_initialized` deferred doesn't exist yet:
76- - create one, kick off `startBackgroundInit ()` as fire-and-forget
82+ 2. successful → return env immediately (background init continues in parallel)
83+ **Failure recovery (in startBackgroundInit error handler):**
84+ - if background init throws: `setInitialized(undefined)` — clear deferred so next `get ()` call retries init
7785
7886 SLOW PATH — fast path conditions not met, or fast path failed:
79- 4. `initialize()` — lazy, once-only per manager (guarded by deferred)
87+ 4. `initialize()` — lazy, once-only per manager (guarded by `_initialized` deferred)
88+ **Once-only guarantee:**
89+ - first caller creates `_initialized` deferred (if not already created by fast path)
90+ - concurrent callers see the existing deferred and await it instead of re-running init
91+ - deferred is **not cleared on failure** here (unlike in fast-path background handler)
92+ so only one init attempt runs, but subsequent calls still await the same failed init
93+ **Note:** In the fast path, if background init fails, the deferred is cleared to allow retry
8094 a. `nativeFinder.refresh(hardRefresh=false)`:
8195 → internally calls `handleSoftRefresh()` → computes cache key from options
8296 - on reload: cache is empty (Map was destroyed) → cache miss
0 commit comments