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 5fda4a1

Browse filesBrowse files
jazellytargos
authored andcommitted
worker: add markAsUncloneable api
External modules need a way to decorate their objects so that node can recognize it as a host object for serialization process. Exposing a way for turning off instead of turning on is much safer. PR-URL: #55234 Refs: #55178 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com> Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
1 parent b36f8c2 commit 5fda4a1
Copy full SHA for 5fda4a1

File tree

Expand file treeCollapse file tree

4 files changed

+120
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+120
-0
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
+32Lines changed: 32 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,38 @@ isMarkedAsUntransferable(pooledBuffer); // Returns true.
194194

195195
There is no equivalent to this API in browsers.
196196

197+
## `worker.markAsUncloneable(object)`
198+
199+
<!-- YAML
200+
added: REPLACEME
201+
-->
202+
203+
* `object` {any} Any arbitrary JavaScript value.
204+
205+
Mark an object as not cloneable. If `object` is used as [`message`](#event-message) in
206+
a [`port.postMessage()`][] call, an error is thrown. This is a no-op if `object` is a
207+
primitive value.
208+
209+
This has no effect on `ArrayBuffer`, or any `Buffer` like objects.
210+
211+
This operation cannot be undone.
212+
213+
```js
214+
const { markAsUncloneable } = require('node:worker_threads');
215+
216+
const anyObject = { foo: 'bar' };
217+
markAsUncloneable(anyObject);
218+
const { port1 } = new MessageChannel();
219+
try {
220+
// This will throw an error, because anyObject is not cloneable.
221+
port1.postMessage(anyObject)
222+
} catch (error) {
223+
// error.name === 'DataCloneError'
224+
}
225+
```
226+
227+
There is no equivalent to this API in browsers.
228+
197229
## `worker.moveMessagePortToContext(port, contextifiedSandbox)`
198230

199231
<!-- YAML
Collapse file

‎lib/internal/worker/io.js‎

Copy file name to clipboardExpand all lines: lib/internal/worker/io.js
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ const {
2929
oninit: onInitSymbol,
3030
no_message_symbol: noMessageSymbol,
3131
} = internalBinding('symbols');
32+
const {
33+
privateSymbols: {
34+
transfer_mode_private_symbol,
35+
},
36+
constants: {
37+
kCloneable,
38+
},
39+
} = internalBinding('util');
3240
const {
3341
MessagePort,
3442
MessageChannel,
@@ -447,13 +455,21 @@ ObjectDefineProperties(BroadcastChannel.prototype, {
447455
defineEventHandler(BroadcastChannel.prototype, 'message');
448456
defineEventHandler(BroadcastChannel.prototype, 'messageerror');
449457

458+
function markAsUncloneable(obj) {
459+
if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null) {
460+
return;
461+
}
462+
obj[transfer_mode_private_symbol] &= ~kCloneable;
463+
}
464+
450465
module.exports = {
451466
drainMessagePort,
452467
messageTypes,
453468
kPort,
454469
kIncrementsPortRef,
455470
kWaitingStreams,
456471
kStdioWantsMoreDataCallback,
472+
markAsUncloneable,
457473
moveMessagePortToContext,
458474
MessagePort,
459475
MessageChannel,
Collapse file

‎lib/worker_threads.js‎

Copy file name to clipboardExpand all lines: lib/worker_threads.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
const {
1414
MessagePort,
1515
MessageChannel,
16+
markAsUncloneable,
1617
moveMessagePortToContext,
1718
receiveMessageOnPort,
1819
BroadcastChannel,
@@ -31,6 +32,7 @@ module.exports = {
3132
isMainThread,
3233
MessagePort,
3334
MessageChannel,
35+
markAsUncloneable,
3436
markAsUntransferable,
3537
isMarkedAsUntransferable,
3638
moveMessagePortToContext,
Collapse file
+70Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const { markAsUncloneable } = require('node:worker_threads');
6+
const { mustCall } = require('../common');
7+
8+
const expectedErrorName = 'DataCloneError';
9+
10+
// Uncloneables cannot be cloned during message posting
11+
{
12+
const anyObject = { foo: 'bar' };
13+
markAsUncloneable(anyObject);
14+
const { port1 } = new MessageChannel();
15+
assert.throws(() => port1.postMessage(anyObject), {
16+
constructor: DOMException,
17+
name: expectedErrorName,
18+
code: 25,
19+
}, `Should throw ${expectedErrorName} when posting uncloneables`);
20+
}
21+
22+
// Uncloneables cannot be cloned during structured cloning
23+
{
24+
class MockResponse extends Response {
25+
constructor() {
26+
super();
27+
markAsUncloneable(this);
28+
}
29+
}
30+
structuredClone(MockResponse.prototype);
31+
32+
markAsUncloneable(MockResponse.prototype);
33+
const r = new MockResponse();
34+
assert.throws(() => structuredClone(r), {
35+
constructor: DOMException,
36+
name: expectedErrorName,
37+
code: 25,
38+
}, `Should throw ${expectedErrorName} when cloning uncloneables`);
39+
}
40+
41+
// markAsUncloneable cannot affect ArrayBuffer
42+
{
43+
const pooledBuffer = new ArrayBuffer(8);
44+
const { port1, port2 } = new MessageChannel();
45+
markAsUncloneable(pooledBuffer);
46+
port1.postMessage(pooledBuffer);
47+
port2.on('message', mustCall((value) => {
48+
assert.deepStrictEqual(value, pooledBuffer);
49+
port2.close(mustCall());
50+
}));
51+
}
52+
53+
// markAsUncloneable can affect Node.js built-in object like Blob
54+
{
55+
const cloneableBlob = new Blob();
56+
const { port1, port2 } = new MessageChannel();
57+
port1.postMessage(cloneableBlob);
58+
port2.on('message', mustCall((value) => {
59+
assert.deepStrictEqual(value, cloneableBlob);
60+
port2.close(mustCall());
61+
}));
62+
63+
const uncloneableBlob = new Blob();
64+
markAsUncloneable(uncloneableBlob);
65+
assert.throws(() => port1.postMessage(uncloneableBlob), {
66+
constructor: DOMException,
67+
name: expectedErrorName,
68+
code: 25,
69+
}, `Should throw ${expectedErrorName} when cloning uncloneables`);
70+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.