-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathfastPath.ts
More file actions
143 lines (131 loc) · 5.75 KB
/
fastPath.ts
File metadata and controls
143 lines (131 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Uri } from 'vscode';
import { GetEnvironmentScope, PythonEnvironment, PythonEnvironmentApi } from '../../api';
import { traceError, traceVerbose, traceWarn } from '../../common/logging';
import { StopWatch } from '../../common/stopWatch';
import { EventNames } from '../../common/telemetry/constants';
import { sendTelemetryEvent } from '../../common/telemetry/sender';
import { createDeferred, Deferred } from '../../common/utils/deferred';
/**
* Options for the fast-path resolution in manager.get().
*/
export interface FastPathOptions {
/** The current _initialized deferred (may be undefined if init hasn't started). */
initialized: Deferred<void> | undefined;
/** Updates the manager's _initialized deferred. */
setInitialized: (initialized: Deferred<void> | undefined) => void;
/** The scope passed to get(). */
scope: GetEnvironmentScope;
/** Label for log messages, e.g. 'venv', 'conda'. */
label: string;
/** Gets the project fsPath for a given Uri scope. */
getProjectFsPath: (scope: Uri) => string;
/** Reads the persisted env path for a workspace fsPath. */
getPersistedPath: (workspaceFsPath: string) => Promise<string | undefined>;
/** Resolves a persisted path to a full PythonEnvironment. */
resolve: (persistedPath: string) => Promise<PythonEnvironment | undefined>;
/** Starts background initialization (full discovery). Returns a promise that completes when init is done. */
startBackgroundInit: () => Promise<void> | Thenable<void>;
/** Optional: reads the persisted env path for global scope (when scope is undefined). */
getGlobalPersistedPath?: () => Promise<string | undefined>;
}
/**
* Result from a successful fast-path resolution.
*/
export interface FastPathResult {
/** The resolved environment. */
env: PythonEnvironment;
}
/**
* Gets the fsPath for a scope by preferring the resolved project path when available.
*/
export function getProjectFsPathForScope(api: Pick<PythonEnvironmentApi, 'getPythonProject'>, scope: Uri): string {
return api.getPythonProject(scope)?.uri.fsPath ?? scope.fsPath;
}
/**
* Attempts fast-path resolution for manager.get(): if full initialization hasn't completed yet
* and there's a persisted environment for the workspace, resolve it directly via nativeFinder
* instead of waiting for full discovery.
*
* Returns the resolved environment (with an optional new deferred) if successful, or undefined
* to fall through to the normal init path.
*/
export async function tryFastPathGet(opts: FastPathOptions): Promise<FastPathResult | undefined> {
const isGlobalScope = !(opts.scope instanceof Uri);
// Global scope is only supported when the caller provides getGlobalPersistedPath
if (isGlobalScope && !opts.getGlobalPersistedPath) {
return undefined;
}
if (opts.initialized?.completed) {
return undefined;
}
let deferred = opts.initialized;
if (!deferred) {
deferred = createDeferred<void>();
opts.setInitialized(deferred);
const deferredRef = deferred;
try {
Promise.resolve(opts.startBackgroundInit()).then(
() => deferredRef.resolve(),
(err) => {
traceError(`[${opts.label}] Background initialization failed:`, err);
// Allow subsequent get()/initialize() calls to retry after a background init failure.
opts.setInitialized(undefined);
deferredRef.resolve();
},
);
} catch (syncErr) {
traceError(`[${opts.label}] Background initialization threw synchronously:`, syncErr);
opts.setInitialized(undefined);
deferredRef.resolve();
}
}
// Look up the persisted path — either from workspace cache or global cache
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));
// Track cross-session cache performance for global scope
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;
if (persistedPath) {
try {
const resolved = await opts.resolve(persistedPath);
if (resolved) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'hit',
});
}
return { env: resolved };
}
// Cached path found but resolve returned undefined (e.g., Python was uninstalled)
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
} catch (err) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
traceWarn(
`[${opts.label}] Fast path resolve failed for '${persistedPath}', falling back to full init:`,
err,
);
}
} else {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'miss',
});
}
traceVerbose(`[${opts.label}] Fast path: no persisted path, falling through to slow path`);
}
return undefined;
}