From 4ea1b816a42dd9f04f37bc2b0e51fac8cab00737 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 21 Nov 2019 00:55:36 +0800 Subject: [PATCH] worker: add argv constructor option A convenience option to populate `process.argv` in worker threads. PR-URL: https://github.com/nodejs/node/pull/30559 Fixes: https://github.com/nodejs/node/issues/30531 Reviewed-By: Anna Henningsen Reviewed-By: Gireesh Punathil Reviewed-By: Denys Otrishko --- doc/api/worker_threads.md | 7 ++++ lib/internal/main/worker_thread.js | 18 ++++++++- lib/internal/worker.js | 10 ++++- test/parallel/test-worker-process-argv.js | 49 +++++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-worker-process-argv.js diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index a584b631696d26..c7e6b1898c416e 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -510,6 +510,9 @@ changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/26628 description: The `resourceLimits` option was introduced. + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/30559 + description: The `argv` option was introduced. --> * `filename` {string} The path to the Worker’s main script. Must be @@ -518,6 +521,10 @@ changes: If `options.eval` is `true`, this is a string containing JavaScript code rather than a path. * `options` {Object} + * `argv` {any[]} List of arguments which would be stringified and appended to + `process.argv` in the worker. This is mostly similar to the `workerData` + but the values will be available on the global `process.argv` as if they + were passed as CLI options to the script. * `env` {Object} If set, specifies the initial value of `process.env` inside the Worker thread. As a special value, [`worker.SHARE_ENV`][] may be used to specify that the parent thread and the child thread should share their diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 395aa60819d1a7..49fad1b5c30513 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -92,6 +92,7 @@ if (process.env.NODE_CHANNEL_FD) { port.on('message', (message) => { if (message.type === LOAD_SCRIPT) { const { + argv, cwdCounter, filename, doEval, @@ -115,6 +116,9 @@ port.on('message', (message) => { assert(!CJSLoader.hasLoadedAnyUserCJSModule); loadPreloadModules(); initializeFrozenIntrinsics(); + if (argv !== undefined) { + process.argv = process.argv.concat(argv); + } publicWorker.parentPort = publicPort; publicWorker.workerData = workerData; @@ -138,12 +142,22 @@ port.on('message', (message) => { port.postMessage({ type: UP_AND_RUNNING }); if (doEval) { const { evalScript } = require('internal/process/execution'); - evalScript('[worker eval]', filename); + const name = '[worker eval]'; + // This is necessary for CJS module compilation. + // TODO: pass this with something really internal. + ObjectDefineProperty(process, '_eval', { + configurable: true, + enumerable: true, + value: filename, + }); + process.argv.splice(1, 0, name); + evalScript(name, filename); } else { // script filename // runMain here might be monkey-patched by users in --require. // XXX: the monkey-patchability here should probably be deprecated. - CJSLoader.Module.runMain(process.argv[1] = filename); + process.argv.splice(1, 0, filename); + CJSLoader.Module.runMain(filename); } } else if (message.type === STDIO_PAYLOAD) { const { stream, chunk, encoding } = message; diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 0488dc2d781446..f078d5329d6017 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -86,9 +86,16 @@ class Worker extends EventEmitter { validateString(filename, 'filename'); if (options.execArgv && !ArrayIsArray(options.execArgv)) { throw new ERR_INVALID_ARG_TYPE('options.execArgv', - 'array', + 'Array', options.execArgv); } + let argv; + if (options.argv) { + if (!ArrayIsArray(options.argv)) { + throw new ERR_INVALID_ARG_TYPE('options.argv', 'Array', options.argv); + } + argv = options.argv.map(String); + } if (!options.eval) { if (!path.isAbsolute(filename) && !/^\.\.?[\\/]/.test(filename)) { throw new ERR_WORKER_PATH(filename); @@ -158,6 +165,7 @@ class Worker extends EventEmitter { this[kPublicPort].on('message', (message) => this.emit('message', message)); setupPortReferencing(this[kPublicPort], this, 'message'); this[kPort].postMessage({ + argv, type: messageTypes.LOAD_SCRIPT, filename, doEval: !!options.eval, diff --git a/test/parallel/test-worker-process-argv.js b/test/parallel/test-worker-process-argv.js new file mode 100644 index 00000000000000..ebdebe02ab855c --- /dev/null +++ b/test/parallel/test-worker-process-argv.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Worker, isMainThread, workerData } = require('worker_threads'); + +if (isMainThread) { + assert.throws(() => { + new Worker(__filename, { argv: 'foo' }); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); + + [ + new Worker(__filename, { + argv: [null, 'foo', 123, Symbol('bar')], + // Asserts only if the worker is started by the test. + workerData: 'assert-argv' + }), + new Worker(` + const assert = require('assert'); + assert.deepStrictEqual( + process.argv, + [process.execPath, '[worker eval]'] + ); + `, { + eval: true + }), + new Worker(` + const assert = require('assert'); + assert.deepStrictEqual( + process.argv, + [process.execPath, '[worker eval]', 'null', 'foo', '123', + String(Symbol('bar'))] + ); + `, { + argv: [null, 'foo', 123, Symbol('bar')], + eval: true + }) + ].forEach((worker) => { + worker.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); + }); +} else if (workerData === 'assert-argv') { + assert.deepStrictEqual( + process.argv, + [process.execPath, __filename, 'null', 'foo', '123', String(Symbol('bar'))] + ); +}