Skip to content

Commit 81e363a

Browse files
legendecastargos
authored andcommitted
worker: add argv constructor option
A convenience option to populate `process.argv` in worker threads. PR-URL: #30559 Fixes: #30531 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Denys Otrishko <[email protected]>
1 parent 2cc0482 commit 81e363a

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

doc/api/worker_threads.md

+7
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,9 @@ changes:
510510
- version: REPLACEME
511511
pr-url: https://github.com/nodejs/node/pull/26628
512512
description: The `resourceLimits` option was introduced.
513+
- version: REPLACEME
514+
pr-url: https://github.com/nodejs/node/pull/30559
515+
description: The `argv` option was introduced.
513516
-->
514517

515518
* `filename` {string} The path to the Worker’s main script. Must be
@@ -518,6 +521,10 @@ changes:
518521
If `options.eval` is `true`, this is a string containing JavaScript code
519522
rather than a path.
520523
* `options` {Object}
524+
* `argv` {any[]} List of arguments which would be stringified and appended to
525+
`process.argv` in the worker. This is mostly similar to the `workerData`
526+
but the values will be available on the global `process.argv` as if they
527+
were passed as CLI options to the script.
521528
* `env` {Object} If set, specifies the initial value of `process.env` inside
522529
the Worker thread. As a special value, [`worker.SHARE_ENV`][] may be used
523530
to specify that the parent thread and the child thread should share their

lib/internal/main/worker_thread.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ if (process.env.NODE_CHANNEL_FD) {
9292
port.on('message', (message) => {
9393
if (message.type === LOAD_SCRIPT) {
9494
const {
95+
argv,
9596
cwdCounter,
9697
filename,
9798
doEval,
@@ -115,6 +116,9 @@ port.on('message', (message) => {
115116
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
116117
loadPreloadModules();
117118
initializeFrozenIntrinsics();
119+
if (argv !== undefined) {
120+
process.argv = process.argv.concat(argv);
121+
}
118122
publicWorker.parentPort = publicPort;
119123
publicWorker.workerData = workerData;
120124

@@ -138,12 +142,22 @@ port.on('message', (message) => {
138142
port.postMessage({ type: UP_AND_RUNNING });
139143
if (doEval) {
140144
const { evalScript } = require('internal/process/execution');
141-
evalScript('[worker eval]', filename);
145+
const name = '[worker eval]';
146+
// This is necessary for CJS module compilation.
147+
// TODO: pass this with something really internal.
148+
ObjectDefineProperty(process, '_eval', {
149+
configurable: true,
150+
enumerable: true,
151+
value: filename,
152+
});
153+
process.argv.splice(1, 0, name);
154+
evalScript(name, filename);
142155
} else {
143156
// script filename
144157
// runMain here might be monkey-patched by users in --require.
145158
// XXX: the monkey-patchability here should probably be deprecated.
146-
CJSLoader.Module.runMain(process.argv[1] = filename);
159+
process.argv.splice(1, 0, filename);
160+
CJSLoader.Module.runMain(filename);
147161
}
148162
} else if (message.type === STDIO_PAYLOAD) {
149163
const { stream, chunk, encoding } = message;

lib/internal/worker.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,16 @@ class Worker extends EventEmitter {
8686
validateString(filename, 'filename');
8787
if (options.execArgv && !ArrayIsArray(options.execArgv)) {
8888
throw new ERR_INVALID_ARG_TYPE('options.execArgv',
89-
'array',
89+
'Array',
9090
options.execArgv);
9191
}
92+
let argv;
93+
if (options.argv) {
94+
if (!ArrayIsArray(options.argv)) {
95+
throw new ERR_INVALID_ARG_TYPE('options.argv', 'Array', options.argv);
96+
}
97+
argv = options.argv.map(String);
98+
}
9299
if (!options.eval) {
93100
if (!path.isAbsolute(filename) && !/^\.\.?[\\/]/.test(filename)) {
94101
throw new ERR_WORKER_PATH(filename);
@@ -158,6 +165,7 @@ class Worker extends EventEmitter {
158165
this[kPublicPort].on('message', (message) => this.emit('message', message));
159166
setupPortReferencing(this[kPublicPort], this, 'message');
160167
this[kPort].postMessage({
168+
argv,
161169
type: messageTypes.LOAD_SCRIPT,
162170
filename,
163171
doEval: !!options.eval,
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { Worker, isMainThread, workerData } = require('worker_threads');
5+
6+
if (isMainThread) {
7+
assert.throws(() => {
8+
new Worker(__filename, { argv: 'foo' });
9+
}, {
10+
code: 'ERR_INVALID_ARG_TYPE'
11+
});
12+
13+
[
14+
new Worker(__filename, {
15+
argv: [null, 'foo', 123, Symbol('bar')],
16+
// Asserts only if the worker is started by the test.
17+
workerData: 'assert-argv'
18+
}),
19+
new Worker(`
20+
const assert = require('assert');
21+
assert.deepStrictEqual(
22+
process.argv,
23+
[process.execPath, '[worker eval]']
24+
);
25+
`, {
26+
eval: true
27+
}),
28+
new Worker(`
29+
const assert = require('assert');
30+
assert.deepStrictEqual(
31+
process.argv,
32+
[process.execPath, '[worker eval]', 'null', 'foo', '123',
33+
String(Symbol('bar'))]
34+
);
35+
`, {
36+
argv: [null, 'foo', 123, Symbol('bar')],
37+
eval: true
38+
})
39+
].forEach((worker) => {
40+
worker.on('exit', common.mustCall((code) => {
41+
assert.strictEqual(code, 0);
42+
}));
43+
});
44+
} else if (workerData === 'assert-argv') {
45+
assert.deepStrictEqual(
46+
process.argv,
47+
[process.execPath, __filename, 'null', 'foo', '123', String(Symbol('bar'))]
48+
);
49+
}

0 commit comments

Comments
 (0)