Skip to content

Commit e83dcde

Browse files
aduh95MylesBorins
authored andcommitted
worker: allow URL in Worker constructor
The explicit goal is to let users use `import.meta.url` to re-load thecurrent module inside a Worker instance. Fixes: #30780 PR-URL: #31664 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 597bcb5 commit e83dcde

8 files changed

Lines changed: 104 additions & 20 deletions

File tree

‎doc/api/worker_threads.md‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ if (isMainThread) {
513513
<!-- YAML
514514
added: v10.5.0
515515
changes:
516+
- version: REPLACEME
517+
pr-url: https://github.com/nodejs/node/pull/31664
518+
description: The `filename` parameter can be a WHATWG `URL` object using
519+
`file:` protocol.
516520
- version: v13.2.0
517521
pr-url: https://github.com/nodejs/node/pull/26628
518522
description: The `resourceLimits` option was introduced.
@@ -521,9 +525,10 @@ changes:
521525
description: The `argv` option was introduced.
522526
-->
523527

524-
*`filename` {string} The path to the Worker’s main script. Must be
525-
either an absolute path or a relative path (i.e. relative to the
526-
current working directory) starting with `./` or `../`.
528+
*`filename` {string|URL} The path to the Worker’s main script or module. Must
529+
be either an absolute path or a relative path (i.e. relative to the
530+
current working directory) starting with `./` or `../`, or a WHATWG `URL`
531+
object using `file:` protocol.
527532
If `options.eval` is `true`, this is a string containing JavaScript code
528533
rather than a path.
529534
*`options` {Object}
@@ -536,8 +541,9 @@ changes:
536541
to specify that the parent thread and the child thread should share their
537542
environment variables; in that case, changes to one thread’s `process.env`
538543
object will affect the other thread as well. **Default:**`process.env`.
539-
*`eval` {boolean} If `true`, interpret the first argument to the constructor
540-
as a script that is executed once the worker is online.
544+
*`eval` {boolean} If `true` and the first argument is a `string`, interpret
545+
the first argument to the constructor as a script that is executed once the
546+
worker is online.
541547
*`execArgv` {string[]} List of node CLI options passed to the worker.
542548
V8 options (such as `--max-old-space-size`) and options that affect the
543549
process (such as `--title`) are not supported. If set, this will be provided

‎lib/internal/errors.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,9 +1391,14 @@ E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') =>
13911391
E('ERR_WORKER_NOT_RUNNING','Worker instance not running',Error);
13921392
E('ERR_WORKER_OUT_OF_MEMORY',
13931393
'Worker terminated due to reaching memory limit: %s',Error);
1394-
E('ERR_WORKER_PATH',
1395-
'The worker script filename must be an absolute path or a relative '+
1396-
'path starting with \'./\' or \'../\'. Received "%s"',
1394+
E('ERR_WORKER_PATH',(filename)=>
1395+
'The worker script or module filename must be an absolute path or a '+
1396+
'relative path starting with \'./\' or \'../\'.'+
1397+
(filename.startsWith('file://') ?
1398+
' If you want to pass a file:// URL, you must wrap it around `new URL`.' :
1399+
''
1400+
)+
1401+
` Received "${filename}"`,
13971402
TypeError);
13981403
E('ERR_WORKER_UNSERIALIZABLE_ERROR',
13991404
'Serializing an uncaught exception failed',Error);

‎lib/internal/url.js‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,8 +1348,7 @@ function getPathFromURLPosix(url) {
13481348
functionfileURLToPath(path){
13491349
if(typeofpath==='string')
13501350
path=newURL(path);
1351-
elseif(path==null||!path[searchParams]||
1352-
!path[searchParams][searchParams])
1351+
elseif(!isURLInstance(path))
13531352
thrownewERR_INVALID_ARG_TYPE('path',['string','URL'],path);
13541353
if(path.protocol!=='file:')
13551354
thrownewERR_INVALID_URL_SCHEME('file');
@@ -1396,9 +1395,13 @@ function pathToFileURL(filepath) {
13961395
returnoutURL;
13971396
}
13981397

1398+
functionisURLInstance(fileURLOrPath){
1399+
returnfileURLOrPath!=null&&fileURLOrPath[searchParams]&&
1400+
fileURLOrPath[searchParams][searchParams];
1401+
}
1402+
13991403
functiontoPathIfFileURL(fileURLOrPath){
1400-
if(fileURLOrPath==null||!fileURLOrPath[searchParams]||
1401-
!fileURLOrPath[searchParams][searchParams])
1404+
if(!isURLInstance(fileURLOrPath))
14021405
returnfileURLOrPath;
14031406
returnfileURLToPath(fileURLOrPath);
14041407
}
@@ -1431,6 +1434,7 @@ module.exports = {
14311434
fileURLToPath,
14321435
pathToFileURL,
14331436
toPathIfFileURL,
1437+
isURLInstance,
14341438
URL,
14351439
URLSearchParams,
14361440
domainToASCII,

‎lib/internal/worker.js‎

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ const {
2727
ERR_INVALID_ARG_TYPE,
2828
// eslint-disable-next-line no-unused-vars
2929
ERR_WORKER_INIT_FAILED,
30+
ERR_INVALID_ARG_VALUE,
3031
}=errorCodes;
31-
const{ validateString }=require('internal/validators');
3232
const{ getOptionValue }=require('internal/options');
3333

3434
constworkerIo=require('internal/worker/io');
@@ -45,7 +45,7 @@ const {
4545
WritableWorkerStdio
4646
}=workerIo;
4747
const{ deserializeError }=require('internal/error-serdes');
48-
const{ pathToFileURL }=require('url');
48+
const{fileURLToPath, isURLInstance,pathToFileURL }=require('internal/url');
4949

5050
const{
5151
ownsProcessState,
@@ -86,7 +86,6 @@ class Worker extends EventEmitter {
8686
constructor(filename,options={}){
8787
super();
8888
debug(`[${threadId}] create new worker`,filename,options);
89-
validateString(filename,'filename');
9089
if(options.execArgv&&!ArrayIsArray(options.execArgv)){
9190
thrownewERR_INVALID_ARG_TYPE('options.execArgv',
9291
'Array',
@@ -99,11 +98,33 @@ class Worker extends EventEmitter {
9998
}
10099
argv=options.argv.map(String);
101100
}
102-
if(!options.eval){
103-
if(!path.isAbsolute(filename)&&!/^\.\.?[\\/]/.test(filename)){
101+
102+
leturl;
103+
if(options.eval){
104+
if(typeoffilename!=='string'){
105+
thrownewERR_INVALID_ARG_VALUE(
106+
'options.eval',
107+
options.eval,
108+
'must be false when \'filename\' is not a string'
109+
);
110+
}
111+
url=null;
112+
}else{
113+
if(isURLInstance(filename)){
114+
url=filename;
115+
filename=fileURLToPath(filename);
116+
}elseif(typeoffilename!=='string'){
117+
thrownewERR_INVALID_ARG_TYPE(
118+
'filename',
119+
['string','URL'],
120+
filename
121+
);
122+
}elseif(path.isAbsolute(filename)||/^\.\.?[\\/]/.test(filename)){
123+
filename=path.resolve(filename);
124+
url=pathToFileURL(filename);
125+
}else{
104126
thrownewERR_WORKER_PATH(filename);
105127
}
106-
filename=path.resolve(filename);
107128

108129
constext=path.extname(filename);
109130
if(ext!=='.js'&&ext!=='.mjs'&&ext!=='.cjs'){
@@ -125,7 +146,6 @@ class Worker extends EventEmitter {
125146
options.env);
126147
}
127148

128-
consturl=options.eval ? null : pathToFileURL(filename);
129149
// Set up the C++ handle for the worker, as well as some internal wiring.
130150
this[kHandle]=newWorkerImpl(url,
131151
env===process.env ? null : env,

‎test/parallel/test-worker-type-check.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const { Worker } = require('worker_threads');
2020
{
2121
code: 'ERR_INVALID_ARG_TYPE',
2222
name: 'TypeError',
23-
message: 'The "filename" argument must be of type string.'+
23+
message: 'The "filename" argument must be of type string '+
24+
'or an instance of URL.'+
2425
common.invalidArgTypeHelper(val)
2526
}
2627
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import'../common/index.mjs';
2+
importassertfrom'assert';
3+
import{Worker}from'worker_threads';
4+
5+
constre=/Theargument'options\.eval'mustbefalsewhen'filename'isnotastring\./;
6+
assert.throws(()=>newWorker(newURL(import.meta.url),{eval: true}),re);

‎test/parallel/test-worker-unsupported-path.js‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const { Worker } = require('worker_threads');
1313
assert.throws(()=>{newWorker('/b');},expectedErr);
1414
assert.throws(()=>{newWorker('/c.wasm');},expectedErr);
1515
assert.throws(()=>{newWorker('/d.txt');},expectedErr);
16+
assert.throws(()=>{newWorker(newURL('file:///C:/e.wasm'));},expectedErr);
1617
}
1718

1819
{
@@ -26,3 +27,26 @@ const { Worker } = require('worker_threads');
2627
assert.throws(()=>{newWorker('file:///file_url');},expectedErr);
2728
assert.throws(()=>{newWorker('https://www.url.com');},expectedErr);
2829
}
30+
31+
{
32+
assert.throws(
33+
()=>{newWorker('file:///file_url');},
34+
/Ifyouwanttopassafile:\/\/URL,youmustwrapitaround`newURL`/
35+
);
36+
assert.throws(
37+
()=>{newWorker('relative_no_dot');},
38+
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
39+
/^((?!Ifyouwanttopassafile:\/\/URL,youmustwrapitaround`newURL`).)*$/s
40+
);
41+
}
42+
43+
{
44+
constexpectedErr={
45+
code: 'ERR_INVALID_URL_SCHEME',
46+
name: 'TypeError'
47+
};
48+
assert.throws(()=>{newWorker(newURL('https://www.url.com'));},
49+
expectedErr);
50+
assert.throws(()=>{newWorker(newURL('data:application/javascript,'));},
51+
expectedErr);
52+
}

‎test/parallel/test-worker.mjs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import{mustCall}from'../common/index.mjs';
2+
importassertfrom'assert';
3+
import{Worker,isMainThread,parentPort}from'worker_threads';
4+
5+
constTEST_STRING='Hello, world!';
6+
7+
if(isMainThread){
8+
constw=newWorker(newURL(import.meta.url));
9+
w.on('message',mustCall((message)=>{
10+
assert.strictEqual(message,TEST_STRING);
11+
}));
12+
}else{
13+
setImmediate(()=>{
14+
process.nextTick(()=>{
15+
parentPort.postMessage(TEST_STRING);
16+
});
17+
});
18+
}

0 commit comments

Comments
 (0)