node-api: add napi_create_external_sharedarraybuffer

Creates a SharedArrayBuffer from externally managed memory.

Fixes: https://github.com/nodejs/node/issues/62259
PR-URL: https://github.com/nodejs/node/pull/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>
This commit is contained in:
Ben Noordhuis authored and Juan José Arboleda committed 2026-07-19 17:31:05 -05:00
1 parent e9e73f56fa
commit 0e83bae7cb
7 files changed
+132 -2

No files matched your search

+35
View File
@@ -2565,6 +2565,41 @@ object just created has been garbage collected.
JavaScript `ArrayBuffer`s are described in
[Section ArrayBuffer objects][] of the ECMAScript Language Specification.
#### `node_api_create_external_sharedarraybuffer`
<!-- YAML
added: REPLACEME
-->
```c
napi_status
node_api_create_external_sharedarraybuffer(napi_env env,
void* external_data,
size_t byte_length,
node_api_noenv_finalize finalize_cb,
void* finalize_hint,
napi_value* result)
```
* `[in] env`: The environment that the API is invoked under.
* `[in] external_data`: Pointer to the underlying byte buffer of the
`SharedArrayBuffer`.
* `[in] byte_length`: The length in bytes of the underlying buffer.
* `[in] finalize_cb`: Optional callback to call when the `SharedArrayBuffer` is
being collected. Called on an arbitrary thread. Because a `SharedArrayBuffer`
can outlive the environment it's created in, the callback does not receive a
reference to `env`.
* `[in] finalize_hint`: Optional hint to pass to the finalize callback during
collection.
* `[out] result`: A `napi_value` representing a JavaScript `SharedArrayBuffer`.
Returns `napi_ok` if the API succeeded.
Create a `SharedArrayBuffer` with externally managed memory.
See the entry on [`napi_create_external_arraybuffer`][] for runtime
compatibility.
#### `napi_create_external_buffer`
<!-- YAML
+10
View File
@@ -420,6 +420,16 @@ napi_create_external_arraybuffer(napi_env env,
node_api_basic_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
#ifdef NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_HAS_CREATE_EXTERNAL_SHAREDARRAYBUFFER
NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_sharedarraybuffer(napi_env env,
void* external_data,
size_t byte_length,
node_api_noenv_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
#endif // NAPI_EXPERIMENTAL
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info(
napi_env env, napi_value arraybuffer, void** data, size_t* byte_length);
+4
View File
@@ -174,6 +174,10 @@ typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env,
#endif
typedef node_api_nogc_finalize node_api_basic_finalize;
// A finalizer that can be called from any thread and at any time.
typedef void(NAPI_CDECL* node_api_noenv_finalize)(void* finalize_data,
void* finalize_hint);
typedef struct {
// One of utf8name or name should be NULL.
const char* utf8name;
+42
View File
@@ -3068,6 +3068,48 @@ napi_create_external_arraybuffer(napi_env env,
env, buffer, nullptr, nullptr, nullptr, result, nullptr);
}
napi_status NAPI_CDECL
node_api_create_external_sharedarraybuffer(napi_env env,
void* external_data,
size_t byte_length,
node_api_noenv_finalize finalize_cb,
void* finalize_hint,
napi_value* result) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, result);
#ifdef V8_ENABLE_SANDBOX
return napi_set_last_error(env, napi_no_external_buffers_allowed);
#else
struct FinalizerData {
void (*cb)(void* external_data, void* finalize_hint);
void* hint;
};
auto deleter = [](void* external_data, size_t length, void* deleter_data) {
if (auto fd = static_cast<FinalizerData*>(deleter_data)) {
fd->cb(external_data, fd->hint);
delete fd;
}
};
FinalizerData* deleter_data = nullptr;
if (finalize_cb != nullptr) {
deleter_data = new FinalizerData{finalize_cb, finalize_hint};
}
auto unique_backing_store = v8::SharedArrayBuffer::NewBackingStore(
external_data,
byte_length,
deleter,
reinterpret_cast<void*>(deleter_data));
CHECK(!!unique_backing_store); // Cannot fail.
auto shared_backing_store =
std::shared_ptr<v8::BackingStore>(std::move(unique_backing_store));
auto shared_array_buffer =
v8::SharedArrayBuffer::New(env->isolate, std::move(shared_backing_store));
CHECK_MAYBE_EMPTY(env, shared_array_buffer, napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(shared_array_buffer);
return napi_clear_last_error(env);
#endif // V8_ENABLE_SANDBOX
}
napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
napi_value arraybuffer,
void** data,
+2 -1
View File
@@ -3,7 +3,8 @@
{
"target_name": "test_buffer",
"defines": [
'NAPI_VERSION=10'
"NAPI_EXPERIMENTAL",
"NODE_API_EXPERIMENTAL_NO_WARNING"
],
"sources": [ "test_buffer.c" ]
},
+13 -1
View File
@@ -4,7 +4,8 @@
const common = require('../../common');
const binding = require(`./build/${common.buildType}/test_buffer`);
const assert = require('assert');
const tick = require('util').promisify(require('../../common/tick'));
const util = require('util');
const tick = util.promisify(require('../../common/tick'));
(async function() {
assert.strictEqual(binding.newBuffer().toString(), binding.theText);
@@ -26,6 +27,17 @@ const tick = require('util').promisify(require('../../common/tick'));
console.log('gc2');
assert.strictEqual(binding.getDeleterCallCount(), 2);
// Caveat emptor: it's indeterminate when the SharedArrayBuffer's backing
// store is reclaimed; at least some of the time it happens even before
// calling gc().
let sab = binding.newExternalSharedArrayBuffer();
assert(util.types.isSharedArrayBuffer(sab));
sab = null;
global.gc();
await tick(10);
console.log('gc3');
assert.strictEqual(binding.getDeleterCallCount(), 3);
// To test this doesn't crash
binding.invalidObjectAsBuffer({});
+26
View File
@@ -58,6 +58,30 @@ static napi_value newExternalBuffer(napi_env env, napi_callback_info info) {
return theBuffer;
}
static char externalSharedArrayBufferData[1];
static void freeExternalSharedArrayBuffer(void* data, void* hint) {
(void)hint;
NODE_API_BASIC_ASSERT_RETURN_VOID(
data == (void*)externalSharedArrayBufferData,
"SharedArrayBuffer points to wrong data");
deleterCallCount++;
}
static napi_value newExternalSharedArrayBuffer(napi_env env,
napi_callback_info info) {
napi_value sab;
NODE_API_CALL(
env,
node_api_create_external_sharedarraybuffer(env,
externalSharedArrayBufferData,
1,
freeExternalSharedArrayBuffer,
NULL,
&sab));
return sab;
}
static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) {
napi_value callCount;
NODE_API_CALL(env, napi_create_int32(env, deleterCallCount, &callCount));
@@ -171,6 +195,8 @@ static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor methods[] = {
DECLARE_NODE_API_PROPERTY("newBuffer", newBuffer),
DECLARE_NODE_API_PROPERTY("newExternalBuffer", newExternalBuffer),
DECLARE_NODE_API_PROPERTY("newExternalSharedArrayBuffer",
newExternalSharedArrayBuffer),
DECLARE_NODE_API_PROPERTY("getDeleterCallCount", getDeleterCallCount),
DECLARE_NODE_API_PROPERTY("copyBuffer", copyBuffer),
DECLARE_NODE_API_PROPERTY("bufferHasInstance", bufferHasInstance),