Skip to content

Commit 7a2e014

Browse files
Add a basic implementation of CachingLocator.
1 parent 8a7724e commit 7a2e014

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import '../../common/extensions';
5+
import { createDeferred } from '../../common/utils/async';
6+
import { PythonEnvInfo } from '../base/info';
7+
import {
8+
ILocator,
9+
IPythonEnvsIterator,
10+
PythonLocatorQuery,
11+
} from '../base/locator';
12+
import { getEnvs, getQueryFilter } from '../base/locatorUtils';
13+
import { PythonEnvsWatcher } from '../base/watcher';
14+
15+
interface IEnvsCache {
16+
initialize(): Promise<void>;
17+
listAll(): Promise<PythonEnvInfo[] | undefined>;
18+
getEnv(env: Partial<PythonEnvInfo>): Promise<PythonEnvInfo | undefined>;
19+
setAll(envs: PythonEnvInfo[]): Promise<void>;
20+
flush(): Promise<void>;
21+
}
22+
23+
export class CachingLocator extends PythonEnvsWatcher implements ILocator {
24+
private readonly initializing = createDeferred<void>();
25+
26+
constructor(
27+
private readonly cache: IEnvsCache,
28+
private readonly locator: ILocator,
29+
) {
30+
super();
31+
locator.onChanged((event) => {
32+
this.refresh()
33+
.then(() => this.fire(event))
34+
.ignoreErrors();
35+
});
36+
}
37+
38+
public async initialize(): Promise<void> {
39+
await this.cache.initialize();
40+
const envs = await this.cache.listAll();
41+
if (envs !== undefined) {
42+
this.initializing.resolve();
43+
await this.refresh();
44+
} else {
45+
// There is nothing in the cache, so we must wait for the
46+
// initial refresh to finish before allowing iteration.
47+
await this.refresh();
48+
this.initializing.resolve();
49+
}
50+
}
51+
52+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
53+
return this.iterFromCache(query);
54+
}
55+
56+
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
57+
// If necessary we could be more aggressive about invalidating
58+
// the cached value.
59+
const envForKey = typeof env === 'string'
60+
? { executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 }}
61+
: env;
62+
const cached = await this.cache.getEnv(envForKey);
63+
if (cached !== undefined) {
64+
return cached;
65+
}
66+
// Fall back to the underlying locator.
67+
const envs = await this.cache.listAll();
68+
const resolved = await this.locator.resolveEnv(env);
69+
if (resolved !== undefined) {
70+
envs!.push(resolved);
71+
await this.update(envs!);
72+
}
73+
return resolved;
74+
}
75+
76+
private async* iterFromCache(query?: PythonLocatorQuery): IPythonEnvsIterator {
77+
// XXX For now we wait for the initial refresh to finish...
78+
await this.initializing.promise;
79+
80+
const envs = await this.cache.listAll();
81+
if (envs === undefined) {
82+
throw Error('this should be unreachable');
83+
}
84+
if (await this.needsRefresh(envs)) {
85+
// Refresh in the background.
86+
this.refresh().ignoreErrors();
87+
}
88+
if (query !== undefined) {
89+
const filter = getQueryFilter(query);
90+
yield* envs.filter(filter);
91+
} else {
92+
yield* envs;
93+
}
94+
}
95+
96+
// eslint-disable-next-line class-methods-use-this,@typescript-eslint/no-unused-vars
97+
private async needsRefresh(_envs: PythonEnvInfo[]): Promise<boolean> {
98+
// XXX
99+
// For now we never refresh. Options:
100+
// * every X minutes (via `initialize()`
101+
// * if at least X minutes have elapsed
102+
// * if some "stale" check on any known env fails
103+
return false;
104+
}
105+
106+
private async refresh(): Promise<void> {
107+
const iterator = this.locator.iterEnvs();
108+
const envs = await getEnvs(iterator);
109+
await this.update(envs);
110+
}
111+
112+
private async update(envs: PythonEnvInfo[]): Promise<void> {
113+
// If necessary, we could skip if there are no changes.
114+
await this.cache.setAll(envs);
115+
await this.cache.flush();
116+
this.fire({}); // Emit an "onCHanged" event.
117+
}
118+
}

0 commit comments

Comments
 (0)