Skip to content

Commit 219b1b8

Browse files
yaelhetargos
authored andcommitted
worker: enable passing command line flags
This PR adds the ability to provide Workers with their own execArgv flags in replacement of the main thread's execArgv. Only per-Isolate/per-Environment options are allowed. Per-Process options and V8 flags are not allowed. Passing an empty execArgv array will reset per-Isolate and per-Environment options of the Worker to their defaults. If execArgv option is not passed, the Worker will get the same flags as the main thread. Usage example: ``` const worker = new Worker(__filename, { execArgv: ['--trace-warnings'], }); ``` PR-URL: #25467 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent da8c526 commit 219b1b8

11 files changed

+167
-9
lines changed

doc/api/errors.md

+6
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,12 @@ The fulfilled value of a linking promise is not a `vm.SourceTextModule` object.
19031903
The current module's status does not allow for this operation. The specific
19041904
meaning of the error depends on the specific function.
19051905

1906+
<a id="ERR_WORKER_INVALID_EXEC_ARGV"></a>
1907+
### ERR_WORKER_INVALID_EXEC_ARGV
1908+
1909+
The `execArgv` option passed to the `Worker` constructor contains
1910+
invalid flags.
1911+
19061912
<a id="ERR_WORKER_PATH"></a>
19071913
### ERR_WORKER_PATH
19081914

doc/api/worker_threads.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -316,13 +316,16 @@ if (isMainThread) {
316316
occur as described in the [HTML structured clone algorithm][], and an error
317317
will be thrown if the object cannot be cloned (e.g. because it contains
318318
`function`s).
319-
* stdin {boolean} If this is set to `true`, then `worker.stdin` will
319+
* `stdin` {boolean} If this is set to `true`, then `worker.stdin` will
320320
provide a writable stream whose contents will appear as `process.stdin`
321321
inside the Worker. By default, no data is provided.
322-
* stdout {boolean} If this is set to `true`, then `worker.stdout` will
322+
* `stdout` {boolean} If this is set to `true`, then `worker.stdout` will
323323
not automatically be piped through to `process.stdout` in the parent.
324-
* stderr {boolean} If this is set to `true`, then `worker.stderr` will
324+
* `stderr` {boolean} If this is set to `true`, then `worker.stderr` will
325325
not automatically be piped through to `process.stderr` in the parent.
326+
* `execArgv` {string[]} List of node CLI options passed to the worker.
327+
V8 options (such as `--max-old-space-size`) and options that affect the
328+
process (such as `--title`) are not supported.
326329

327330
### Event: 'error'
328331
<!-- YAML

lib/internal/errors.js

+3
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,9 @@ E('ERR_VM_MODULE_NOT_LINKED',
992992
E('ERR_VM_MODULE_NOT_MODULE',
993993
'Provided module is not an instance of Module', Error);
994994
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
995+
E('ERR_WORKER_INVALID_EXEC_ARGV', (errors) =>
996+
`Initiated Worker with invalid execArgv flags: ${errors.join(', ')}`,
997+
Error);
995998
E('ERR_WORKER_PATH',
996999
'The worker script filename must be an absolute path or a relative ' +
9971000
'path starting with \'./\' or \'../\'. Received "%s"',

lib/internal/worker.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const {
88
ERR_WORKER_PATH,
99
ERR_WORKER_UNSERIALIZABLE_ERROR,
1010
ERR_WORKER_UNSUPPORTED_EXTENSION,
11+
ERR_WORKER_INVALID_EXEC_ARGV,
12+
ERR_INVALID_ARG_TYPE,
1113
} = require('internal/errors').codes;
1214
const { validateString } = require('internal/validators');
1315

@@ -49,7 +51,11 @@ class Worker extends EventEmitter {
4951
super();
5052
debug(`[${threadId}] create new worker`, filename, options);
5153
validateString(filename, 'filename');
52-
54+
if (options.execArgv && !Array.isArray(options.execArgv)) {
55+
throw new ERR_INVALID_ARG_TYPE('options.execArgv',
56+
'array',
57+
options.execArgv);
58+
}
5359
if (!options.eval) {
5460
if (!path.isAbsolute(filename) &&
5561
!filename.startsWith('./') &&
@@ -68,7 +74,10 @@ class Worker extends EventEmitter {
6874

6975
const url = options.eval ? null : pathToFileURL(filename);
7076
// Set up the C++ handle for the worker, as well as some internal wiring.
71-
this[kHandle] = new WorkerImpl(url);
77+
this[kHandle] = new WorkerImpl(url, options.execArgv);
78+
if (this[kHandle].invalidExecArgv) {
79+
throw new ERR_WORKER_INVALID_EXEC_ARGV(this[kHandle].invalidExecArgv);
80+
}
7281
this[kHandle].onexit = (code) => this[kOnExit](code);
7382
this[kPort] = this[kHandle].messagePort;
7483
this[kPort].on('message', (data) => this[kOnMessage](data));

src/env-inl.h

+5
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,11 @@ inline std::shared_ptr<PerIsolateOptions> IsolateData::options() {
595595
return options_;
596596
}
597597

598+
inline void IsolateData::set_options(
599+
std::shared_ptr<PerIsolateOptions> options) {
600+
options_ = options;
601+
}
602+
598603
void Environment::CreateImmediate(native_immediate_callback cb,
599604
void* data,
600605
v8::Local<v8::Object> obj,

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ class IsolateData {
392392
inline uint32_t* zero_fill_field() const;
393393
inline MultiIsolatePlatform* platform() const;
394394
inline std::shared_ptr<PerIsolateOptions> options();
395+
inline void set_options(std::shared_ptr<PerIsolateOptions> options);
395396

396397
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
397398
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)

src/node_worker.cc

+64-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
#include "async_wrap-inl.h"
1010

1111
#include <string>
12+
#include <vector>
1213

14+
using node::options_parser::kDisallowedInEnvironment;
1315
using v8::ArrayBuffer;
1416
using v8::Context;
1517
using v8::Function;
@@ -67,7 +69,10 @@ void WaitForWorkerInspectorToStop(Environment* child) {}
6769

6870
} // anonymous namespace
6971

70-
Worker::Worker(Environment* env, Local<Object> wrap, const std::string& url)
72+
Worker::Worker(Environment* env,
73+
Local<Object> wrap,
74+
const std::string& url,
75+
std::shared_ptr<PerIsolateOptions> per_isolate_opts)
7176
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER), url_(url) {
7277
// Generate a new thread id.
7378
{
@@ -112,6 +117,9 @@ Worker::Worker(Environment* env, Local<Object> wrap, const std::string& url)
112117
&loop_,
113118
env->isolate_data()->platform(),
114119
array_buffer_allocator_.get()));
120+
if (per_isolate_opts != nullptr) {
121+
isolate_data_->set_options(per_isolate_opts);
122+
}
115123
CHECK(isolate_data_);
116124

117125
Local<Context> context = NewContext(isolate_);
@@ -390,14 +398,67 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
390398
}
391399

392400
std::string url;
401+
std::shared_ptr<PerIsolateOptions> per_isolate_opts = nullptr;
402+
393403
// Argument might be a string or URL
394-
if (args.Length() == 1 && !args[0]->IsNullOrUndefined()) {
404+
if (args.Length() > 0 && !args[0]->IsNullOrUndefined()) {
395405
Utf8Value value(
396406
args.GetIsolate(),
397407
args[0]->ToString(env->context()).FromMaybe(v8::Local<v8::String>()));
398408
url.append(value.out(), value.length());
409+
410+
if (args.Length() > 1 && args[1]->IsArray()) {
411+
v8::Local<v8::Array> array = args[1].As<v8::Array>();
412+
// The first argument is reserved for program name, but we don't need it
413+
// in workers.
414+
std::vector<std::string> exec_argv = {""};
415+
uint32_t length = array->Length();
416+
for (uint32_t i = 0; i < length; i++) {
417+
v8::Local<v8::Value> arg;
418+
if (!array->Get(env->context(), i).ToLocal(&arg)) {
419+
return;
420+
}
421+
v8::MaybeLocal<v8::String> arg_v8_string =
422+
arg->ToString(env->context());
423+
if (arg_v8_string.IsEmpty()) {
424+
return;
425+
}
426+
Utf8Value arg_utf8_value(
427+
args.GetIsolate(),
428+
arg_v8_string.FromMaybe(v8::Local<v8::String>()));
429+
std::string arg_string(arg_utf8_value.out(), arg_utf8_value.length());
430+
exec_argv.push_back(arg_string);
431+
}
432+
433+
std::vector<std::string> invalid_args{};
434+
std::vector<std::string> errors{};
435+
per_isolate_opts.reset(new PerIsolateOptions());
436+
437+
// Using invalid_args as the v8_args argument as it stores unknown
438+
// options for the per isolate parser.
439+
options_parser::PerIsolateOptionsParser::instance.Parse(
440+
&exec_argv,
441+
nullptr,
442+
&invalid_args,
443+
per_isolate_opts.get(),
444+
kDisallowedInEnvironment,
445+
&errors);
446+
447+
// The first argument is program name.
448+
invalid_args.erase(invalid_args.begin());
449+
if (errors.size() > 0 || invalid_args.size() > 0) {
450+
v8::Local<v8::Value> value =
451+
ToV8Value(env->context(),
452+
errors.size() > 0 ? errors : invalid_args)
453+
.ToLocalChecked();
454+
Local<String> key =
455+
FIXED_ONE_BYTE_STRING(env->isolate(), "invalidExecArgv");
456+
args.This()->Set(env->context(), key, value).FromJust();
457+
return;
458+
}
459+
}
399460
}
400-
new Worker(env, args.This(), url);
461+
new Worker(env, args.This(), url, per_isolate_opts);
401462
}
402463

403464
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {

src/node_worker.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ namespace worker {
1212
// A worker thread, as represented in its parent thread.
1313
class Worker : public AsyncWrap {
1414
public:
15-
Worker(Environment* env, v8::Local<v8::Object> wrap, const std::string& url);
15+
Worker(Environment* env,
16+
v8::Local<v8::Object> wrap,
17+
const std::string& url,
18+
std::shared_ptr<PerIsolateOptions> per_isolate_opts);
1619
~Worker();
1720

1821
// Run the worker. This is only called from the worker thread.

test/parallel/test-internal-errors.js

+10
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,13 @@ assert.strictEqual(
269269

270270
restoreStdout();
271271
}
272+
273+
{
274+
const error = new errors.codes.ERR_WORKER_INVALID_EXEC_ARGV(
275+
['--foo, --bar']
276+
);
277+
assert.strictEqual(
278+
error.message,
279+
'Initiated Worker with invalid execArgv flags: --foo, --bar'
280+
);
281+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { Worker } = require('worker_threads');
6+
7+
{
8+
const expectedErr = common.expectsError({
9+
code: 'ERR_INVALID_ARG_TYPE',
10+
type: TypeError
11+
}, 2);
12+
13+
assert.throws(() => {
14+
new Worker(__filename, { execArgv: 'hello' });
15+
}, expectedErr);
16+
assert.throws(() => {
17+
new Worker(__filename, { execArgv: 6 });
18+
}, expectedErr);
19+
}
20+
21+
{
22+
const expectedErr = common.expectsError({
23+
code: 'ERR_WORKER_INVALID_EXEC_ARGV',
24+
type: Error
25+
}, 3);
26+
assert.throws(() => {
27+
new Worker(__filename, { execArgv: ['--foo'] });
28+
}, expectedErr);
29+
assert.throws(() => {
30+
new Worker(__filename, { execArgv: ['--title=blah'] });
31+
}, expectedErr);
32+
assert.throws(() => {
33+
new Worker(__filename, { execArgv: ['--redirect-warnings'] });
34+
}, expectedErr);
35+
}

test/parallel/test-worker-execargv.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
// This test ensures that Workers have the ability to get
6+
// their own command line flags.
7+
8+
const { Worker, isMainThread } = require('worker_threads');
9+
const { StringDecoder } = require('string_decoder');
10+
const decoder = new StringDecoder('utf8');
11+
12+
if (isMainThread) {
13+
const w = new Worker(__filename, { execArgv: ['--trace-warnings'] });
14+
w.stderr.on('data', common.mustCall((chunk) => {
15+
const error = decoder.write(chunk);
16+
assert.ok(
17+
/Warning: some warning[\s\S]*at Object\.<anonymous>/.test(error)
18+
);
19+
}));
20+
} else {
21+
process.emitWarning('some warning');
22+
}

0 commit comments

Comments
 (0)