@mongosh/node-runtime-worker-thread 2.3.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +1 -0
- package/dist/153.js +1 -0
- package/dist/41.js +1 -0
- package/dist/502.js +1 -0
- package/dist/{578.js → 503.js} +1 -1
- package/dist/{43.js → 527.js} +1 -1
- package/dist/534.js +1 -0
- package/dist/711.js +1 -0
- package/dist/739.js +1 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/report.html +2 -2
- package/dist/rpc.d.ts +6 -15
- package/dist/rpc.js +7 -71
- package/dist/rpc.js.map +1 -1
- package/dist/worker-process-mongosh-bus.d.ts +7 -0
- package/dist/{child-process-mongosh-bus.js → worker-process-mongosh-bus.js} +8 -8
- package/dist/worker-process-mongosh-bus.js.map +1 -0
- package/dist/worker-runtime.js +16 -16
- package/dist/worker-runtime.js.map +1 -1
- package/dist/{child-process-evaluation-listener.d.ts → worker-thread-evaluation-listener.d.ts} +2 -4
- package/dist/{child-process-evaluation-listener.js → worker-thread-evaluation-listener.js} +6 -6
- package/dist/worker-thread-evaluation-listener.js.map +1 -0
- package/package.json +13 -12
- package/src/index.spec.ts +148 -147
- package/src/index.ts +98 -120
- package/src/lock.spec.ts +1 -1
- package/src/rpc.spec.ts +24 -90
- package/src/rpc.ts +17 -98
- package/src/{child-process-mongosh-bus.ts → worker-process-mongosh-bus.ts} +5 -6
- package/src/worker-runtime.spec.ts +19 -29
- package/src/worker-runtime.ts +15 -22
- package/src/{child-process-evaluation-listener.ts → worker-thread-evaluation-listener.ts} +3 -4
- package/tests/register-worker.js +1 -0
- package/tsconfig.json +2 -1
- package/webpack.config.js +4 -6
- package/__fixtures__/script-that-throws.js +0 -1
- package/dist/354.js +0 -1
- package/dist/528.js +0 -1
- package/dist/650.js +0 -1
- package/dist/722.js +0 -1
- package/dist/777.js +0 -1
- package/dist/942.js +0 -1
- package/dist/child-process-evaluation-listener.js.map +0 -1
- package/dist/child-process-mongosh-bus.d.ts +0 -9
- package/dist/child-process-mongosh-bus.js.map +0 -1
- package/dist/child-process-proxy.d.ts +0 -1
- package/dist/child-process-proxy.js +0 -1
- package/dist/child-process-proxy.js.map +0 -1
- package/dist/spawn-child-from-source.d.ts +0 -5
- package/dist/spawn-child-from-source.js +0 -74
- package/dist/spawn-child-from-source.js.map +0 -1
- package/src/child-process-proxy.spec.ts +0 -84
- package/src/child-process-proxy.ts +0 -124
- package/src/spawn-child-from-source.spec.ts +0 -90
- package/src/spawn-child-from-source.ts +0 -102
- package/tsconfig.test.json +0 -11
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/* istanbul ignore file */
|
|
2
|
-
/* ^^^ we test the dist directly, so isntanbul can't calculate the coverage correctly */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* This proxy is needed as a workaround for the old electron verison "bug" where
|
|
6
|
-
* due to the electron runtime being a chromium, not just node (even with
|
|
7
|
-
* `ELECTRON_RUN_AS_NODE` enabled), SIGINT doesn't break code execution. This is
|
|
8
|
-
* fixed in the later versions of electron/node but we are still on the older
|
|
9
|
-
* one, we have to have this proxy in place
|
|
10
|
-
*
|
|
11
|
-
* @todo as soon as we update electron version in compass, we can get rid of
|
|
12
|
-
* this part of the worker runtime as it becomes redundant
|
|
13
|
-
*
|
|
14
|
-
* @see {@link https://github.com/nodejs/node/pull/36344}
|
|
15
|
-
*/
|
|
16
|
-
import { once } from 'events';
|
|
17
|
-
import { SHARE_ENV, Worker } from 'worker_threads';
|
|
18
|
-
import path from 'path';
|
|
19
|
-
import { exposeAll, createCaller } from './rpc';
|
|
20
|
-
import type { InterruptHandle } from 'interruptor';
|
|
21
|
-
import { interrupt as nativeInterrupt } from 'interruptor';
|
|
22
|
-
|
|
23
|
-
const workerRuntimeSrcPath =
|
|
24
|
-
process.env.WORKER_RUNTIME_SRC_PATH_DO_NOT_USE_THIS_EXCEPT_FOR_TESTING ||
|
|
25
|
-
path.resolve(__dirname, 'worker-runtime.js');
|
|
26
|
-
|
|
27
|
-
const workerProcess = new Worker(workerRuntimeSrcPath, { env: SHARE_ENV });
|
|
28
|
-
|
|
29
|
-
const workerReadyPromise: Promise<void> = (async () => {
|
|
30
|
-
const waitForReadyMessage = async () => {
|
|
31
|
-
let msg: string;
|
|
32
|
-
while (([msg] = await once(workerProcess, 'message'))) {
|
|
33
|
-
if (msg === 'ready') return;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const waitForError = async () => {
|
|
38
|
-
const [err] = await once(workerProcess, 'error');
|
|
39
|
-
if (err) {
|
|
40
|
-
err.message = `Worker thread failed to start with the following error: ${err.message}`;
|
|
41
|
-
throw err;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
await Promise.race([waitForReadyMessage(), waitForError()]);
|
|
46
|
-
})();
|
|
47
|
-
|
|
48
|
-
// We expect the amount of listeners to be more than the default value of 10 but
|
|
49
|
-
// probably not more than ~25 (all exposed methods on
|
|
50
|
-
// ChildProcessEvaluationListener and ChildProcessMongoshBus + any concurrent
|
|
51
|
-
// in-flight calls on ChildProcessRuntime) at once
|
|
52
|
-
process.setMaxListeners(25);
|
|
53
|
-
workerProcess.setMaxListeners(25);
|
|
54
|
-
|
|
55
|
-
let interruptHandle: InterruptHandle | null = null;
|
|
56
|
-
|
|
57
|
-
const { interrupt } = createCaller(['interrupt'], workerProcess);
|
|
58
|
-
|
|
59
|
-
const worker = Object.assign(
|
|
60
|
-
createCaller(
|
|
61
|
-
['init', 'evaluate', 'getCompletions', 'getShellPrompt'],
|
|
62
|
-
workerProcess
|
|
63
|
-
),
|
|
64
|
-
{
|
|
65
|
-
interrupt(): boolean {
|
|
66
|
-
if (interruptHandle) {
|
|
67
|
-
nativeInterrupt(interruptHandle);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return interrupt();
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
function waitForWorkerReadyProxy<T extends Function>(fn: T): T {
|
|
77
|
-
return new Proxy(fn, {
|
|
78
|
-
async apply(target, thisArg, argumentsList) {
|
|
79
|
-
await workerReadyPromise;
|
|
80
|
-
return target.call(thisArg, ...Array.from(argumentsList));
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Every time parent process wants to request something from worker through
|
|
86
|
-
// proxy, we want to make sure worker process is ready
|
|
87
|
-
(Object.keys(worker) as (keyof typeof worker)[]).forEach((key) => {
|
|
88
|
-
worker[key] = waitForWorkerReadyProxy(worker[key]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
exposeAll(worker, process);
|
|
92
|
-
|
|
93
|
-
const evaluationListener = Object.assign(
|
|
94
|
-
createCaller(
|
|
95
|
-
[
|
|
96
|
-
'onPrint',
|
|
97
|
-
'onPrompt',
|
|
98
|
-
'getConfig',
|
|
99
|
-
'setConfig',
|
|
100
|
-
'resetConfig',
|
|
101
|
-
'validateConfig',
|
|
102
|
-
'listConfigOptions',
|
|
103
|
-
'onClearCommand',
|
|
104
|
-
'onExit',
|
|
105
|
-
],
|
|
106
|
-
process
|
|
107
|
-
),
|
|
108
|
-
{
|
|
109
|
-
onRunInterruptible(handle: InterruptHandle | null) {
|
|
110
|
-
interruptHandle = handle;
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
exposeAll(evaluationListener, workerProcess);
|
|
116
|
-
|
|
117
|
-
const messageBus = createCaller(['emit', 'on'], process);
|
|
118
|
-
|
|
119
|
-
exposeAll(messageBus, workerProcess);
|
|
120
|
-
|
|
121
|
-
process.once('disconnect', () => process.exit());
|
|
122
|
-
process.nextTick(() => {
|
|
123
|
-
process.send?.('ready');
|
|
124
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import type { ChildProcess } from 'child_process';
|
|
3
|
-
import childProcess from 'child_process';
|
|
4
|
-
import { once } from 'events';
|
|
5
|
-
import spawnChildFromSource, { kill } from './spawn-child-from-source';
|
|
6
|
-
|
|
7
|
-
describe('spawnChildFromSource', function () {
|
|
8
|
-
let spawned: ChildProcess;
|
|
9
|
-
|
|
10
|
-
afterEach(async function () {
|
|
11
|
-
if (spawned) {
|
|
12
|
-
await kill(spawned, 'SIGKILL');
|
|
13
|
-
spawned = null;
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should throw if stdin is missing', async function () {
|
|
18
|
-
let err: Error;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
spawned = await spawnChildFromSource('console.log("Hi")', {
|
|
22
|
-
// Making istanbul happy by passing stuff that's not allowed
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
24
|
-
// @ts-expect-error
|
|
25
|
-
stdio: 'ignore',
|
|
26
|
-
});
|
|
27
|
-
} catch (e: any) {
|
|
28
|
-
err = e;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
expect(err).to.be.instanceof(Error);
|
|
32
|
-
expect(err)
|
|
33
|
-
.to.have.property('message')
|
|
34
|
-
.match(/missing stdin/);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should resolve with a child process', async function () {
|
|
38
|
-
spawned = await spawnChildFromSource('');
|
|
39
|
-
expect(spawned).to.be.instanceof((childProcess as any).ChildProcess);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should spawn a process with an ipc channel open', async function () {
|
|
43
|
-
spawned = await spawnChildFromSource(
|
|
44
|
-
'process.on("message", (data) => process.send(data))'
|
|
45
|
-
);
|
|
46
|
-
spawned.send('Hi!');
|
|
47
|
-
const [message] = await once(spawned, 'message');
|
|
48
|
-
expect(message).to.equal('Hi!');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should fail if process exited before successfully starting', async function () {
|
|
52
|
-
let err: Error;
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
spawned = await spawnChildFromSource(
|
|
56
|
-
'throw new Error("Whoops!")',
|
|
57
|
-
{},
|
|
58
|
-
undefined,
|
|
59
|
-
'ignore',
|
|
60
|
-
'ignore'
|
|
61
|
-
);
|
|
62
|
-
} catch (e: any) {
|
|
63
|
-
err = e;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
expect(err).to.be.instanceof(Error);
|
|
67
|
-
expect(err.message).to.match(
|
|
68
|
-
/Child process exited with error before starting/
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should fail if a timeout exceeded before the process is "ready"', async function () {
|
|
73
|
-
let err: Error;
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
spawned = await spawnChildFromSource(
|
|
77
|
-
'let i = 0; while(++i < 10000000000){};',
|
|
78
|
-
{},
|
|
79
|
-
10
|
|
80
|
-
);
|
|
81
|
-
} catch (e: any) {
|
|
82
|
-
err = e;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
expect(err).to.be.instanceof(Error);
|
|
86
|
-
expect(err.message).to.match(
|
|
87
|
-
/Timed out while waiting for child process to start/
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChildProcess,
|
|
3
|
-
Serializable,
|
|
4
|
-
SpawnOptions,
|
|
5
|
-
StdioNull,
|
|
6
|
-
StdioPipe,
|
|
7
|
-
} from 'child_process';
|
|
8
|
-
import { spawn } from 'child_process';
|
|
9
|
-
import { once } from 'events';
|
|
10
|
-
|
|
11
|
-
export async function kill(
|
|
12
|
-
childProcess: ChildProcess,
|
|
13
|
-
code: NodeJS.Signals | number = 'SIGTERM'
|
|
14
|
-
) {
|
|
15
|
-
childProcess.kill(code);
|
|
16
|
-
if (childProcess.exitCode === null && childProcess.signalCode === null) {
|
|
17
|
-
await once(childProcess, 'exit');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default function spawnChildFromSource(
|
|
22
|
-
src: string,
|
|
23
|
-
spawnOptions: Omit<SpawnOptions, 'stdio'> = {},
|
|
24
|
-
timeoutMs?: number,
|
|
25
|
-
_stdout: StdioNull | StdioPipe = 'inherit',
|
|
26
|
-
_stderr: StdioNull | StdioPipe = 'inherit'
|
|
27
|
-
): Promise<ChildProcess> {
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
29
|
-
const readyToken = Date.now().toString(32);
|
|
30
|
-
|
|
31
|
-
const childProcess = spawn(process.execPath, {
|
|
32
|
-
stdio: ['pipe', _stdout, _stderr, 'ipc'],
|
|
33
|
-
...spawnOptions,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
if (!childProcess.stdin) {
|
|
37
|
-
kill(childProcess)
|
|
38
|
-
.then(() => {
|
|
39
|
-
reject(
|
|
40
|
-
new Error("Can't write src to the spawned process, missing stdin")
|
|
41
|
-
);
|
|
42
|
-
})
|
|
43
|
-
.catch((err: any) => {
|
|
44
|
-
reject(err);
|
|
45
|
-
});
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// eslint-disable-next-line prefer-const
|
|
50
|
-
let timeoutId: NodeJS.Timeout | null;
|
|
51
|
-
|
|
52
|
-
function cleanupListeners() {
|
|
53
|
-
if (timeoutId) {
|
|
54
|
-
clearTimeout(timeoutId);
|
|
55
|
-
}
|
|
56
|
-
if (childProcess.stdin) {
|
|
57
|
-
childProcess.stdin.off('error', onWriteError);
|
|
58
|
-
}
|
|
59
|
-
childProcess.off('message', onMessage);
|
|
60
|
-
childProcess.off('exit', onExit);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function onExit(exitCode: number | null) {
|
|
64
|
-
if (exitCode && exitCode > 0) {
|
|
65
|
-
cleanupListeners();
|
|
66
|
-
reject(new Error('Child process exited with error before starting'));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* really hard to reproduce in tests and coverage is not happy */
|
|
71
|
-
/* istanbul ignore next */
|
|
72
|
-
async function onWriteError(error: Error) {
|
|
73
|
-
cleanupListeners();
|
|
74
|
-
await kill(childProcess);
|
|
75
|
-
reject(error);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function onTimeout() {
|
|
79
|
-
cleanupListeners();
|
|
80
|
-
await kill(childProcess);
|
|
81
|
-
reject(new Error('Timed out while waiting for child process to start'));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function onMessage(data: Serializable) {
|
|
85
|
-
if (data === readyToken) {
|
|
86
|
-
cleanupListeners();
|
|
87
|
-
resolve(childProcess);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
childProcess.on('message', onMessage);
|
|
92
|
-
childProcess.on('exit', onExit);
|
|
93
|
-
childProcess.stdin.on('error', onWriteError);
|
|
94
|
-
|
|
95
|
-
childProcess.stdin.write(src);
|
|
96
|
-
childProcess.stdin.write(`;process.send(${JSON.stringify(readyToken)})`);
|
|
97
|
-
childProcess.stdin.end();
|
|
98
|
-
|
|
99
|
-
timeoutId =
|
|
100
|
-
timeoutMs !== undefined ? setTimeout(onTimeout, timeoutMs) : null;
|
|
101
|
-
});
|
|
102
|
-
}
|