Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

#### `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,
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. 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
Expand Down
10 changes: 10 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,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,
void (*finalize_cb)(void* external_data, void* finalize_hint),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically do not use the inline function pointer types.
Can we use the node_api_basic_finalize here?
I guess it should OK to document there that env is null there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that intentionally, of course. Passing nullptr is just setting up users for segfaults.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the semantic of this finalizer is different from node_api_basic_finalize. node_api_basic_finalize is still invoked on the JS thread. But this finalizer could be invoked on non-main-JS thread. I think it is worth a dedicated type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would be better to define a new type alias for it. It is better to follow the established patterns.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 on dedicated type

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);
Expand Down
42 changes: 42 additions & 0 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3134,6 +3134,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,
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
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,
Expand Down
4 changes: 3 additions & 1 deletion test/node-api/test_buffer/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
{
"target_name": "test_buffer",
"defines": [
'NAPI_VERSION=10'
"NAPI_EXPERIMENTAL",
"NAPI_VERSION=10",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NAPI_EXPERIMENTAL overrides NAPI_VERSION=10. It is better to avoid confusion and remove the NAPI_VERSION=10.

"NODE_API_EXPERIMENTAL_NO_WARNING"
],
"sources": [ "test_buffer.c" ]
},
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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion:

Suggested change
let sab = binding.newExternalSharedArrayBuffer();
let sab = binding.newExternalSharedArrayBuffer();
assert(util.types.isSharedArrayBuffer(sab));

sab = null; // eslint-disable-line no-unused-vars
global.gc();
await tick(10);
console.log('gc3');
assert.strictEqual(binding.getDeleterCallCount(), 3);

// 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,
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));
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