| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 74f4aae commit 4e42eb5
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,6 +80,42 @@ if (isMainThread) { | |||
| 80 | 80 | } | |
| 81 | 81 | ``` | |
| 82 | 82 | ||
| 83 | + ## `worker.markAsUntransferable(object)` | ||
| 84 | + <!-- YAML | ||
| 85 | + added: REPLACEME | ||
| 86 | + --> | ||
| 87 | + | ||
| 88 | + Mark an object as not transferable. If `object` occurs in the transfer list of | ||
| 89 | + a [`port.postMessage()`][] call, it will be ignored. | ||
| 90 | + | ||
| 91 | + In particular, this makes sense for objects that can be cloned, rather than | ||
| 92 | + transferred, and which are used by other objects on the sending side. | ||
| 93 | + For example, Node.js marks the `ArrayBuffer`s it uses for its | ||
| 94 | + [`Buffer` pool][`Buffer.allocUnsafe()`] with this. | ||
| 95 | + | ||
| 96 | + This operation cannot be undone. | ||
| 97 | + | ||
| 98 | + ```js | ||
| 99 | + const { MessageChannel, markAsUntransferable } = require('worker_threads'); | ||
| 100 | + | ||
| 101 | + const pooledBuffer = new ArrayBuffer(8); | ||
| 102 | + const typedArray1 = new Uint8Array(pooledBuffer); | ||
| 103 | + const typedArray2 = new Float64Array(pooledBuffer); | ||
| 104 | + | ||
| 105 | + markAsUntransferable(pooledBuffer); | ||
| 106 | + | ||
| 107 | + const { port1 } = new MessageChannel(); | ||
| 108 | + port1.postMessage(typedArray1, [ typedArray1.buffer ]); | ||
| 109 | + | ||
| 110 | + // The following line prints the contents of typedArray1 -- it still owns its | ||
| 111 | + // memory and has been cloned, not transfered. Without `markAsUntransferable()`, | ||
| 112 | + // this would print an empty Uint8Array. typedArray2 is intact as well. | ||
| 113 | + console.log(typedArray1); | ||
| 114 | + console.log(typedArray2); | ||
| 115 | + ``` | ||
| 116 | + | ||
| 117 | + There is no equivalent to this API in browsers. | ||
| 118 | + | ||
| 83 | 119 | ## `worker.moveMessagePortToContext(port, contextifiedSandbox)` | |
| 84 | 120 | <!-- YAML | |
| 85 | 121 | added: v11.13.0 | |
@@ -442,6 +478,9 @@ For `Buffer` instances, specifically, whether the underlying | |||
| 442 | 478 | `ArrayBuffer` can be transferred or cloned depends entirely on how | |
| 443 | 479 | instances were created, which often cannot be reliably determined. | |
| 444 | 480 | ||
| 481 | + An `ArrayBuffer` can be marked with [`markAsUntransferable()`][] to indicate | ||
| 482 | + that it should always be cloned and never transferred. | ||
| 483 | + | ||
| 445 | 484 | Depending on how a `Buffer` instance was created, it may or may | |
| 446 | 485 | not own its underlying `ArrayBuffer`. An `ArrayBuffer` must not | |
| 447 | 486 | be transferred unless it is known that the `Buffer` instance | |
@@ -853,6 +892,7 @@ active handle in the event system. If the worker is already `unref()`ed calling | |||
| 853 | 892 | [`WebAssembly.Module`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module | |
| 854 | 893 | [`Worker`]: #worker_threads_class_worker | |
| 855 | 894 | [`cluster` module]: cluster.html | |
| 895 | + [`markAsUntransferable()`]: #worker_threads_worker_markasuntransferable_object | ||
| 856 | 896 | [`port.on('message')`]: #worker_threads_event_message | |
| 857 | 897 | [`port.onmessage()`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/onmessage | |
| 858 | 898 | [`port.postMessage()`]: #worker_threads_port_postmessage_value_transferlist | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,13 +59,11 @@ const { | |||
| 59 | 59 | zeroFill: bindingZeroFill | |
| 60 | 60 | } = internalBinding('buffer'); | |
| 61 | 61 | const { | |
| 62 | - arraybuffer_untransferable_private_symbol, | ||
| 63 | 62 | getOwnNonIndexProperties, | |
| 64 | 63 | propertyFilter: { | |
| 65 | 64 | ALL_PROPERTIES, | |
| 66 | 65 | ONLY_ENUMERABLE | |
| 67 | 66 | }, | |
| 68 | - setHiddenValue, | ||
| 69 | 67 | } = internalBinding('util'); | |
| 70 | 68 | const { | |
| 71 | 69 | customInspectSymbol, | |
@@ -83,7 +81,6 @@ const { | |||
| 83 | 81 | } = require('internal/util/inspect'); | |
| 84 | 82 | const { encodings } = internalBinding('string_decoder'); | |
| 85 | 83 | ||
| 86 | - | ||
| 87 | 84 | const { | |
| 88 | 85 | codes: { | |
| 89 | 86 | ERR_BUFFER_OUT_OF_BOUNDS, | |
@@ -104,6 +101,7 @@ const { | |||
| 104 | 101 | ||
| 105 | 102 | const { | |
| 106 | 103 | FastBuffer, | |
| 104 | + markAsUntransferable, | ||
| 107 | 105 | addBufferPrototypeMethods | |
| 108 | 106 | } = require('internal/buffer'); | |
| 109 | 107 | ||
@@ -156,7 +154,7 @@ function createUnsafeBuffer(size) { | |||
| 156 | 154 | function createPool() { | |
| 157 | 155 | poolSize = Buffer.poolSize; | |
| 158 | 156 | allocPool = createUnsafeBuffer(poolSize).buffer; | |
| 159 | - setHiddenValue(allocPool, arraybuffer_untransferable_private_symbol, true); | ||
| 157 | + markAsUntransferable(allocPool); | ||
| 160 | 158 | poolOffset = 0; | |
| 161 | 159 | } | |
| 162 | 160 | createPool(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,6 +27,10 @@ const { | |||
| 27 | 27 | ucs2Write, | |
| 28 | 28 | utf8Write | |
| 29 | 29 | } = internalBinding('buffer'); | |
| 30 | + const { | ||
| 31 | + untransferable_object_private_symbol, | ||
| 32 | + setHiddenValue, | ||
| 33 | + } = internalBinding('util'); | ||
| 30 | 34 | ||
| 31 | 35 | // Temporary buffers to convert numbers. | |
| 32 | 36 | const float32Array = new Float32Array(1); | |
@@ -1007,7 +1011,16 @@ function addBufferPrototypeMethods(proto) { | |||
| 1007 | 1011 | proto.utf8Write = utf8Write; | |
| 1008 | 1012 | } | |
| 1009 | 1013 | ||
| 1014 | + // This would better be placed in internal/worker/io.js, but that doesn't work | ||
| 1015 | + // because Buffer needs this and that would introduce a cyclic dependency. | ||
| 1016 | + function markAsUntransferable(obj) { | ||
| 1017 | + if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null) | ||
| 1018 | + return; // This object is a primitive and therefore already untransferable. | ||
| 1019 | + setHiddenValue(obj, untransferable_object_private_symbol, true); | ||
| 1020 | + } | ||
| 1021 | + | ||
| 1010 | 1022 | module.exports = { | |
| 1011 | 1023 | FastBuffer, | |
| 1012 | - addBufferPrototypeMethods | ||
| 1024 | + addBufferPrototypeMethods, | ||
| 1025 | + markAsUntransferable, | ||
| 1013 | 1026 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,13 +12,18 @@ const { | |||
| 12 | 12 | MessagePort, | |
| 13 | 13 | MessageChannel, | |
| 14 | 14 | moveMessagePortToContext, | |
| 15 | - receiveMessageOnPort | ||
| 15 | + receiveMessageOnPort, | ||
| 16 | 16 | } = require('internal/worker/io'); | |
| 17 | 17 | ||
| 18 | + const { | ||
| 19 | + markAsUntransferable, | ||
| 20 | + } = require('internal/buffer'); | ||
| 21 | + | ||
| 18 | 22 | module.exports = { | |
| 19 | 23 | isMainThread, | |
| 20 | 24 | MessagePort, | |
| 21 | 25 | MessageChannel, | |
| 26 | + markAsUntransferable, | ||
| 22 | 27 | moveMessagePortToContext, | |
| 23 | 28 | receiveMessageOnPort, | |
| 24 | 29 | resourceLimits, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -146,12 +146,12 @@ constexpr size_t kFsStatsBufferLength = | |||
| 146 | 146 | // "node:" prefix to avoid name clashes with third-party code. | |
| 147 | 147 | #define PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) \ | |
| 148 | 148 | V(alpn_buffer_private_symbol, "node:alpnBuffer") \ | |
| 149 | - V(arraybuffer_untransferable_private_symbol, "node:untransferableBuffer") \ | ||
| 150 | 149 | V(arrow_message_private_symbol, "node:arrowMessage") \ | |
| 151 | 150 | V(contextify_context_private_symbol, "node:contextify:context") \ | |
| 152 | 151 | V(contextify_global_private_symbol, "node:contextify:global") \ | |
| 153 | 152 | V(decorated_private_symbol, "node:decorated") \ | |
| 154 | 153 | V(napi_wrapper, "node:napi:wrapper") \ | |
| 154 | + V(untransferable_object_private_symbol, "node:untransferableObject") \ | ||
| 155 | 155 | ||
| 156 | 156 | // Symbols are per-isolate primitives but Environment proxies them | |
| 157 | 157 | // for the sake of convenience. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,7 +29,7 @@ struct node_napi_env__ : public napi_env__ { | |||
| 29 | 29 | v8::Local<v8::ArrayBuffer> ab) const override { | |
| 30 | 30 | return ab->SetPrivate( | |
| 31 | 31 | context(), | |
| 32 | - node_env()->arraybuffer_untransferable_private_symbol(), | ||
| 32 | + node_env()->untransferable_object_private_symbol(), | ||
| 33 | 33 | v8::True(isolate)); | |
| 34 | 34 | } | |
| 35 | 35 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -419,7 +419,7 @@ MaybeLocal<Object> New(Environment* env, | |||
| 419 | 419 | Local<ArrayBuffer> ab = | |
| 420 | 420 | CallbackInfo::CreateTrackedArrayBuffer(env, data, length, callback, hint); | |
| 421 | 421 | if (ab->SetPrivate(env->context(), | |
| 422 | - env->arraybuffer_untransferable_private_symbol(), | ||
| 422 | + env->untransferable_object_private_symbol(), | ||
| 423 | 423 | True(env->isolate())).IsNothing()) { | |
| 424 | 424 | return Local<Object>(); | |
| 425 | 425 | } | |
@@ -1179,7 +1179,7 @@ void Initialize(Local<Object> target, | |||
| 1179 | 1179 | ArrayBuffer::New(env->isolate(), std::move(backing)); | |
| 1180 | 1180 | array_buffer->SetPrivate( | |
| 1181 | 1181 | env->context(), | |
| 1182 | - env->arraybuffer_untransferable_private_symbol(), | ||
| 1182 | + env->untransferable_object_private_symbol(), | ||
| 1183 | 1183 | True(env->isolate())).Check(); | |
| 1184 | 1184 | CHECK(target | |
| 1185 | 1185 | ->Set(env->context(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -399,7 +399,21 @@ Maybe<bool> Message::Serialize(Environment* env, | |||
| 399 | 399 | std::vector<Local<ArrayBuffer>> array_buffers; | |
| 400 | 400 | for (uint32_t i = 0; i < transfer_list_v.length(); ++i) { | |
| 401 | 401 | Local<Value> entry = transfer_list_v[i]; | |
| 402 | - // Currently, we support ArrayBuffers and MessagePorts. | ||
| 402 | + if (entry->IsObject()) { | ||
| 403 | + // See https://github.com/nodejs/node/pull/30339#issuecomment-552225353 | ||
| 404 | + // for details. | ||
| 405 | + bool untransferable; | ||
| 406 | + if (!entry.As<Object>()->HasPrivate( | ||
| 407 | + context, | ||
| 408 | + env->untransferable_object_private_symbol()) | ||
| 409 | + .To(&untransferable)) { | ||
| 410 | + return Nothing<bool>(); | ||
| 411 | + } | ||
| 412 | + if (untransferable) continue; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + // Currently, we support ArrayBuffers and BaseObjects for which | ||
| 416 | + // GetTransferMode() does not return kUntransferable. | ||
| 403 | 417 | if (entry->IsArrayBuffer()) { | |
| 404 | 418 | Local<ArrayBuffer> ab = entry.As<ArrayBuffer>(); | |
| 405 | 419 | // If we cannot render the ArrayBuffer unusable in this Isolate, | |
@@ -411,16 +425,6 @@ Maybe<bool> Message::Serialize(Environment* env, | |||
| 411 | 425 | // is always going to outlive any Workers it creates, and so will its | |
| 412 | 426 | // allocator along with it. | |
| 413 | 427 | if (!ab->IsDetachable()) continue; | |
| 414 | - // See https://github.com/nodejs/node/pull/30339#issuecomment-552225353 | ||
| 415 | - // for details. | ||
| 416 | - bool untransferrable; | ||
| 417 | - if (!ab->HasPrivate( | ||
| 418 | - context, | ||
| 419 | - env->arraybuffer_untransferable_private_symbol()) | ||
| 420 | - .To(&untransferrable)) { | ||
| 421 | - return Nothing<bool>(); | ||
| 422 | - } | ||
| 423 | - if (untransferrable) continue; | ||
| 424 | 428 | if (std::find(array_buffers.begin(), array_buffers.end(), ab) != | |
| 425 | 429 | array_buffers.end()) { | |
| 426 | 430 | ThrowDataCloneException( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,37 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + const { MessageChannel, markAsUntransferable } = require('worker_threads'); | ||
| 5 | + | ||
| 6 | + { | ||
| 7 | + const ab = new ArrayBuffer(8); | ||
| 8 | + | ||
| 9 | + markAsUntransferable(ab); | ||
| 10 | + assert.strictEqual(ab.byteLength, 8); | ||
| 11 | + | ||
| 12 | + const { port1, port2 } = new MessageChannel(); | ||
| 13 | + port1.postMessage(ab, [ ab ]); | ||
| 14 | + | ||
| 15 | + assert.strictEqual(ab.byteLength, 8); // The AB is not detached. | ||
| 16 | + port2.once('message', common.mustCall()); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + { | ||
| 20 | + const channel1 = new MessageChannel(); | ||
| 21 | + const channel2 = new MessageChannel(); | ||
| 22 | + | ||
| 23 | + markAsUntransferable(channel2.port1); | ||
| 24 | + | ||
| 25 | + assert.throws(() => { | ||
| 26 | + channel1.port1.postMessage(channel2.port1, [ channel2.port1 ]); | ||
| 27 | + }, /was found in message but not listed in transferList/); | ||
| 28 | + | ||
| 29 | + channel2.port1.postMessage('still works, not closed/transferred'); | ||
| 30 | + channel2.port2.once('message', common.mustCall()); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + { | ||
| 34 | + for (const value of [0, null, false, true, undefined, [], {}]) { | ||
| 35 | + markAsUntransferable(value); // Has no visible effect. | ||
| 36 | + } | ||
| 37 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments