Skip to content

Commit 3b8f808

Browse files
committed
node-api: add napi_create_external_sharedarraybuffer
Creates a SharedArrayBuffer from externally managed memory. Fixes: #62259
1 parent 511a57a commit 3b8f808

5 files changed

Lines changed: 130 additions & 0 deletions

File tree

doc/api/n-api.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,43 @@ 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+
#### `napi_create_external_sharedarraybuffer`
2579+
2580+
<!-- YAML
2581+
added: TODO
2582+
napiVersion: 1
2583+
-->
2584+
2585+
```c
2586+
napi_status
2587+
napi_create_external_sharedarraybuffer(napi_env env,
2588+
void* external_data,
2589+
size_t byte_length,
2590+
void (*finalize_cb)(
2591+
void* external_data,
2592+
void* finalize_hint),
2593+
void* finalize_hint,
2594+
napi_value* result)
2595+
```
2596+
2597+
* `[in] env`: The environment that the API is invoked under.
2598+
* `[in] external_data`: Pointer to the underlying byte buffer of the
2599+
`SharedArrayBuffer`.
2600+
* `[in] byte_length`: The length in bytes of the underlying buffer.
2601+
* `[in] finalize_cb`: Optional callback to call when the `SharedArrayBuffer` is
2602+
being collected. Because a `SharedArrayBuffer` can outlive the environment
2603+
it was created in, the callback does not get receive a reference to `env`.
2604+
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
2605+
collection.
2606+
* `[out] result`: A `napi_value` representing a JavaScript `SharedArrayBuffer`.
2607+
2608+
Returns `napi_ok` if the API succeeded.
2609+
2610+
Create a `SharedArrayBuffer` with externally managed memory.
2611+
2612+
See the entry on [`napi_create_external_arraybuffer`][] for runtime
2613+
compatibility.
2614+
25782615
#### `napi_create_external_buffer`
25792616

25802617
<!-- YAML

src/js_native_api.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,15 @@ napi_create_external_arraybuffer(napi_env env,
437437
node_api_basic_finalize finalize_cb,
438438
void* finalize_hint,
439439
napi_value* result);
440+
NAPI_EXTERN napi_status NAPI_CDECL
441+
napi_create_external_sharedarraybuffer(napi_env env,
442+
void* external_data,
443+
size_t byte_length,
444+
void (*finalize_cb)(
445+
void* external_data,
446+
void* finalize_hint),
447+
void* finalize_hint,
448+
napi_value* result);
440449
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
441450
NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info(
442451
napi_env env, napi_value arraybuffer, void** data, size_t* byte_length);

src/js_native_api_v8.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,42 @@ napi_status NewExternalString(napi_env env,
136136
return status;
137137
}
138138

139+
napi_status NewExternalSharedArrayBuffer(napi_env env,
140+
void* external_data,
141+
size_t byte_length,
142+
void (*finalize_cb)(
143+
void* external_data,
144+
void* finalize_hint),
145+
void* finalize_hint,
146+
napi_value* result) {
147+
struct FinalizerData {
148+
void (*cb)(void* external_data, void* finalize_hint);
149+
void* hint;
150+
};
151+
auto deleter = [](void* external_data, size_t length, void* deleter_data) {
152+
if (auto fd = static_cast<FinalizerData*>(deleter_data)) {
153+
fd->cb(external_data, fd->hint);
154+
delete fd;
155+
}
156+
};
157+
FinalizerData* deleter_data = nullptr;
158+
if (finalize_cb != nullptr) {
159+
deleter_data = new FinalizerData { finalize_cb, finalize_hint };
160+
}
161+
auto unique_backing_store =
162+
v8::SharedArrayBuffer::NewBackingStore(
163+
external_data, byte_length, deleter,
164+
reinterpret_cast<void*>(deleter_data));
165+
CHECK(!!unique_backing_store); // Cannot fail.
166+
auto shared_backing_store =
167+
std::shared_ptr<v8::BackingStore>(std::move(unique_backing_store));
168+
auto shared_array_buffer =
169+
v8::SharedArrayBuffer::New(env->isolate, shared_backing_store);
170+
CHECK_MAYBE_EMPTY(env, shared_array_buffer, napi_generic_failure);
171+
*result = v8impl::JsValueFromV8LocalValue(shared_array_buffer);
172+
return napi_clear_last_error(env);
173+
}
174+
139175
class TrackedStringResource : private RefTracker {
140176
public:
141177
TrackedStringResource(napi_env env,
@@ -3134,6 +3170,19 @@ napi_create_external_arraybuffer(napi_env env,
31343170
env, buffer, nullptr, nullptr, nullptr, result, nullptr);
31353171
}
31363172

3173+
napi_status NAPI_CDECL
3174+
napi_create_external_sharedarraybuffer(napi_env env,
3175+
void* external_data,
3176+
size_t byte_length,
3177+
void (*finalize_cb)(
3178+
void* external_data,
3179+
void* finalize_hint),
3180+
void* finalize_hint,
3181+
napi_value* result) {
3182+
return v8impl::NewExternalSharedArrayBuffer(
3183+
env, external_data, byte_length, finalize_cb, finalize_hint, result);
3184+
}
3185+
31373186
napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
31383187
napi_value arraybuffer,
31393188
void** data,

test/node-api/test_buffer/test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ const tick = require('util').promisify(require('../../common/tick'));
2626
console.log('gc2');
2727
assert.strictEqual(binding.getDeleterCallCount(), 2);
2828

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

test/node-api/test_buffer/test_buffer.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ 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(env,
75+
napi_create_external_sharedarraybuffer(env,
76+
externalSharedArrayBufferData,
77+
1,
78+
freeExternalSharedArrayBuffer,
79+
NULL,
80+
&sab));
81+
return sab;
82+
}
83+
6184
static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) {
6285
napi_value callCount;
6386
NODE_API_CALL(env, napi_create_int32(env, deleterCallCount, &callCount));
@@ -171,6 +194,8 @@ static napi_value Init(napi_env env, napi_value exports) {
171194
napi_property_descriptor methods[] = {
172195
DECLARE_NODE_API_PROPERTY("newBuffer", newBuffer),
173196
DECLARE_NODE_API_PROPERTY("newExternalBuffer", newExternalBuffer),
197+
DECLARE_NODE_API_PROPERTY("newExternalSharedArrayBuffer",
198+
newExternalSharedArrayBuffer),
174199
DECLARE_NODE_API_PROPERTY("getDeleterCallCount", getDeleterCallCount),
175200
DECLARE_NODE_API_PROPERTY("copyBuffer", copyBuffer),
176201
DECLARE_NODE_API_PROPERTY("bufferHasInstance", bufferHasInstance),

0 commit comments

Comments
 (0)