Skip to content

Commit f6e1338

Browse files
author
Kartik Raj
authored
Use worker threads for fetching conda environments and interpreter related info (#22481)
1 parent 3c552f9 commit f6e1338

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+537
-91
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ jobs:
282282
shell: pwsh
283283
if: matrix.test-suite == 'venv'
284284
run: |
285-
# 1. For `terminalActivation.testvirtualenvs.test.ts`
285+
# 1. For `*.testvirtualenvs.test.ts`
286286
if ('${{ matrix.os }}' -match 'windows-latest') {
287287
$condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe
288288
$condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda

.github/workflows/pr-check.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ jobs:
255255
shell: pwsh
256256
if: matrix.test-suite == 'venv'
257257
run: |
258-
# 1. For `terminalActivation.testvirtualenvs.test.ts`
258+
# 1. For `*.testvirtualenvs.test.ts`
259259
if ('${{ matrix.os }}' -match 'windows-latest') {
260260
$condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe
261261
$condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda
@@ -451,7 +451,7 @@ jobs:
451451
PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json'
452452
shell: pwsh
453453
run: |
454-
# 1. For `terminalActivation.testvirtualenvs.test.ts`
454+
# 1. For `*.testvirtualenvs.test.ts`
455455
if ('${{ matrix.os }}' -match 'windows-latest') {
456456
$condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe
457457
$condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ log.log
55
**/node_modules
66
*.pyc
77
*.vsix
8+
envVars.txt
89
**/.vscode/.ropeproject/**
910
**/testFiles/**/.cache/**
1011
*.noseids

.vscode/settings.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"[python]": {
2626
"editor.formatOnSave": true,
2727
"editor.codeActionsOnSave": {
28-
"source.organizeImports.isort": true
28+
"source.fixAll.eslint": "explicit",
29+
"source.organizeImports.isort": "explicit"
2930
},
3031
"editor.defaultFormatter": "ms-python.black-formatter",
3132
},
@@ -51,7 +52,7 @@
5152
"prettier.printWidth": 120,
5253
"prettier.singleQuote": true,
5354
"editor.codeActionsOnSave": {
54-
"source.fixAll.eslint": true
55+
"source.fixAll.eslint": "explicit"
5556
},
5657
"python.languageServer": "Default",
5758
"typescript.preferences.importModuleSpecifier": "relative",

build/webpack/webpack.extension.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const config = {
1919
target: 'node',
2020
entry: {
2121
extension: './src/client/extension.ts',
22+
'shellExec.worker': './src/client/common/process/worker/shellExec.worker.ts',
23+
'plainExec.worker': './src/client/common/process/worker/plainExec.worker.ts',
24+
'registryKeys.worker': 'src/client/pythonEnvironments/common/registryKeys.worker.ts',
25+
'registryValues.worker': 'src/client/pythonEnvironments/common/registryValues.worker.ts',
2226
},
2327
devtool: 'source-map',
2428
node: {
@@ -51,6 +55,10 @@ const config = {
5155
},
5256
],
5357
},
58+
{
59+
test: /\.worker\.js$/,
60+
use: { loader: 'worker-loader' },
61+
},
5462
],
5563
},
5664
externals: [

package-lock.json

+31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,7 @@
16651665
"typescript": "4.5.5",
16661666
"uuid": "^8.3.2",
16671667
"webpack": "^5.76.0",
1668+
"worker-loader": "^3.0.8",
16681669
"webpack-bundle-analyzer": "^4.5.0",
16691670
"webpack-cli": "^4.9.2",
16701671
"webpack-fix-default-import-plugin": "^1.0.3",

src/client/common/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export namespace ThemeIcons {
9494

9595
export const DEFAULT_INTERPRETER_SETTING = 'python';
9696

97-
export const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined;
97+
export const isCI =
98+
process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined || process.env.GITHUB_ACTIONS === 'true';
9899

99100
export function isTestExecution(): boolean {
100101
return process.env.VSC_PYTHON_CI_TEST === '1' || isUnitTestExecution();

src/client/common/process/proc.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { IDisposable } from '../types';
77
import { EnvironmentVariables } from '../variables/types';
88
import { execObservable, killPid, plainExec, shellExec } from './rawProcessApis';
99
import { ExecutionResult, IProcessService, ObservableExecutionResult, ShellOptions, SpawnOptions } from './types';
10+
import { workerPlainExec, workerShellExec } from './worker/rawProcessApiWrapper';
1011

1112
export class ProcessService extends EventEmitter implements IProcessService {
1213
private processesToKill = new Set<IDisposable>();
@@ -47,14 +48,20 @@ export class ProcessService extends EventEmitter implements IProcessService {
4748
}
4849

4950
public exec(file: string, args: string[], options: SpawnOptions = {}): Promise<ExecutionResult<string>> {
51+
this.emit('exec', file, args, options);
52+
if (options.useWorker) {
53+
return workerPlainExec(file, args, options);
54+
}
5055
const execOptions = { ...options, doNotLog: true };
5156
const promise = plainExec(file, args, execOptions, this.env, this.processesToKill);
52-
this.emit('exec', file, args, options);
5357
return promise;
5458
}
5559

5660
public shellExec(command: string, options: ShellOptions = {}): Promise<ExecutionResult<string>> {
5761
this.emit('exec', command, undefined, options);
62+
if (options.useWorker) {
63+
return workerShellExec(command, options);
64+
}
5865
const disposables = new Set<IDisposable>();
5966
const shellOptions = { ...options, doNotLog: true };
6067
return shellExec(command, shellOptions, this.env, disposables).finally(() => {

src/client/common/process/rawProcessApis.ts

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export function shellExec(
5656
disposables?: Set<IDisposable>,
5757
): Promise<ExecutionResult<string>> {
5858
const shellOptions = getDefaultOptions(options, defaultEnv);
59-
traceVerbose(`Shell Exec: ${command} with options: ${JSON.stringify(shellOptions, null, 4)}`);
6059
if (!options.doNotLog) {
6160
const processLogger = new ProcessLogger(new WorkspaceService());
6261
processLogger.logProcess(command, undefined, shellOptions);

src/client/common/process/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export type SpawnOptions = ChildProcessSpawnOptions & {
2626
extraVariables?: NodeJS.ProcessEnv;
2727
outputChannel?: OutputChannel;
2828
stdinStr?: string;
29+
useWorker?: boolean;
2930
};
3031

31-
export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean };
32+
export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean; useWorker?: boolean };
3233

3334
export type ExecutionResult<T extends string | Buffer> = {
3435
stdout: T;
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { Worker } from 'worker_threads';
5+
import * as path from 'path';
6+
import { traceVerbose, traceError } from '../../../logging/index';
7+
8+
/**
9+
* Executes a worker file. Make sure to declare the worker file as a entry in the webpack config.
10+
* @param workerFileName Filename of the worker file to execute, it has to end with ".worker.js" for webpack to bundle it.
11+
* @param workerData Arguments to the worker file.
12+
* @returns
13+
*/
14+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
15+
export async function executeWorkerFile(workerFileName: string, workerData: any): Promise<any> {
16+
if (!workerFileName.endsWith('.worker.js')) {
17+
throw new Error('Worker file must end with ".worker.js" for webpack to bundle webworkers');
18+
}
19+
return new Promise((resolve, reject) => {
20+
const worker = new Worker(workerFileName, { workerData });
21+
const id = worker.threadId;
22+
traceVerbose(
23+
`Worker id ${id} for file ${path.basename(workerFileName)} with data ${JSON.stringify(workerData)}`,
24+
);
25+
worker.on('message', (msg: { err: Error; res: unknown }) => {
26+
if (msg.err) {
27+
reject(msg.err);
28+
}
29+
resolve(msg.res);
30+
});
31+
worker.on('error', (ex: Error) => {
32+
traceError(`Error in worker ${workerFileName}`, ex);
33+
reject(ex);
34+
});
35+
worker.on('exit', (code) => {
36+
traceVerbose(`Worker id ${id} exited with code ${code}`);
37+
if (code !== 0) {
38+
reject(new Error(`Worker ${workerFileName} stopped with exit code ${code}`));
39+
}
40+
});
41+
});
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { parentPort, workerData } from 'worker_threads';
2+
import { _workerPlainExecImpl } from './workerRawProcessApis';
3+
4+
_workerPlainExecImpl(workerData.file, workerData.args, workerData.options)
5+
.then((res) => {
6+
if (!parentPort) {
7+
throw new Error('Not in a worker thread');
8+
}
9+
parentPort.postMessage({ res });
10+
})
11+
.catch((err) => {
12+
if (!parentPort) {
13+
throw new Error('Not in a worker thread');
14+
}
15+
parentPort.postMessage({ err });
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { SpawnOptions } from 'child_process';
5+
import * as path from 'path';
6+
import { executeWorkerFile } from './main';
7+
import { ExecutionResult, ShellOptions } from './types';
8+
9+
export function workerShellExec(command: string, options: ShellOptions): Promise<ExecutionResult<string>> {
10+
return executeWorkerFile(path.join(__dirname, 'shellExec.worker.js'), {
11+
command,
12+
options,
13+
});
14+
}
15+
16+
export function workerPlainExec(
17+
file: string,
18+
args: string[],
19+
options: SpawnOptions = {},
20+
): Promise<ExecutionResult<string>> {
21+
return executeWorkerFile(path.join(__dirname, 'plainExec.worker.js'), {
22+
file,
23+
args,
24+
options,
25+
});
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { parentPort, workerData } from 'worker_threads';
2+
import { _workerShellExecImpl } from './workerRawProcessApis';
3+
4+
_workerShellExecImpl(workerData.command, workerData.options, workerData.defaultEnv)
5+
.then((res) => {
6+
if (!parentPort) {
7+
throw new Error('Not in a worker thread');
8+
}
9+
parentPort.postMessage({ res });
10+
})
11+
.catch((ex) => {
12+
if (!parentPort) {
13+
throw new Error('Not in a worker thread');
14+
}
15+
parentPort.postMessage({ ex });
16+
});
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* eslint-disable @typescript-eslint/no-empty-function */
2+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
3+
import { ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process';
4+
5+
export function noop() {}
6+
export interface IDisposable {
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
dispose(): void | undefined | Promise<void>;
9+
}
10+
export type EnvironmentVariables = Record<string, string | undefined>;
11+
export class StdErrError extends Error {
12+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
13+
constructor(message: string) {
14+
super(message);
15+
}
16+
}
17+
18+
export type SpawnOptions = ChildProcessSpawnOptions & {
19+
encoding?: string;
20+
// /**
21+
// * Can't use `CancellationToken` here as it comes from vscode which is not available in worker threads.
22+
// */
23+
// token?: CancellationToken;
24+
mergeStdOutErr?: boolean;
25+
throwOnStdErr?: boolean;
26+
extraVariables?: NodeJS.ProcessEnv;
27+
// /**
28+
// * Can't use `OutputChannel` here as it comes from vscode which is not available in worker threads.
29+
// */
30+
// outputChannel?: OutputChannel;
31+
stdinStr?: string;
32+
};
33+
export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean };
34+
35+
export type ExecutionResult<T extends string | Buffer> = {
36+
stdout: T;
37+
stderr?: T;
38+
};

0 commit comments

Comments
 (0)