55user opens VS Code
66python environments extension begins activation
77
8- SYNC (` activate ` in extension.ts):
9- 1 . create core objects: ProjectManager, EnvironmentManagers, ManagerReady
10- 2 . ` setPythonApi() ` — API object created, deferred resolved (API is now available to consumers)
11- 3 . create views (EnvManagerView, ProjectView), status bar, terminal manager
12- 4 . register all commands
13- 5 . activate() returns — extension is "active" from VS Code's perspective
8+ ** SYNC (` activate ` in extension.ts):**
9+ 1 . create StatusBar, ProjectManager, EnvVarManager, EnvironmentManagers, ManagerReady
10+ 2 . create TerminalActivation, shell providers, TerminalManager
11+ 3 . create ProjectCreators
12+ 4 . ` setPythonApi() ` — API object created, deferred resolved (API is now available to consumers)
13+ 5 . create views (EnvManagerView, ProjectView)
14+ 6 . register all commands
15+ 7 . activate() returns — extension is "active" from VS Code's perspective
1416
1517 📊 TELEMETRY: EXTENSION.ACTIVATION_DURATION { duration }
1618
17- ASYNC (setImmediate callback, still in extension.ts):
19+ ** ASYNC (setImmediate callback, still in extension.ts):**
18201 . spawn PET process (` createNativePythonFinder ` )
1921 1 . sets up a JSON-RPC connection to it over stdin/stdout
20222 . register all built-in managers in parallel (Promise.all):
@@ -35,17 +37,20 @@ ASYNC (setImmediate callback, still in extension.ts):
3537 - all managers fire ` onDidChangeEnvironmentManager ` → ManagerReady resolves
36383 . all registrations complete (Promise.all resolves) — fast, typically milliseconds
3739
38- --- gate point: ` applyInitialEnvironmentSelection ` ---
40+
41+ ** --- gate point: ` applyInitialEnvironmentSelection ` ---**
42+
3943 📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount }
4044
41451 . for each workspace folder + global scope (no workspace case), run ` resolvePriorityChainCore ` to find manager:
4246 - P1: pythonProjects[ ] setting → specific manager for this project
4347 - P2: user-configured defaultEnvManager setting
4448 - P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path)
45- - P4: auto-discovery → try venv manager (local .venv), fall back to system python
46- - for workspace scope: ask venv manager if there's a local env (.venv/venv in the folder)
47- - if found → use venv manager with that env
48- - if not found → fall back to system python manager
49+ - P4: auto-discovery → try venv manager, fall back to system python
50+ - for workspace scope: call ` venvManager.get(scope) `
51+ - if venv found (local .venv/venv) → use venv manager with that env
52+ - if no local venv → venv manager may still return its ` globalEnv ` (system Python)
53+ - if venvManager.get returns undefined → fall back to system python manager
4954 - for global scope: use system python manager directly
5055
51562 . get the environment from the winning priority level:
@@ -63,33 +68,55 @@ ASYNC (setImmediate callback, still in extension.ts):
6368
6469 managerDiscovery — P1, P2, or P4 won (manager → interpreter):
6570 ` resolvePriorityChainCore ` returns { manager, environment: undefined }
66- → result.environment is undefined → falls through to ` await result.manager.get(scope) `
67- ` manager.get(scope) ` (e.g. ` CondaEnvManager.get() ` , ` PyEnvManager.get() ` ):
68- 4. ` initialize() ` — lazy, once-only per manager (guarded by deferred)
69- a. ` nativeFinder.refresh(hardRefresh=false) ` :
70- → ` handleSoftRefresh() ` checks in-memory cache (Map) for key 'all'
71- - on reload: cache is empty (Map was destroyed) → cache miss
72- - falls through to ` handleHardRefresh() `
73- → ` handleHardRefresh() ` :
74- - adds request to WorkerPool queue (concurrency 1, so serialized)
75- - when its turn comes, calls ` doRefresh() ` :
76- 1. ` configure() ` — JSON-RPC to PET with search paths, conda/poetry/pipenv paths, cache dir
77- 2. ` refresh ` — JSON-RPC to PET, PET scans filesystem
78- - PET has had time to warm up since spawn (registration was fast)
79- - PET may use its own on-disk cache (cacheDirectory) to speed this up
80- - PET streams back results as 'environment' and 'manager' notifications
81- - envs missing version/prefix get an inline resolve() call
82- 3. returns NativeInfo[ ] (all envs of all types)
83- - result stored in in-memory cache under key 'all'
84- → subsequent managers calling nativeFinder.refresh(false) get cache hit → instant
85- b. filter results to this manager's env type (e.g. conda filters to kind=conda, pyenv to kind=pyenv)
86- c. for pipenv/poetry/pyenv: if tool CLI was not found via PATH during registration,
87- extract tool executable from PET's manager info in the refresh results
88- d. convert NativeEnvInfo → PythonEnvironment objects → populate collection
89- e. ` loadEnvMap() ` — reads persisted env path from workspace state
90- → matches path against freshly discovered collection via ` findEnvironmentByPath() `
91- → populates ` fsPathToEnv ` map
92- 5. look up scope in ` fsPathToEnv ` → return the matched env
71+ → falls through to ` await result.manager.get(scope) `
72+
73+ **--- inner fork: fast path vs slow path (tryFastPathGet in fastPath.ts) ---**
74+ Conditions checked before entering fast path:
75+ a. ` _initialized ` deferred is undefined (never created) OR has not yet completed
76+ b. scope is a ` Uri ` (not global/undefined)
77+
78+ FAST PATH (background init kickoff + optional early return):
79+ **Race-condition safety (runs before any await):**
80+ 1. if `_initialized` doesn't exist yet:
81+ - create deferred and **register immediately** via `setInitialized()` callback
82+ - this blocks concurrent callers from spawning duplicate background inits
83+ - kick off `startBackgroundInit()` as fire-and-forget
84+ - this happens as soon as (a) and (b) are true, **even if** no persisted path exists
85+ 2. get project fsPath: `getProjectFsPathForScope(api, scope)`
86+ - prefers resolved project path if available, falls back to scope.fsPath
87+ - shared across all managers to avoid lambda duplication
88+ 3. read persisted path (only if scope is a `Uri`; may return undefined)
89+ 4. if a persisted path exists:
90+ - attempt `resolve(persistedPath)`
91+ - failure (no env, mismatched manager, etc.) → fall through to SLOW PATH
92+ - success → return env immediately (background init continues in parallel)
93+ **Failure recovery (in startBackgroundInit error handler):**
94+ - if background init throws: `setInitialized(undefined)` — clear deferred so next `get()` call retries init
95+
96+ SLOW PATH — fast path conditions not met, or fast path failed:
97+ 4. `initialize()` — lazy, once-only per manager (guarded by `_initialized` deferred)
98+ **Once-only guarantee:**
99+ - first caller creates `_initialized` deferred (if not already created by fast path)
100+ - concurrent callers see the existing deferred and await it instead of re-running init
101+ - deferred is **not cleared on failure** here (unlike in fast-path background handler)
102+ so only one init attempt runs, but subsequent calls still await the same failed init
103+ **Note:** In the fast path, if background init fails, the deferred is cleared to allow retry
104+ a. `nativeFinder.refresh(hardRefresh=false)`:
105+ → internally calls `handleSoftRefresh()` → computes cache key from options
106+ - on reload: cache is empty (Map was destroyed) → cache miss
107+ - falls through to `handleHardRefresh()`
108+ → `handleHardRefresh()` adds request to WorkerPool queue (concurrency 1):
109+ 1. run `configure()` to setup PET search paths
110+ 2. run `refresh` — PET scans filesystem
111+ - PET may use its own on-disk cache
112+ 3. returns NativeInfo[] (all envs of all types)
113+ - result stored in in-memory cache so subsequent managers get instant cache hit
114+ b. filter results to this manager's env type (e.g. conda filters to kind=conda)
115+ c. convert NativeEnvInfo → PythonEnvironment objects → populate collection
116+ d. `loadEnvMap()` — reads persisted env path from workspace state
117+ → matches path against PET discovery results
118+ → populates `fsPathToEnv` map
119+ 5. look up scope in `fsPathToEnv` → return the matched env
93120
94121 📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection }
95122
@@ -111,4 +138,4 @@ POST-INIT:
1111381 . register terminal package watcher
1121392 . register settings change listener (` registerInterpreterSettingsChangeListener ` ) — re-runs priority chain if settings change
1131403 . initialize terminal manager
114- 4 . send telemetry (manager selection, project structure, discovery summary)
141+ 4 . send telemetry (manager selection, project structure, discovery summary)
0 commit comments