@@ -19,104 +19,79 @@ python environments extension begins activation
1919** ASYNC (setImmediate callback, still in extension.ts):**
20201 . spawn PET process (` createNativePythonFinder ` )
2121 1 . sets up a JSON-RPC connection to it over stdin/stdout
22- 2 . register all built-in managers + shell env init in parallel (Promise.all):
23- - ` shellStartupVarsMgr.initialize() `
24- - for each manager (system, conda, pyenv, pipenv, poetry):
25- 1 . check if tool exists (e.g. ` getConda(nativeFinder) ` asks PET for the conda binary)
26- 2 . if tool not found → log, return early (manager not registered )
27- 3 . if tool found → create manager, call ` api.registerEnvironmentManager(manager) `
28- - this adds it to the ` EnvironmentManagers ` map
29- - fires ` onDidChangeEnvironmentManager ` → ` ManagerReady ` deferred resolves for this manager
30- 3 . all registrations complete (Promise.all resolves)
22+ 2 . register all built-in managers in parallel (Promise.all):
23+ - system: create SysPythonManager + VenvManager + PipPackageManager, register immediately (✅ NO PET call, sets up file watcher)
24+ - conda: ` getConda(nativeFinder) ` checks settings → cache → persistent state → PATH
25+ - pyenv & pipenv & poetry: create PyEnvManager, register immediately
26+ - ✅ NO PET call — always registers unconditionally (lazy discovery )
27+ - shellStartupVars: initialize
28+ - all managers fire ` onDidChangeEnvironmentManager ` → ManagerReady resolves
29+ 3 . all registrations complete (Promise.all resolves) — fast, typically milliseconds
30+
3131
3232** --- gate point: ` applyInitialEnvironmentSelection ` ---**
3333
34- 📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount }
35-
36- 1 . for each workspace folder + global scope (no workspace case), run ` resolvePriorityChainCore ` to find manager:
37- - P1: pythonProjects[ ] setting → specific manager for this project
38- - P2: user-configured defaultEnvManager setting
39- - P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path)
40- - P4: auto-discovery → try venv manager, fall back to system python
41- - for workspace scope: call ` venvManager.get(scope) `
42- - if venv found (local .venv/venv) → use venv manager with that env
43- - if no local venv → venv manager may still return its ` globalEnv ` (system Python)
44- - if venvManager.get returns undefined → fall back to system python manager
45- - for global scope: use system python manager directly
46-
47- 2 . get the environment from the winning priority level:
48-
49- --- fork point: ` result.environment ?? await result.manager.get(folder.uri) ` ---
50- left side truthy = envPreResolved | left side undefined = managerDiscovery
51-
52- envPreResolved — P3 won (interpreter → manager):
53- ` resolvePriorityChainCore ` calls ` tryResolveInterpreterPath() ` :
54- 1. ` nativeFinder.resolve(path) ` — single PET call, resolves just this one binary
55- 2. find which manager owns the resolved env (by managerId)
56- 3. return { manager, environment } — BOTH are known
57- → result.environment is set → the ` ?? ` short-circuits
58- → no ` manager.get() ` called, no ` initialize() ` , no full discovery
59-
60- managerDiscovery — P1, P2, or P4 won (manager → interpreter):
61- ` resolvePriorityChainCore ` returns { manager, environment: undefined }
62- → falls through to ` await result.manager.get(scope) `
63-
64- **--- inner fork: fast path vs slow path (tryFastPathGet in fastPath.ts) ---**
65- Conditions checked before entering fast path:
66- a. ` _initialized ` deferred is undefined (never created) OR has not yet completed
67- b. scope is a ` Uri ` (not global/undefined)
68-
69- FAST PATH (background init kickoff + optional early return):
70- **Race-condition safety (runs before any await):**
71- 1. if `_initialized` doesn't exist yet:
72- - create deferred and **register immediately** via `setInitialized()` callback
73- - this blocks concurrent callers from spawning duplicate background inits
74- - kick off `startBackgroundInit()` as fire-and-forget
75- - this happens as soon as (a) and (b) are true, **even if** no persisted path exists
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 (only if scope is a `Uri`; may return undefined)
80- 4. if a persisted path exists:
81- - attempt `resolve(persistedPath)`
82- - failure (no env, mismatched manager, etc.) → fall through to SLOW PATH
83- - success → return env immediately (background init continues in parallel)
84- **Failure recovery (in startBackgroundInit error handler):**
85- - if background init throws: `setInitialized(undefined)` — clear deferred so next `get()` call retries init
86-
87- SLOW PATH — fast path conditions not met, or fast path failed:
88- 4. `initialize()` — lazy, once-only per manager (guarded by `_initialized` deferred)
89- **Once-only guarantee:**
90- - first caller creates `_initialized` deferred (if not already created by fast path)
91- - concurrent callers see the existing deferred and await it instead of re-running init
92- - deferred is **not cleared on failure** here (unlike in fast-path background handler)
93- so only one init attempt runs, but subsequent calls still await the same failed init
94- **Note:** In the fast path, if background init fails, the deferred is cleared to allow retry
95- a. `nativeFinder.refresh(hardRefresh=false)`:
96- → internally calls `handleSoftRefresh()` → computes cache key from options
97- - on reload: cache is empty (Map was destroyed) → cache miss
98- - falls through to `handleHardRefresh()`
99- → `handleHardRefresh()` adds request to WorkerPool queue (concurrency 1):
100- 1. run `configure()` to setup PET search paths
101- 2. run `refresh` — PET scans filesystem
102- - PET may use its own on-disk cache
103- 3. returns NativeInfo[] (all envs of all types)
104- - result stored in in-memory cache so subsequent managers get instant cache hit
105- b. filter results to this manager's env type (e.g. conda filters to kind=conda)
106- c. convert NativeEnvInfo → PythonEnvironment objects → populate collection
107- d. `loadEnvMap()` — reads persisted env path from workspace state
108- → matches path against PET discovery results
109- → populates `fsPathToEnv` map
110- 5. look up scope in `fsPathToEnv` → return the matched env
111-
112- 📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection }
113-
114- 3 . env is cached in memory (no settings.json write)
115- 4 . Python extension / status bar can now get the selected env via ` api.getEnvironment(scope) `
116-
117- 📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration (activation→here), result, failureStage?, errorType? }
118-
119- ** POST-INIT:**
34+ 📊 TELEMETRY: ENV_SELECTION.STARTED { duration, registeredManagerCount, registeredManagerIds, workspaceFolderCount }
35+
36+ ** Step 1 — pick a manager** (` resolvePriorityChainCore ` , per workspace folder + global):
37+
38+ | Priority | Source | Returns |
39+ | ----------| --------| ---------|
40+ | P1 | ` pythonProjects[] ` setting | manager only |
41+ | P2 | ` defaultEnvManager ` setting | manager only |
42+ | P3 | ` python.defaultInterpreterPath ` → ` nativeFinder.resolve(path) ` | manager ** + environment** |
43+ | P4 | auto-discovery: venv → system python fallback | manager only |
44+
45+ ** Step 2 — get the environment** (` result.environment ?? await result.manager.get(scope) ` ):
46+
47+ - ** If P3 won:** environment is already resolved → done, no ` get() ` call needed.
48+ - ** Otherwise:** calls ` manager.get(scope) ` , which has two internal paths:
49+
50+ ** Fast path** (` tryFastPathGet ` in ` fastPath.ts ` ) — entered when ` _initialized ` hasn't completed and scope is a ` Uri ` :
51+ 1 . Synchronously create ` _initialized ` deferred + kick off ` startBackgroundInit() ` (fire-and-forget full PET discovery)
52+ 2 . Read persisted env path from workspace state
53+ 3 . If persisted path exists → ` resolve(path) ` → return immediately (background init continues in parallel)
54+ 4 . If no persisted path or resolve fails → fall through to slow path
55+ - * On background init failure:* clears ` _initialized ` so next ` get() ` retries
56+
57+ ** Slow path** — fast path skipped or failed:
58+ 1 . ` initialize() ` — lazy, once-only (guarded by ` _initialized ` deferred, concurrent callers await it)
59+ - ` nativeFinder.refresh(false) ` → PET scan (cached across managers after first call)
60+ - Filter results to this manager's type → populate ` collection `
61+ - ` loadEnvMap() ` → match persisted paths against discovered envs
62+ 2 . Look up scope in ` fsPathToEnv ` → return matched env
63+
64+ 📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration, scope, prioritySource, managerId, path, hasPersistedSelection }
65+
66+ ** Step 3 — done:**
67+ - env cached in memory (no settings.json write)
68+ - available via ` api.getEnvironment(scope) `
69+
70+ 📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration, result, failureStage?, errorType? }
71+
72+ ---
73+
74+ ### Other entry points to ` initialize() `
75+
76+ All three trigger ` initialize() ` lazily (once-only, guarded by ` _initialized ` deferred). After the first call completes, subsequent calls are no-ops.
77+
78+ ** ` manager.get(scope) ` ** — environment selection (Step 2 above):
79+ - Called during ` applyInitialEnvironmentSelection ` or when settings change triggers re-selection
80+ - Fast path may resolve immediately; slow path awaits ` initialize() `
81+
82+ ** ` manager.getEnvironments(scope) ` ** — sidebar / listing:
83+ - Called when user expands a manager node in the Python environments panel
84+ - Also called by any API consumer requesting the full environment list
85+ - If PET cache populated from earlier ` get() ` → instant hit; otherwise warm PET call
86+
87+ ** ` manager.resolve(context) ` ** — path resolution:
88+ - Called when resolving a specific Python binary path to check if it belongs to this manager
89+ - Used by ` tryResolveInterpreterPath() ` in the priority chain (P3) and by external API consumers
90+ - Awaits ` initialize() ` , then delegates to manager-specific resolve (e.g., ` resolvePipenvPath ` )
91+
92+ ---
93+
94+ POST-INIT:
120951 . register terminal package watcher
121962 . register settings change listener (` registerInterpreterSettingsChangeListener ` ) — re-runs priority chain if settings change
122973 . initialize terminal manager
0 commit comments