Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to use worker threads when discovering #21987

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 68 additions & 28 deletions src/client/pythonEnvironments/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { Event } from 'vscode';
import * as path from 'path';
import { Worker, isMainThread } from 'worker_threads';
import { Event, EventEmitter } from 'vscode';
import {
GetRefreshEnvironmentsOptions,
IDiscoveryAPI,
Expand All @@ -10,57 +16,91 @@ import {
PythonLocatorQuery,
TriggerRefreshOptions,
} from './base/locator';
import { PythonEnvCollectionChangedEvent, PythonEnvsWatcher } from './base/watcher';
import { EnvsCache, IEnvsCache } from './base/locators/composite/envCache';
import { getQueryFilter } from './base/locatorUtils';
import { ExtensionState } from '../components';

export type GetLocatorFunc = () => Promise<IDiscoveryAPI>;
export class PythonEnvironmentsWorkerWrapper extends PythonEnvsWatcher<PythonEnvCollectionChangedEvent>
implements IDiscoveryAPI {
private worker: Worker;

/**
* The public API for the Python environments component.
*
* Note that this is composed of sub-components.
*/
class PythonEnvironments implements IDiscoveryAPI {
private locator!: IDiscoveryAPI;
private cache: IEnvsCache;

constructor(
// These are factories for the sub-components the full component is composed of:
private readonly getLocator: GetLocatorFunc,
) {}
constructor() {
super();
if (!isMainThread) {
throw new Error('DiscoveryAPIWorkerWrapper cannot be instantiated in a worker thread.');
}
this.worker = new Worker(path.join(__dirname, 'worker.js'));
this.worker.on('message', (msg) => {
console.log(msg);
});
this.cache = new EnvsCache();
this.cache.onChanged((e) => {
this.fire(e);
});
// TODO: Also fire events from downstream locators.
}

public async activate(): Promise<void> {
this.locator = await this.getLocator();
public async activate(ext: ExtensionState): Promise<void> {
const extobj = { disposables: ext.disposables };
return this.callMethod('activate', []);
// TODO: Populate the cache.
}

public get onProgress(): Event<ProgressNotificationEvent> {
return this.locator.onProgress;
const eventEmitter = new EventEmitter<ProgressNotificationEvent>();
return eventEmitter.event;
}

public get refreshState(): ProgressReportStage {
return this.locator.refreshState;
return ProgressReportStage.discoveryStarted;
}

public getRefreshPromise(options?: GetRefreshEnvironmentsOptions) {
return this.locator.getRefreshPromise(options);
}

public get onChanged() {
return this.locator.onChanged;
return this.callMethod('getRefreshPromise', [options]);
}

public getEnvs(query?: PythonLocatorQuery) {
return this.locator.getEnvs(query);
const cachedEnvs = this.cache.getAllEnvs();
return query ? cachedEnvs.filter(getQueryFilter(query)) : cachedEnvs;
}

public async resolveEnv(env: string) {
return this.locator.resolveEnv(env);
return this.callMethod('resolveEnv', [env]);
}

public async triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions) {
return this.locator.triggerRefresh(query, options);
return this.callMethod('triggerRefresh', [query, options]);
}

private async callMethod(currMethod: string, args: any[]): Promise<any> {
return new Promise((resolve, reject) => {
this.worker.addListener('message', (event) => {
const { methodName, result, error } = event;
if (currMethod !== methodName) {
return;
}
if (result !== undefined) {
resolve(result);
} else if (error) {
reject(new Error(error));
}
});

this.worker.postMessage({ methodName: currMethod, args });
});
}
}

export async function createPythonEnvironments(getLocator: GetLocatorFunc): Promise<IDiscoveryAPI> {
const api = new PythonEnvironments(getLocator);
await api.activate();
export async function createPythonEnvironments(ext: ExtensionState): Promise<IDiscoveryAPI> {
const worker = new Worker(path.join(__dirname, 'worker2.js'));
// Listen for messages from the worker and print them.
worker.on('message', (msg) => {
console.log(msg);
});
const api = new PythonEnvironmentsWorkerWrapper();
await api.activate(ext);
return api;
}
53 changes: 53 additions & 0 deletions src/client/pythonEnvironments/apiImplementation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Event } from 'vscode';
import {
IDiscoveryAPI,
ProgressNotificationEvent,
GetRefreshEnvironmentsOptions,
PythonLocatorQuery,
TriggerRefreshOptions,
ProgressReportStage,
} from './base/locator';
import { ExtensionState } from '../components';
import { createLocator } from './createLocator';

/**
* The public API for the Python environments component.
*
* Note that this is composed of sub-components.
*/
export class PythonEnvironments implements IDiscoveryAPI {
private locator!: IDiscoveryAPI;

public async activate(ext: ExtensionState): Promise<void> {
this.locator = await createLocator(ext);
}

public get onProgress(): Event<ProgressNotificationEvent> {
return this.locator.onProgress;
}

public get refreshState(): ProgressReportStage {
return this.locator.refreshState;
}

public getRefreshPromise(options?: GetRefreshEnvironmentsOptions) {
return this.locator.getRefreshPromise(options);
}

public get onChanged() {
return this.locator.onChanged;
}

public getEnvs(query?: PythonLocatorQuery) {
return this.locator.getEnvs(query);
}

public async resolveEnv(env: string) {
return this.locator.resolveEnv(env);
}

public async triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions) {
return this.locator.triggerRefresh(query, options);
}
}
64 changes: 64 additions & 0 deletions src/client/pythonEnvironments/base/locators/composite/envCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { Event } from 'vscode';
import { PythonEnvInfo } from '../../info';
import { areSameEnv } from '../../info/env';
import {
BasicPythonEnvCollectionChangedEvent,
PythonEnvCollectionChangedEvent,
PythonEnvsWatcher,
} from '../../watcher';

export interface IEnvsCache {
/**
* Return all environment info currently in memory for this session.
*/
getAllEnvs(): PythonEnvInfo[];

/**
* Updates environment in cache using the value provided.
* If no new value is provided, remove the existing value from cache.
*/
updateEnv(oldValue: PythonEnvInfo, newValue: PythonEnvInfo | undefined): void;

/**
* Fires with details if the cache changes.
*/
onChanged: Event<BasicPythonEnvCollectionChangedEvent>;

/**
* Adds environment to cache.
*/
addEnv(env: PythonEnvInfo): void;
}
/**
* Environment info cache using persistent storage to save and retrieve pre-cached env info.
*/
export class EnvsCache extends PythonEnvsWatcher<PythonEnvCollectionChangedEvent> implements IEnvsCache {
private envs: PythonEnvInfo[] = [];

public getAllEnvs(): PythonEnvInfo[] {
return this.envs;
}

public addEnv(env: PythonEnvInfo): void {
const found = this.envs.find((e) => areSameEnv(e, env));
if (!found) {
this.envs.push(env);
this.fire({ new: env });
}
}

public updateEnv(oldValue: PythonEnvInfo, newValue: PythonEnvInfo | undefined): void {
const index = this.envs.findIndex((e) => areSameEnv(e, oldValue));
if (index !== -1) {
if (newValue === undefined) {
this.envs.splice(index, 1);
} else {
this.envs[index] = newValue;
}
this.fire({ old: oldValue, new: newValue });
}
}
}
65 changes: 65 additions & 0 deletions src/client/pythonEnvironments/createLocator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as vscode from 'vscode';
import { ExtensionState } from '../components';
import { BasicEnvInfo, IDiscoveryAPI, ILocator } from './base/locator';
import { WindowsPathEnvVarLocator } from './base/locators/lowLevel/windowsKnownPathsLocator';
import { WorkspaceVirtualEnvironmentLocator } from './base/locators/lowLevel/workspaceVirtualEnvLocator';
import { WatchRootsArgs, WorkspaceLocators } from './base/locators/wrappers';
import { CustomVirtualEnvironmentLocator } from './base/locators/lowLevel/customVirtualEnvLocator';
import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator';
import { GlobalVirtualEnvironmentLocator } from './base/locators/lowLevel/globalVirtualEnvronmentLocator';
import { PosixKnownPathsLocator } from './base/locators/lowLevel/posixKnownPathsLocator';
import { PyenvLocator } from './base/locators/lowLevel/pyenvLocator';
import { WindowsRegistryLocator } from './base/locators/lowLevel/windowsRegistryLocator';
import { MicrosoftStoreLocator } from './base/locators/lowLevel/microsoftStoreLocator';
import { PoetryLocator } from './base/locators/lowLevel/poetryLocator';
import {
createCollectionCache as createCache,
IEnvsCollectionCache,
} from './base/locators/composite/envsCollectionCache';
import { IDisposable } from '../common/types';
import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator';

export enum OSType {
Unknown = 'Unknown',
Windows = 'Windows',
OSX = 'OSX',
Linux = 'Linux',
}

// Return the OS type for the given platform string.
export function getOSType(platform: string = process.platform): OSType {
if (/^win/.test(platform)) {
return OSType.Windows;
}
if (/^darwin/.test(platform)) {
return OSType.OSX;
}
if (/^linux/.test(platform)) {
return OSType.Linux;
}
return OSType.Unknown;
}
/**
* Get the locator to use in the component.
*/

export async function createLocator(): Promise<IDiscoveryAPI> {
return ([] as unknown) as IDiscoveryAPI;
}
function watchRoots(args: WatchRootsArgs): IDisposable {
const { initRoot, addRoot, removeRoot } = args;

const folders = vscode.workspace.workspaceFolders;
if (folders) {
folders.map((f) => f.uri).forEach(initRoot);
}

return vscode.workspace.onDidChangeWorkspaceFolders((event) => {
for (const root of event.removed) {
removeRoot(root.uri);
}
for (const root of event.added) {
addRoot(root.uri);
}
});
}
Loading
Loading