Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e83dcde

Browse filesBrowse 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
Copy full SHA for e83dcde

File tree

Expand file treeCollapse file tree

8 files changed

+104
-20
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

8 files changed

+104
-20
lines changed
Open diff view settings
Collapse file

‎doc/api/worker_threads.md‎

Copy file name to clipboardExpand all lines: doc/api/worker_threads.md
+11-5Lines changed: 11 additions & 5 deletions
  • Display the source diff
  • Display the rich diff
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
Collapse file

‎lib/internal/errors.js‎

Copy file name to clipboardExpand all lines: lib/internal/errors.js
+8-3Lines 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);
Collapse file

‎lib/internal/url.js‎

Copy file name to clipboardExpand all lines: lib/internal/url.js
+8-4Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,8 +1348,7 @@ function getPathFromURLPosix(url) {
13481348
function fileURLToPath(path) {
13491349
if (typeof path === 'string')
13501350
path = new URL(path);
1351-
else if (path == null || !path[searchParams] ||
1352-
!path[searchParams][searchParams])
1351+
else if (!isURLInstance(path))
13531352
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
13541353
if (path.protocol !== 'file:')
13551354
throw new ERR_INVALID_URL_SCHEME('file');
@@ -1396,9 +1395,13 @@ function pathToFileURL(filepath) {
13961395
return outURL;
13971396
}
13981397

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

‎lib/internal/worker.js‎

Copy file name to clipboardExpand all lines: lib/internal/worker.js
+27-7Lines 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
const workerIo = 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
throw new ERR_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+
let url;
103+
if (options.eval) {
104+
if (typeof filename !== 'string') {
105+
throw new ERR_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+
} else if (typeof filename !== 'string') {
117+
throw new ERR_INVALID_ARG_TYPE(
118+
'filename',
119+
['string', 'URL'],
120+
filename
121+
);
122+
} else if (path.isAbsolute(filename) || /^\.\.?[\\/]/.test(filename)) {
123+
filename = path.resolve(filename);
124+
url = pathToFileURL(filename);
125+
} else {
104126
throw new ERR_WORKER_PATH(filename);
105127
}
106-
filename = path.resolve(filename);
107128

108129
const ext = 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-
const url = options.eval ? null : pathToFileURL(filename);
129149
// Set up the C++ handle for the worker, as well as some internal wiring.
130150
this[kHandle] = new WorkerImpl(url,
131151
env === process.env ? null : env,
Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-worker-type-check.js
+2-1Lines 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
);
Collapse file
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import '../common/index.mjs';
2+
import assert from 'assert';
3+
import { Worker } from 'worker_threads';
4+
5+
const re = /The argument 'options\.eval' must be false when 'filename' is not a string\./;
6+
assert.throws(() => new Worker(new URL(import.meta.url), { eval: true }), re);
Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-worker-unsupported-path.js
+24Lines 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(() => { new Worker('/b'); }, expectedErr);
1414
assert.throws(() => { new Worker('/c.wasm'); }, expectedErr);
1515
assert.throws(() => { new Worker('/d.txt'); }, expectedErr);
16+
assert.throws(() => { new Worker(new URL('file:///C:/e.wasm')); }, expectedErr);
1617
}
1718

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

‎test/parallel/test-worker.mjs‎

Copy file name to clipboard
+18Lines 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+
import assert from 'assert';
3+
import { Worker, isMainThread, parentPort } from 'worker_threads';
4+
5+
const TEST_STRING = 'Hello, world!';
6+
7+
if (isMainThread) {
8+
const w = new Worker(new URL(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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.