From 3b8f8083730973c00a9b1a76674bb117c6623467 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 7 Apr 2026 11:11:15 +0200 Subject: [PATCH 1/9] node-api: add napi_create_external_sharedarraybuffer Creates a SharedArrayBuffer from externally managed memory. Fixes: https://github.com/nodejs/node/issues/62259 --- doc/api/n-api.md | 37 +++++++++++++++++++ src/js_native_api.h | 9 +++++ src/js_native_api_v8.cc | 49 +++++++++++++++++++++++++ test/node-api/test_buffer/test.js | 10 +++++ test/node-api/test_buffer/test_buffer.c | 25 +++++++++++++ 5 files changed, 130 insertions(+) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 269492b480fd20..9d5be3a69d554d 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2575,6 +2575,43 @@ object just created has been garbage collected. JavaScript `ArrayBuffer`s are described in [Section ArrayBuffer objects][] of the ECMAScript Language Specification. +#### `napi_create_external_sharedarraybuffer` + + + +```c +napi_status +napi_create_external_sharedarraybuffer(napi_env env, + void* external_data, + size_t byte_length, + void (*finalize_cb)( + void* external_data, + void* finalize_hint), + 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. Because a `SharedArrayBuffer` can outlive the environment + it was created in, the callback does not get 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` From 5d2dd37cdbde4dff1135bfb08ddc0a2644b34922 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 7 Apr 2026 12:13:31 +0200 Subject: [PATCH 5/9] squash! more more lint --- doc/api/n-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 6062749e6fbf58..dcf426884b7d71 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2599,8 +2599,8 @@ napi_create_external_sharedarraybuffer(napi_env env, `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. Because a `SharedArrayBuffer` can outlive the environment - it was created in, the callback does not get receive a reference to `env`. + being collected. Because a `SharedArrayBuffer` can outlive the environment + it was created in, the callback does not get 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`. From 236ef2841f29c10f494e207d60fb0f79cc6cb2d1 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 9 Apr 2026 22:48:09 +0200 Subject: [PATCH 6/9] squash! review feedback --- doc/api/n-api.md | 24 ++++----- src/js_native_api.h | 5 +- src/js_native_api_v8.cc | 72 ++++++++++++------------- test/node-api/test_buffer/test_buffer.c | 12 ++--- 4 files changed, 55 insertions(+), 58 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index dcf426884b7d71..c0644175a1a832 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2575,23 +2575,22 @@ object just created has been garbage collected. JavaScript `ArrayBuffer`s are described in [Section ArrayBuffer objects][] of the ECMAScript Language Specification. -#### `napi_create_external_sharedarraybuffer` +#### `node_api_create_external_sharedarraybuffer` ```c napi_status -napi_create_external_sharedarraybuffer(napi_env env, - void* external_data, - size_t byte_length, - void (*finalize_cb)( - void* external_data, - void* finalize_hint), - void* finalize_hint, - napi_value* result) +node_api_create_external_sharedarraybuffer(napi_env env, + void* external_data, + size_t byte_length, + void (*finalize_cb)( + void* external_data, + void* finalize_hint), + void* finalize_hint, + napi_value* result) ``` * `[in] env`: The environment that the API is invoked under. @@ -2599,8 +2598,9 @@ napi_create_external_sharedarraybuffer(napi_env env, `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. Because a `SharedArrayBuffer` can outlive the environment - it was created in, the callback does not get receive a reference to `env`. + 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`. diff --git a/src/js_native_api.h b/src/js_native_api.h index e2bcaa8e5bbc44..81c956a8cd9bfb 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -437,13 +437,16 @@ napi_create_external_arraybuffer(napi_env env, node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_sharedarraybuffer( +#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, void (*finalize_cb)(void* external_data, void* finalize_hint), 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); diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index fc85c2b232051c..1f2da131c0f1fd 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -136,42 +136,6 @@ napi_status NewExternalString(napi_env env, return status; } -napi_status NewExternalSharedArrayBuffer( - napi_env env, - void* external_data, - size_t byte_length, - void (*finalize_cb)(void* external_data, void* finalize_hint), - void* finalize_hint, - napi_value* result) { - 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(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(deleter_data)); - CHECK(!!unique_backing_store); // Cannot fail. - auto shared_backing_store = - std::shared_ptr(std::move(unique_backing_store)); - auto shared_array_buffer = - v8::SharedArrayBuffer::New(env->isolate, 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); -} - class TrackedStringResource : private RefTracker { public: TrackedStringResource(napi_env env, @@ -3170,15 +3134,45 @@ napi_create_external_arraybuffer(napi_env env, env, buffer, nullptr, nullptr, nullptr, result, nullptr); } -napi_status NAPI_CDECL napi_create_external_sharedarraybuffer( +napi_status NAPI_CDECL node_api_create_external_sharedarraybuffer( napi_env env, void* external_data, size_t byte_length, void (*finalize_cb)(void* external_data, void* finalize_hint), void* finalize_hint, napi_value* result) { - return v8impl::NewExternalSharedArrayBuffer( - env, external_data, byte_length, finalize_cb, finalize_hint, 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(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(deleter_data)); + CHECK(!!unique_backing_store); // Cannot fail. + auto shared_backing_store = + std::shared_ptr(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, diff --git a/test/node-api/test_buffer/test_buffer.c b/test/node-api/test_buffer/test_buffer.c index 4f2fc886323435..cb5e609e91a4cf 100644 --- a/test/node-api/test_buffer/test_buffer.c +++ b/test/node-api/test_buffer/test_buffer.c @@ -73,12 +73,12 @@ static napi_value newExternalSharedArrayBuffer(napi_env env, napi_value sab; NODE_API_CALL( env, - napi_create_external_sharedarraybuffer(env, - externalSharedArrayBufferData, - 1, - freeExternalSharedArrayBuffer, - NULL, - &sab)); + node_api_create_external_sharedarraybuffer(env, + externalSharedArrayBufferData, + 1, + freeExternalSharedArrayBuffer, + NULL, + &sab)); return sab; } From a23c5086933982c888dc117430305f8a58906a69 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 9 Apr 2026 22:53:52 +0200 Subject: [PATCH 7/9] squash! preamble --- src/js_native_api_v8.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 1f2da131c0f1fd..34c64ef06b6cbc 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -3141,6 +3141,8 @@ napi_status NAPI_CDECL node_api_create_external_sharedarraybuffer( void (*finalize_cb)(void* external_data, void* finalize_hint), 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 From 182151c1be74d2028ad5e56d8bbd305b35ff55c6 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 9 Apr 2026 23:16:58 +0200 Subject: [PATCH 8/9] squash! lint --- src/js_native_api_v8.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 34c64ef06b6cbc..42d6eff2902ca9 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -3168,9 +3168,8 @@ napi_status NAPI_CDECL node_api_create_external_sharedarraybuffer( CHECK(!!unique_backing_store); // Cannot fail. auto shared_backing_store = std::shared_ptr(std::move(unique_backing_store)); - auto shared_array_buffer = v8::SharedArrayBuffer::New( - env->isolate, - std::move(shared_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); From a021c17c632bf93261dc44306bbc8c1c813e4715 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 9 Apr 2026 23:19:58 +0200 Subject: [PATCH 9/9] squash! update binding.gyp --- test/node-api/test_buffer/binding.gyp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/node-api/test_buffer/binding.gyp b/test/node-api/test_buffer/binding.gyp index 0a1dc92de7ffb4..cc7a3fa6862d6a 100644 --- a/test/node-api/test_buffer/binding.gyp +++ b/test/node-api/test_buffer/binding.gyp @@ -3,7 +3,9 @@ { "target_name": "test_buffer", "defines": [ - 'NAPI_VERSION=10' + "NAPI_EXPERIMENTAL", + "NAPI_VERSION=10", + "NODE_API_EXPERIMENTAL_NO_WARNING" ], "sources": [ "test_buffer.c" ] },