Skip to content

Commit 5fda4a1

Browse 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

File tree

4 files changed

+120
-0
lines changed

4 files changed

+120
-0
lines changed

doc/api/worker_threads.md

Lines changed: 32 additions & 0 deletions
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

lib/internal/worker/io.js

Lines 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,

lib/worker_threads.js

Lines 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,
Lines 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy