Skip to content

Commit 6cb8e4b

Browse files
committed
src: mark ArrayBuffers with free callbacks as untransferable
More precisely, make them untransferable if they were created through *our* APIs, because those do not follow the improved free callback mechanism that V8 uses now. All other ArrayBuffers can be transferred between threads now, the assumption being that they were created in a clean way that follows the V8 API on this. This addresses a TODO comment. Refs: #30339 (comment) PR-URL: #30475 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: David Carlier <devnexen@gmail.com>
1 parent 1317ac6 commit 6cb8e4b

File tree

9 files changed

+86
-8
lines changed

9 files changed

+86
-8
lines changed

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ constexpr size_t kFsStatsBufferLength =
151151
// "node:" prefix to avoid name clashes with third-party code.
152152
#define PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) \
153153
V(alpn_buffer_private_symbol, "node:alpnBuffer") \
154+
V(arraybuffer_untransferable_private_symbol, "node:untransferableBuffer") \
154155
V(arrow_message_private_symbol, "node:arrowMessage") \
155156
V(contextify_context_private_symbol, "node:contextify:context") \
156157
V(contextify_global_private_symbol, "node:contextify:global") \

src/js_native_api_v8.cc

+2
Original file line numberDiff line numberDiff line change
@@ -2581,6 +2581,8 @@ napi_status napi_create_external_arraybuffer(napi_env env,
25812581
v8::Isolate* isolate = env->isolate;
25822582
v8::Local<v8::ArrayBuffer> buffer =
25832583
v8::ArrayBuffer::New(isolate, external_data, byte_length);
2584+
v8::Maybe<bool> marked = env->mark_arraybuffer_as_untransferable(buffer);
2585+
CHECK_MAYBE_NOTHING(env, marked, napi_generic_failure);
25842586

25852587
if (finalize_cb != nullptr) {
25862588
// Create a self-deleting weak reference that invokes the finalizer

src/js_native_api_v8.h

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ struct napi_env__ {
3939
inline void Unref() { if ( --refs == 0) delete this; }
4040

4141
virtual bool can_call_into_js() const { return true; }
42+
virtual v8::Maybe<bool> mark_arraybuffer_as_untransferable(
43+
v8::Local<v8::ArrayBuffer> ab) const {
44+
return v8::Just(true);
45+
}
4246

4347
template <typename T, typename U>
4448
void CallIntoModule(T&& call, U&& handle_exception) {

src/node_api.cc

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ struct node_napi_env__ : public napi_env__ {
2424
bool can_call_into_js() const override {
2525
return node_env()->can_call_into_js();
2626
}
27+
28+
v8::Maybe<bool> mark_arraybuffer_as_untransferable(
29+
v8::Local<v8::ArrayBuffer> ab) const {
30+
return ab->SetPrivate(
31+
context(),
32+
node_env()->arraybuffer_untransferable_private_symbol(),
33+
v8::True(isolate));
34+
}
2735
};
2836

2937
typedef node_napi_env__* node_napi_env;

src/node_buffer.cc

+8-4
Original file line numberDiff line numberDiff line change
@@ -350,10 +350,8 @@ MaybeLocal<Object> New(Isolate* isolate,
350350
THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
351351
return MaybeLocal<Object>();
352352
}
353-
Local<Object> obj;
354-
if (Buffer::New(env, data, length, callback, hint).ToLocal(&obj))
355-
return handle_scope.Escape(obj);
356-
return Local<Object>();
353+
return handle_scope.EscapeMaybe(
354+
Buffer::New(env, data, length, callback, hint));
357355
}
358356

359357

@@ -371,6 +369,12 @@ MaybeLocal<Object> New(Environment* env,
371369
}
372370

373371
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), data, length);
372+
if (ab->SetPrivate(env->context(),
373+
env->arraybuffer_untransferable_private_symbol(),
374+
True(env->isolate())).IsNothing()) {
375+
callback(data, hint);
376+
return Local<Object>();
377+
}
374378
MaybeLocal<Uint8Array> ui = Buffer::New(env, ab, 0, length);
375379

376380
CallbackInfo::New(env->isolate(), ab, callback, data, hint);

src/node_messaging.cc

+10-4
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,17 @@ Maybe<bool> Message::Serialize(Environment* env,
306306
// raw data *and* an Isolate with a non-default ArrayBuffer allocator
307307
// is always going to outlive any Workers it creates, and so will its
308308
// allocator along with it.
309-
// TODO(addaleax): Eventually remove the IsExternal() condition,
310-
// see https://github.com/nodejs/node/pull/30339#issuecomment-552225353
309+
if (!ab->IsDetachable()) continue;
310+
// See https://github.com/nodejs/node/pull/30339#issuecomment-552225353
311311
// for details.
312-
if (!ab->IsDetachable() || ab->IsExternal())
313-
continue;
312+
bool untransferrable;
313+
if (!ab->HasPrivate(
314+
context,
315+
env->arraybuffer_untransferable_private_symbol())
316+
.To(&untransferrable)) {
317+
return Nothing<bool>();
318+
}
319+
if (untransferrable) continue;
314320
if (std::find(array_buffers.begin(), array_buffers.end(), ab) !=
315321
array_buffers.end()) {
316322
ThrowDataCloneException(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include <node.h>
2+
#include <node_buffer.h>
3+
#include <v8.h>
4+
5+
using v8::Context;
6+
using v8::Isolate;
7+
using v8::Local;
8+
using v8::Object;
9+
using v8::Value;
10+
11+
char data[] = "hello";
12+
13+
void Initialize(Local<Object> exports,
14+
Local<Value> module,
15+
Local<Context> context) {
16+
Isolate* isolate = context->GetIsolate();
17+
exports->Set(context,
18+
v8::String::NewFromUtf8(
19+
isolate, "buffer", v8::NewStringType::kNormal)
20+
.ToLocalChecked(),
21+
node::Buffer::New(
22+
isolate,
23+
data,
24+
sizeof(data),
25+
[](char* data, void* hint) {},
26+
nullptr).ToLocalChecked()).Check();
27+
}
28+
29+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
}
8+
]
9+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const { MessageChannel } = require('worker_threads');
5+
const { buffer } = require(`./build/${common.buildType}/binding`);
6+
7+
// Test that buffers allocated with a free callback through our APIs are not
8+
// transfered.
9+
10+
const { port1 } = new MessageChannel();
11+
const origByteLength = buffer.byteLength;
12+
port1.postMessage(buffer, [buffer.buffer]);
13+
14+
assert.strictEqual(buffer.byteLength, origByteLength);
15+
assert.notStrictEqual(buffer.byteLength, 0);

0 commit comments

Comments
 (0)