mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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: https://github.com/nodejs/node/pull/55234 Refs: https://github.com/nodejs/node/pull/55178 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com> Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
This commit is contained in:
parent
b2161d3a13
commit
d2ad9b4fb6
@ -194,6 +194,38 @@ isMarkedAsUntransferable(pooledBuffer); // Returns true.
|
||||
|
||||
There is no equivalent to this API in browsers.
|
||||
|
||||
## `worker.markAsUncloneable(object)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `object` {any} Any arbitrary JavaScript value.
|
||||
|
||||
Mark an object as not cloneable. If `object` is used as [`message`](#event-message) in
|
||||
a [`port.postMessage()`][] call, an error is thrown. This is a no-op if `object` is a
|
||||
primitive value.
|
||||
|
||||
This has no effect on `ArrayBuffer`, or any `Buffer` like objects.
|
||||
|
||||
This operation cannot be undone.
|
||||
|
||||
```js
|
||||
const { markAsUncloneable } = require('node:worker_threads');
|
||||
|
||||
const anyObject = { foo: 'bar' };
|
||||
markAsUncloneable(anyObject);
|
||||
const { port1 } = new MessageChannel();
|
||||
try {
|
||||
// This will throw an error, because anyObject is not cloneable.
|
||||
port1.postMessage(anyObject)
|
||||
} catch (error) {
|
||||
// error.name === 'DataCloneError'
|
||||
}
|
||||
```
|
||||
|
||||
There is no equivalent to this API in browsers.
|
||||
|
||||
## `worker.moveMessagePortToContext(port, contextifiedSandbox)`
|
||||
|
||||
<!-- YAML
|
||||
|
@ -29,6 +29,14 @@ const {
|
||||
oninit: onInitSymbol,
|
||||
no_message_symbol: noMessageSymbol,
|
||||
} = internalBinding('symbols');
|
||||
const {
|
||||
privateSymbols: {
|
||||
transfer_mode_private_symbol,
|
||||
},
|
||||
constants: {
|
||||
kCloneable,
|
||||
},
|
||||
} = internalBinding('util');
|
||||
const {
|
||||
MessagePort,
|
||||
MessageChannel,
|
||||
@ -447,6 +455,13 @@ ObjectDefineProperties(BroadcastChannel.prototype, {
|
||||
defineEventHandler(BroadcastChannel.prototype, 'message');
|
||||
defineEventHandler(BroadcastChannel.prototype, 'messageerror');
|
||||
|
||||
function markAsUncloneable(obj) {
|
||||
if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null) {
|
||||
return;
|
||||
}
|
||||
obj[transfer_mode_private_symbol] &= ~kCloneable;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
drainMessagePort,
|
||||
messageTypes,
|
||||
@ -454,6 +469,7 @@ module.exports = {
|
||||
kIncrementsPortRef,
|
||||
kWaitingStreams,
|
||||
kStdioWantsMoreDataCallback,
|
||||
markAsUncloneable,
|
||||
moveMessagePortToContext,
|
||||
MessagePort,
|
||||
MessageChannel,
|
||||
|
@ -13,6 +13,7 @@ const {
|
||||
const {
|
||||
MessagePort,
|
||||
MessageChannel,
|
||||
markAsUncloneable,
|
||||
moveMessagePortToContext,
|
||||
receiveMessageOnPort,
|
||||
BroadcastChannel,
|
||||
@ -31,6 +32,7 @@ module.exports = {
|
||||
isMainThread,
|
||||
MessagePort,
|
||||
MessageChannel,
|
||||
markAsUncloneable,
|
||||
markAsUntransferable,
|
||||
isMarkedAsUntransferable,
|
||||
moveMessagePortToContext,
|
||||
|
70
test/parallel/test-worker-message-mark-as-uncloneable.js
Normal file
70
test/parallel/test-worker-message-mark-as-uncloneable.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { markAsUncloneable } = require('node:worker_threads');
|
||||
const { mustCall } = require('../common');
|
||||
|
||||
const expectedErrorName = 'DataCloneError';
|
||||
|
||||
// Uncloneables cannot be cloned during message posting
|
||||
{
|
||||
const anyObject = { foo: 'bar' };
|
||||
markAsUncloneable(anyObject);
|
||||
const { port1 } = new MessageChannel();
|
||||
assert.throws(() => port1.postMessage(anyObject), {
|
||||
constructor: DOMException,
|
||||
name: expectedErrorName,
|
||||
code: 25,
|
||||
}, `Should throw ${expectedErrorName} when posting uncloneables`);
|
||||
}
|
||||
|
||||
// Uncloneables cannot be cloned during structured cloning
|
||||
{
|
||||
class MockResponse extends Response {
|
||||
constructor() {
|
||||
super();
|
||||
markAsUncloneable(this);
|
||||
}
|
||||
}
|
||||
structuredClone(MockResponse.prototype);
|
||||
|
||||
markAsUncloneable(MockResponse.prototype);
|
||||
const r = new MockResponse();
|
||||
assert.throws(() => structuredClone(r), {
|
||||
constructor: DOMException,
|
||||
name: expectedErrorName,
|
||||
code: 25,
|
||||
}, `Should throw ${expectedErrorName} when cloning uncloneables`);
|
||||
}
|
||||
|
||||
// markAsUncloneable cannot affect ArrayBuffer
|
||||
{
|
||||
const pooledBuffer = new ArrayBuffer(8);
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
markAsUncloneable(pooledBuffer);
|
||||
port1.postMessage(pooledBuffer);
|
||||
port2.on('message', mustCall((value) => {
|
||||
assert.deepStrictEqual(value, pooledBuffer);
|
||||
port2.close(mustCall());
|
||||
}));
|
||||
}
|
||||
|
||||
// markAsUncloneable can affect Node.js built-in object like Blob
|
||||
{
|
||||
const cloneableBlob = new Blob();
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
port1.postMessage(cloneableBlob);
|
||||
port2.on('message', mustCall((value) => {
|
||||
assert.deepStrictEqual(value, cloneableBlob);
|
||||
port2.close(mustCall());
|
||||
}));
|
||||
|
||||
const uncloneableBlob = new Blob();
|
||||
markAsUncloneable(uncloneableBlob);
|
||||
assert.throws(() => port1.postMessage(uncloneableBlob), {
|
||||
constructor: DOMException,
|
||||
name: expectedErrorName,
|
||||
code: 25,
|
||||
}, `Should throw ${expectedErrorName} when cloning uncloneables`);
|
||||
}
|
Loading…
Reference in New Issue
Block a user