Skip to content

Commit 0e83bae

Browse files
bnoordhuisjuanarbol
authored andcommitted
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 e9e73f5 commit 0e83bae

7 files changed

Lines changed: 132 additions & 2 deletions

File tree

doc/api/n-api.md

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

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

25702605
<!-- YAML

src/js_native_api.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ napi_create_external_arraybuffer(napi_env env,
420420
node_api_basic_finalize finalize_cb,
421421
void* finalize_hint,
422422
napi_value* result);
423+
#ifdef NAPI_EXPERIMENTAL
424+
#define NODE_API_EXPERIMENTAL_HAS_CREATE_EXTERNAL_SHAREDARRAYBUFFER
425+
NAPI_EXTERN napi_status NAPI_CDECL
426+
node_api_create_external_sharedarraybuffer(napi_env env,
427+
void* external_data,
428+
size_t byte_length,
429+
node_api_noenv_finalize finalize_cb,
430+
void* finalize_hint,
431+
napi_value* result);
432+
#endif // NAPI_EXPERIMENTAL
423433
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
424434
NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info(
425435
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
@@ -174,6 +174,10 @@ typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env,
174174
#endif
175175
typedef node_api_nogc_finalize node_api_basic_finalize;
176176

177+
// A finalizer that can be called from any thread and at any time.
178+
typedef void(NAPI_CDECL* node_api_noenv_finalize)(void* finalize_data,
179+
void* finalize_hint);
180+
177181
typedef struct {
178182
// One of utf8name or name should be NULL.
179183
const char* utf8name;

src/js_native_api_v8.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,6 +3068,48 @@ napi_create_external_arraybuffer(napi_env env,
30683068
env, buffer, nullptr, nullptr, nullptr, result, nullptr);
30693069
}
30703070

3071+
napi_status NAPI_CDECL
3072+
node_api_create_external_sharedarraybuffer(napi_env env,
3073+
void* external_data,
3074+
size_t byte_length,
3075+
node_api_noenv_finalize finalize_cb,
3076+
void* finalize_hint,
3077+
napi_value* result) {
3078+
NAPI_PREAMBLE(env);
3079+
CHECK_ARG(env, result);
3080+
#ifdef V8_ENABLE_SANDBOX
3081+
return napi_set_last_error(env, napi_no_external_buffers_allowed);
3082+
#else
3083+
struct FinalizerData {
3084+
void (*cb)(void* external_data, void* finalize_hint);
3085+
void* hint;
3086+
};
3087+
auto deleter = [](void* external_data, size_t length, void* deleter_data) {
3088+
if (auto fd = static_cast<FinalizerData*>(deleter_data)) {
3089+
fd->cb(external_data, fd->hint);
3090+
delete fd;
3091+
}
3092+
};
3093+
FinalizerData* deleter_data = nullptr;
3094+
if (finalize_cb != nullptr) {
3095+
deleter_data = new FinalizerData{finalize_cb, finalize_hint};
3096+
}
3097+
auto unique_backing_store = v8::SharedArrayBuffer::NewBackingStore(
3098+
external_data,
3099+
byte_length,
3100+
deleter,
3101+
reinterpret_cast<void*>(deleter_data));
3102+
CHECK(!!unique_backing_store); // Cannot fail.
3103+
auto shared_backing_store =
3104+
std::shared_ptr<v8::BackingStore>(std::move(unique_backing_store));
3105+
auto shared_array_buffer =
3106+
v8::SharedArrayBuffer::New(env->isolate, std::move(shared_backing_store));
3107+
CHECK_MAYBE_EMPTY(env, shared_array_buffer, napi_generic_failure);
3108+
*result = v8impl::JsValueFromV8LocalValue(shared_array_buffer);
3109+
return napi_clear_last_error(env);
3110+
#endif // V8_ENABLE_SANDBOX
3111+
}
3112+
30713113
napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
30723114
napi_value arraybuffer,
30733115
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)