Skip to content

Commit c1d7150

Browse files
authored
node-api: add napi_create_external_sharedarraybuffer
Creates a SharedArrayBuffer from externally managed memory. Fixes: #62259 PR-URL: #62623 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com> Reviewed-By: Robert Nagy <ronagy@icloud.com>
1 parent 2503408 commit c1d7150

File tree

7 files changed

+132
-2
lines changed

7 files changed

+132
-2
lines changed

doc/api/n-api.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,41 @@ object just created has been garbage collected.
25752575
JavaScript `ArrayBuffer`s are described in
25762576
[Section ArrayBuffer objects][] of the ECMAScript Language Specification.
25772577

2578+
#### `node_api_create_external_sharedarraybuffer`
2579+
2580+
<!-- YAML
2581+
added: REPLACEME
2582+
-->
2583+
2584+
```c
2585+
napi_status
2586+
node_api_create_external_sharedarraybuffer(napi_env env,
2587+
void* external_data,
2588+
size_t byte_length,
2589+
node_api_noenv_finalize finalize_cb,
2590+
void* finalize_hint,
2591+
napi_value* result)
2592+
```
2593+
2594+
* `[in] env`: The environment that the API is invoked under.
2595+
* `[in] external_data`: Pointer to the underlying byte buffer of the
2596+
`SharedArrayBuffer`.
2597+
* `[in] byte_length`: The length in bytes of the underlying buffer.
2598+
* `[in] finalize_cb`: Optional callback to call when the `SharedArrayBuffer` is
2599+
being collected. Called on an arbitrary thread. Because a `SharedArrayBuffer`
2600+
can outlive the environment it's created in, the callback does not receive a
2601+
reference to `env`.
2602+
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
2603+
collection.
2604+
* `[out] result`: A `napi_value` representing a JavaScript `SharedArrayBuffer`.
2605+
2606+
Returns `napi_ok` if the API succeeded.
2607+
2608+
Create a `SharedArrayBuffer` with externally managed memory.
2609+
2610+
See the entry on [`napi_create_external_arraybuffer`][] for runtime
2611+
compatibility.
2612+
25782613
#### `napi_create_external_buffer`
25792614

25802615
<!-- YAML

src/js_native_api.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,16 @@ napi_create_external_arraybuffer(napi_env env,
437437
node_api_basic_finalize finalize_cb,
438438
void* finalize_hint,
439439
napi_value* result);
440+
#ifdef NAPI_EXPERIMENTAL
441+
#define NODE_API_EXPERIMENTAL_HAS_CREATE_EXTERNAL_SHAREDARRAYBUFFER
442+
NAPI_EXTERN napi_status NAPI_CDECL
443+
node_api_create_external_sharedarraybuffer(napi_env env,
444+
void* external_data,
445+
size_t byte_length,
446+
node_api_noenv_finalize finalize_cb,
447+
void* finalize_hint,
448+
napi_value* result);
449+
#endif // NAPI_EXPERIMENTAL
440450
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
441451
NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info(
442452
napi_env env, napi_value arraybuffer, void** data, size_t* byte_length);

src/js_native_api_types.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env,
188188
#endif
189189
typedef node_api_nogc_finalize node_api_basic_finalize;
190190

191+
// A finalizer that can be called from any thread and at any time.
192+
typedef void(NAPI_CDECL* node_api_noenv_finalize)(void* finalize_data,
193+
void* finalize_hint);
194+
191195
typedef struct {
192196
// One of utf8name or name should be NULL.
193197
const char* utf8name;

src/js_native_api_v8.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3134,6 +3134,48 @@ napi_create_external_arraybuffer(napi_env env,
31343134
env, buffer, nullptr, nullptr, nullptr, result, nullptr);
31353135
}
31363136

3137+
napi_status NAPI_CDECL
3138+
node_api_create_external_sharedarraybuffer(napi_env env,
3139+
void* external_data,
3140+
size_t byte_length,
3141+
node_api_noenv_finalize finalize_cb,
3142+
void* finalize_hint,
3143+
napi_value* result) {
3144+
NAPI_PREAMBLE(env);
3145+
CHECK_ARG(env, result);
3146+
#ifdef V8_ENABLE_SANDBOX
3147+
return napi_set_last_error(env, napi_no_external_buffers_allowed);
3148+
#else
3149+
struct FinalizerData {
3150+
void (*cb)(void* external_data, void* finalize_hint);
3151+
void* hint;
3152+
};
3153+
auto deleter = [](void* external_data, size_t length, void* deleter_data) {
3154+
if (auto fd = static_cast<FinalizerData*>(deleter_data)) {
3155+
fd->cb(external_data, fd->hint);
3156+
delete fd;
3157+
}
3158+
};
3159+
FinalizerData* deleter_data = nullptr;
3160+
if (finalize_cb != nullptr) {
3161+
deleter_data = new FinalizerData{finalize_cb, finalize_hint};
3162+
}
3163+
auto unique_backing_store = v8::SharedArrayBuffer::NewBackingStore(
3164+
external_data,
3165+
byte_length,
3166+
deleter,
3167+
reinterpret_cast<void*>(deleter_data));
3168+
CHECK(!!unique_backing_store); // Cannot fail.
3169+
auto shared_backing_store =
3170+
std::shared_ptr<v8::BackingStore>(std::move(unique_backing_store));
3171+
auto shared_array_buffer =
3172+
v8::SharedArrayBuffer::New(env->isolate, std::move(shared_backing_store));
3173+
CHECK_MAYBE_EMPTY(env, shared_array_buffer, napi_generic_failure);
3174+
*result = v8impl::JsValueFromV8LocalValue(shared_array_buffer);
3175+
return napi_clear_last_error(env);
3176+
#endif // V8_ENABLE_SANDBOX
3177+
}
3178+
31373179
napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
31383180
napi_value arraybuffer,
31393181
void** data,

test/node-api/test_buffer/binding.gyp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
{
44
"target_name": "test_buffer",
55
"defines": [
6-
'NAPI_VERSION=10'
6+
"NAPI_EXPERIMENTAL",
7+
"NODE_API_EXPERIMENTAL_NO_WARNING"
78
],
89
"sources": [ "test_buffer.c" ]
910
},

test/node-api/test_buffer/test.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
const common = require('../../common');
55
const binding = require(`./build/${common.buildType}/test_buffer`);
66
const assert = require('assert');
7-
const tick = require('util').promisify(require('../../common/tick'));
7+
const util = require('util');
8+
const tick = util.promisify(require('../../common/tick'));
89

910
(async function() {
1011
assert.strictEqual(binding.newBuffer().toString(), binding.theText);
@@ -26,6 +27,17 @@ const tick = require('util').promisify(require('../../common/tick'));
2627
console.log('gc2');
2728
assert.strictEqual(binding.getDeleterCallCount(), 2);
2829

30+
// Caveat emptor: it's indeterminate when the SharedArrayBuffer's backing
31+
// store is reclaimed; at least some of the time it happens even before
32+
// calling gc().
33+
let sab = binding.newExternalSharedArrayBuffer();
34+
assert(util.types.isSharedArrayBuffer(sab));
35+
sab = null;
36+
global.gc();
37+
await tick(10);
38+
console.log('gc3');
39+
assert.strictEqual(binding.getDeleterCallCount(), 3);
40+
2941
// To test this doesn't crash
3042
binding.invalidObjectAsBuffer({});
3143

test/node-api/test_buffer/test_buffer.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@ static napi_value newExternalBuffer(napi_env env, napi_callback_info info) {
5858
return theBuffer;
5959
}
6060

61+
static char externalSharedArrayBufferData[1];
62+
63+
static void freeExternalSharedArrayBuffer(void* data, void* hint) {
64+
(void)hint;
65+
NODE_API_BASIC_ASSERT_RETURN_VOID(
66+
data == (void*)externalSharedArrayBufferData,
67+
"SharedArrayBuffer points to wrong data");
68+
deleterCallCount++;
69+
}
70+
71+
static napi_value newExternalSharedArrayBuffer(napi_env env,
72+
napi_callback_info info) {
73+
napi_value sab;
74+
NODE_API_CALL(
75+
env,
76+
node_api_create_external_sharedarraybuffer(env,
77+
externalSharedArrayBufferData,
78+
1,
79+
freeExternalSharedArrayBuffer,
80+
NULL,
81+
&sab));
82+
return sab;
83+
}
84+
6185
static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) {
6286
napi_value callCount;
6387
NODE_API_CALL(env, napi_create_int32(env, deleterCallCount, &callCount));
@@ -171,6 +195,8 @@ static napi_value Init(napi_env env, napi_value exports) {
171195
napi_property_descriptor methods[] = {
172196
DECLARE_NODE_API_PROPERTY("newBuffer", newBuffer),
173197
DECLARE_NODE_API_PROPERTY("newExternalBuffer", newExternalBuffer),
198+
DECLARE_NODE_API_PROPERTY("newExternalSharedArrayBuffer",
199+
newExternalSharedArrayBuffer),
174200
DECLARE_NODE_API_PROPERTY("getDeleterCallCount", getDeleterCallCount),
175201
DECLARE_NODE_API_PROPERTY("copyBuffer", copyBuffer),
176202
DECLARE_NODE_API_PROPERTY("bufferHasInstance", bufferHasInstance),

0 commit comments

Comments
 (0)