Skip to content

Commit 4f04e72

Browse files
authored
Lazy-register Conda manager; defer discovery until first use
1 parent 1b87faa commit 4f04e72

3 files changed

Lines changed: 80 additions & 50 deletions

File tree

docs/startup-flow.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ python environments extension begins activation
2121
1. sets up a JSON-RPC connection to it over stdin/stdout
2222
2. register all built-in managers in parallel (Promise.all):
2323
- 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
24+
- conda & pyenv & pipenv & poetry: create manager, register immediately
2625
- ✅ NO PET call — always registers unconditionally (lazy discovery)
2726
- shellStartupVars: initialize
2827
- all managers fire `onDidChangeEnvironmentManager` → ManagerReady resolves

src/managers/conda/condaEnvManager.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,29 @@ import {
2020
SetEnvironmentScope,
2121
} from '../../api';
2222
import { CondaStrings } from '../../common/localize';
23-
import { traceError } from '../../common/logging';
23+
import { traceError, traceInfo } from '../../common/logging';
24+
import { StopWatch } from '../../common/stopWatch';
25+
import { EventNames } from '../../common/telemetry/constants';
26+
import { classifyError } from '../../common/telemetry/errorClassifier';
27+
import { sendTelemetryEvent } from '../../common/telemetry/sender';
2428
import { createDeferred, Deferred } from '../../common/utils/deferred';
2529
import { normalizePath } from '../../common/utils/pathUtils';
2630
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
31+
import { PythonProjectManager } from '../../internal.api';
2732
import { getProjectFsPathForScope, tryFastPathGet } from '../common/fastPath';
2833
import { NativePythonFinder } from '../common/nativePythonFinder';
29-
import { CondaSourcingStatus } from './condaSourcingUtils';
34+
import { notifyMissingManagerIfDefault } from '../common/utils';
35+
import { constructCondaSourcingStatus, CondaSourcingStatus } from './condaSourcingUtils';
3036
import {
3137
checkForNoPythonCondaEnvironment,
3238
clearCondaCache,
3339
createCondaEnvironment,
3440
deleteCondaEnvironment,
3541
generateName,
42+
getConda,
3643
getCondaForGlobal,
3744
getCondaForWorkspace,
45+
getCondaPathSetting,
3846
getDefaultCondaPrefix,
3947
quickCreateConda,
4048
refreshCondaEnvs,
@@ -61,6 +69,7 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
6169
private readonly nativeFinder: NativePythonFinder,
6270
private readonly api: PythonEnvironmentApi,
6371
public readonly log: LogOutputChannel,
72+
private readonly projectManager?: PythonProjectManager,
6473
) {
6574
this.name = 'conda';
6675
this.displayName = 'Conda';
@@ -87,8 +96,27 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
8796
}
8897

8998
this._initialized = createDeferred();
99+
const stopWatch = new StopWatch();
100+
let result: 'success' | 'tool_not_found' | 'error' = 'success';
101+
let envCount = 0;
102+
let toolSource = 'none';
103+
let errorType: string | undefined;
90104

91105
try {
106+
// Check if tool is findable before PET refresh (settings/cache/persistent state/PATH only, no PET).
107+
// Calling getConda() without a native finder skips the PET refresh path inside getCondaExecutable.
108+
const hasExplicitSetting = !!getCondaPathSetting();
109+
let condaTool: string | undefined;
110+
try {
111+
condaTool = await getConda();
112+
} catch {
113+
condaTool = undefined;
114+
}
115+
const preRefreshTool = condaTool;
116+
if (preRefreshTool) {
117+
toolSource = hasExplicitSetting ? 'settings' : 'local';
118+
}
119+
92120
await withProgress(
93121
{
94122
location: ProgressLocation.Window,
@@ -104,7 +132,47 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
104132
);
105133
},
106134
);
135+
136+
envCount = this.collection.length;
137+
138+
// If tool wasn't found via local lookup, check if refresh discovered it via PET
139+
// (refreshCondaEnvs persists the conda path it found from native data).
140+
if (!preRefreshTool) {
141+
try {
142+
condaTool = await getConda();
143+
} catch {
144+
condaTool = undefined;
145+
}
146+
toolSource = condaTool ? 'pet' : 'none';
147+
}
148+
149+
if (toolSource === 'none') {
150+
result = 'tool_not_found';
151+
traceInfo('Conda not found, conda features will be inactive.');
152+
if (this.projectManager) {
153+
await notifyMissingManagerIfDefault('ms-python.python:conda', this.projectManager, this.api);
154+
}
155+
} else if (condaTool) {
156+
// Conda was found — populate sourcing information for activation/shell support.
157+
try {
158+
this.sourcingInformation = await constructCondaSourcingStatus(condaTool);
159+
traceInfo(this.sourcingInformation.toString());
160+
} catch (ex) {
161+
traceError('Failed to construct conda sourcing status', ex);
162+
}
163+
}
164+
} catch (ex) {
165+
result = 'error';
166+
errorType = classifyError(ex);
167+
traceError('Conda lazy initialization failed', ex);
107168
} finally {
169+
sendTelemetryEvent(EventNames.MANAGER_LAZY_INIT, stopWatch.elapsedTime, {
170+
managerName: 'conda',
171+
result,
172+
envCount,
173+
toolSource,
174+
errorType,
175+
});
108176
this._initialized.resolve();
109177
}
110178
}

src/managers/conda/main.ts

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,28 @@
11
import { Disposable, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4-
import { EventNames } from '../../common/telemetry/constants';
5-
import { sendTelemetryEvent } from '../../common/telemetry/sender';
64
import { getPythonApi } from '../../features/pythonApi';
75
import { PythonProjectManager } from '../../internal.api';
86
import { NativePythonFinder } from '../common/nativePythonFinder';
9-
import { notifyMissingManagerIfDefault } from '../common/utils';
107
import { CondaEnvManager } from './condaEnvManager';
118
import { CondaPackageManager } from './condaPackageManager';
12-
import { CondaSourcingStatus, constructCondaSourcingStatus } from './condaSourcingUtils';
13-
import { getConda } from './condaUtils';
149

1510
export async function registerCondaFeatures(
1611
nativeFinder: NativePythonFinder,
1712
disposables: Disposable[],
1813
log: LogOutputChannel,
1914
projectManager: PythonProjectManager,
2015
): Promise<void> {
21-
let stage = 'getPythonApi';
2216
const api: PythonEnvironmentApi = await getPythonApi();
2317

24-
let condaPath: string | undefined;
25-
try {
26-
// get Conda will return only ONE conda manager, that correlates to a single conda install
27-
stage = 'getConda';
28-
condaPath = await getConda(nativeFinder);
29-
} catch (ex) {
30-
traceInfo('Conda not found, turning off conda features.', ex);
31-
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
32-
managerName: 'conda',
33-
reason: 'tool_not_found',
34-
});
35-
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
36-
return;
37-
}
18+
traceInfo('Registering conda manager (environments will be discovered lazily)');
19+
const envManager = new CondaEnvManager(nativeFinder, api, log, projectManager);
20+
const packageManager = new CondaPackageManager(api, log);
3821

39-
// Conda was found — errors below are real registration failures (let safeRegister handle telemetry)
40-
try {
41-
stage = 'constructCondaSourcingStatus';
42-
const sourcingStatus: CondaSourcingStatus = await constructCondaSourcingStatus(condaPath);
43-
traceInfo(sourcingStatus.toString());
44-
45-
stage = 'createEnvManager';
46-
const envManager = new CondaEnvManager(nativeFinder, api, log);
47-
stage = 'createPkgManager';
48-
const packageManager = new CondaPackageManager(api, log);
49-
50-
envManager.sourcingInformation = sourcingStatus;
51-
52-
stage = 'registerManagers';
53-
disposables.push(
54-
envManager,
55-
packageManager,
56-
api.registerEnvironmentManager(envManager),
57-
api.registerPackageManager(packageManager),
58-
);
59-
} catch (ex) {
60-
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
61-
const err = ex instanceof Error ? ex : new Error(String(ex));
62-
(err as Error & { failureStage?: string }).failureStage = stage;
63-
throw err;
64-
}
22+
disposables.push(
23+
envManager,
24+
packageManager,
25+
api.registerEnvironmentManager(envManager),
26+
api.registerPackageManager(packageManager),
27+
);
6528
}

0 commit comments

Comments
 (0)