@@ -20,18 +20,9 @@ python environments extension begins activation
20201 . spawn PET process (` createNativePythonFinder ` )
2121 1 . sets up a JSON-RPC connection to it over stdin/stdout
22222 . register all built-in managers in parallel (Promise.all):
23- - system: create SysPythonManager + VenvManager + PipPackageManager, register immediately
24- - ✅ NO PET call — managers are created and registered with no tool detection
25- - sets up file watcher for venv activation scripts
23+ - system: create SysPythonManager + VenvManager + PipPackageManager, register immediately (✅ NO PET call, sets up file watcher)
2624 - conda: ` getConda(nativeFinder) ` checks settings → cache → persistent state → PATH
27- - if found → register CondaEnvManager + CondaPackageManager
28- - if not found → PET fallback as last resort (rarely hit, conda is usually on PATH)
29- - if not found at all → skip, send MANAGER_REGISTRATION.SKIPPED telemetry
30- - pyenv: create PyEnvManager, register immediately
31- - ✅ NO PET call — always registers unconditionally (lazy discovery)
32- - pipenv: create PipenvManager, register immediately
33- - ✅ NO PET call — always registers unconditionally (lazy discovery)
34- - poetry: create PoetryManager + PoetryPackageManager, register immediately
25+ - pyenv & pipenv & poetry: create PyEnvManager, register immediately
3526 - ✅ NO PET call — always registers unconditionally (lazy discovery)
3627 - shellStartupVars: initialize
3728 - all managers fire ` onDidChangeEnvironmentManager ` → ManagerReady resolves
@@ -40,99 +31,65 @@ python environments extension begins activation
4031
4132** --- gate point: ` applyInitialEnvironmentSelection ` ---**
4233
43- 📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount }
44-
45- 1 . for each workspace folder + global scope (no workspace case), run ` resolvePriorityChainCore ` to find manager:
46- - P1: pythonProjects[ ] setting → specific manager for this project
47- - P2: user-configured defaultEnvManager setting
48- - P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path)
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
54- - for global scope: use system python manager directly
55-
56- 2 . get the environment from the winning priority level:
57-
58- --- fork point: ` result.environment ?? await result.manager.get(folder.uri) ` ---
59- left side truthy = envPreResolved | left side undefined = managerDiscovery
60-
61- envPreResolved — P3 won (interpreter → manager):
62- ` resolvePriorityChainCore ` calls ` tryResolveInterpreterPath() ` :
63- 1. ` nativeFinder.resolve(path) ` — single PET call, resolves just this one binary
64- 2. find which manager owns the resolved env (by managerId)
65- 3. return { manager, environment } — BOTH are known
66- → result.environment is set → the ` ?? ` short-circuits
67- → no ` manager.get() ` called, no ` initialize() ` , no full discovery
68-
69- managerDiscovery — P1, P2, or P4 won (manager → interpreter):
70- ` resolvePriorityChainCore ` returns { manager, environment: undefined }
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
120-
121- 📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection }
122-
123- 3 . env is cached in memory (no settings.json write)
124- 4 . Python extension / status bar can now get the selected env via ` api.getEnvironment(scope) `
125-
126- 📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration (activation→here), result, failureStage?, errorType? }
127-
128- SIDEBAR ACCESS (on-demand, if user opens Python environments panel):
129- - view iterates ` providers.managers ` → all registered managers appear (including pyenv/pipenv/poetry)
130- - user expands a manager node → ` getChildren() ` → ` manager.getEnvironments('all') `
131- → ` initialize() ` (lazy, once-only) → ` nativeFinder.refresh(false) ` :
132- - if cache populated from earlier env selection → instant cache hit
133- - if first access → warm PET call (no concurrent pressure, single caller)
134- → environments appear under the manager node
135- → if no environments found → "No environments" placeholder shown
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+ ---
13693
13794POST-INIT:
138951 . register terminal package watcher
0 commit comments