Skip to content
Merged
37 changes: 37 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated

<!-- YAML
added: REPLACEME
napiVersion: 1
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
-->

```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.
Comment thread
bnoordhuis marked this conversation as resolved.

#### `napi_create_external_buffer`

<!-- YAML
Expand Down
7 changes: 7 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,13 @@ 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(
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
napi_env env,
void* external_data,
size_t byte_length,
void (*finalize_cb)(void* external_data, void* finalize_hint),
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
void* finalize_hint,
napi_value* result);
#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);
Expand Down
47 changes: 47 additions & 0 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,42 @@ napi_status NewExternalString(napi_env env,
return status;
}

napi_status NewExternalSharedArrayBuffer(
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
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<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(
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
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, shared_backing_store);
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
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,
Expand Down Expand Up @@ -3134,6 +3170,17 @@ napi_create_external_arraybuffer(napi_env env,
env, buffer, nullptr, nullptr, nullptr, result, nullptr);
}

napi_status NAPI_CDECL 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) {
return v8impl::NewExternalSharedArrayBuffer(
Comment thread
bnoordhuis marked this conversation as resolved.
Outdated
env, external_data, byte_length, finalize_cb, finalize_hint, result);
}

napi_status NAPI_CDECL napi_get_arraybuffer_info(napi_env env,
napi_value arraybuffer,
void** data,
Expand Down
10 changes: 10 additions & 0 deletions test/node-api/test_buffer/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ 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();
Comment thread
bnoordhuis marked this conversation as resolved.
sab = null; // eslint-disable-line no-unused-vars
global.gc();
await tick(10);
console.log('gc3');
assert.strictEqual(binding.getDeleterCallCount(), 3);
Comment thread
bnoordhuis marked this conversation as resolved.

// To test this doesn't crash
binding.invalidObjectAsBuffer({});

Expand Down
26 changes: 26 additions & 0 deletions test/node-api/test_buffer/test_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
napi_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));
Expand Down Expand Up @@ -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),
Expand Down
Loading