Compare commits

...

7 Commits

Author SHA1 Message Date
190n
fff799ecca Merge branch 'main' into ben/napi-finalizer-test 2025-05-21 10:58:39 -07:00
Ben Grant
881fe0c7f1 carriage return 2025-05-20 17:23:21 -07:00
190n
a19e5d4350 bun run prettier 2025-05-20 23:56:36 +00:00
Ben Grant
771b2e0408 Merge branch 'main' into ben/napi-finalizer-test 2025-05-20 16:54:58 -07:00
Ben Grant
f1e8b5bb70 flush stdout 2025-05-20 16:54:41 -07:00
190n
88c3e91baa bun run prettier 2025-05-20 19:27:12 +00:00
190n
add6387f9f Add test from #18287 (#19775) 2025-05-20 12:23:55 -07:00
4 changed files with 84 additions and 14 deletions

View File

@@ -5,14 +5,17 @@
// during a finalizer -- not during a callback scheduled with
// node_api_post_finalizer -- so it cannot use NAPI_VERSION_EXPERIMENTAL.
#include <assert.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <js_native_api.h>
#include <node_api.h>
#include <stdio.h>
#include <stdlib.h>
#include <thread>
// "we have static_assert at home" - MSVC
char assertion[NAPI_VERSION == 8 ? 1 : -1];
static_assert(NAPI_VERSION == 8,
"this module must be built with Node-API version 8");
static std::thread::id js_thread_id;
#define NODE_API_CALL_CUSTOM_RETURN(env, call, retval) \
do { \
@@ -46,14 +49,14 @@ static void finalizer(napi_env env, void *data, void *hint) {
(void)hint;
RefHolder *ref_holder = (RefHolder *)data;
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, ref_holder->ref));
free(ref_holder);
delete ref_holder;
}
static napi_value create_ref(napi_env env, napi_callback_info info) {
(void)info;
napi_value object;
NODE_API_CALL(env, napi_create_object(env, &object));
RefHolder *ref_holder = calloc(1, sizeof *ref_holder);
RefHolder *ref_holder = new RefHolder;
NODE_API_CALL(env, napi_wrap(env, object, ref_holder, finalizer, NULL,
&ref_holder->ref));
napi_value undefined;
@@ -61,12 +64,59 @@ static napi_value create_ref(napi_env env, napi_callback_info info) {
return undefined;
}
static int buffer_finalize_count = 0;
static void buffer_finalizer(napi_env env, void *data, void *hint) {
(void)hint;
if (std::this_thread::get_id() == js_thread_id) {
printf("buffer_finalizer run from js thread\n");
} else {
printf("buffer_finalizer run from another thread\n");
}
fflush(stdout);
free(data);
buffer_finalize_count++;
}
static napi_value create_buffer(napi_env env, napi_callback_info info) {
(void)info;
static const size_t len = 1000000;
void *data = malloc(len);
memset(data, 5, len);
napi_value buf;
// JavaScriptCore often runs external ArrayBuffer finalizers off the main
// thread. In this case, Bun needs to concurrently post a task to the main
// thread to invoke the finalizer.
NODE_API_CALL(env, napi_create_external_arraybuffer(
env, data, len, buffer_finalizer, NULL, &buf));
return buf;
}
static napi_value get_buffer_finalize_count(napi_env env,
napi_callback_info info) {
(void)info;
napi_value count;
NODE_API_CALL(env, napi_create_int32(env, buffer_finalize_count, &count));
return count;
}
/* napi_value */ NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
napi_value create_ref_function;
js_thread_id = std::this_thread::get_id();
napi_value js_function;
NODE_API_CALL(env, napi_create_function(env, "create_ref", NAPI_AUTO_LENGTH,
create_ref, NULL, &js_function));
NODE_API_CALL(
env, napi_set_named_property(env, exports, "create_ref", js_function));
NODE_API_CALL(env,
napi_create_function(env, "create_ref", NAPI_AUTO_LENGTH,
create_ref, NULL, &create_ref_function));
NODE_API_CALL(env, napi_set_named_property(env, exports, "create_ref",
create_ref_function));
napi_create_function(env, "create_buffer", NAPI_AUTO_LENGTH,
create_buffer, NULL, &js_function));
NODE_API_CALL(
env, napi_set_named_property(env, exports, "create_buffer", js_function));
NODE_API_CALL(env, napi_create_function(
env, "get_buffer_finalize_count", NAPI_AUTO_LENGTH,
get_buffer_finalize_count, NULL, &js_function));
NODE_API_CALL(env, napi_set_named_property(env, exports,
"get_buffer_finalize_count",
js_function));
return exports;
}

View File

@@ -81,7 +81,7 @@
},
{
"target_name": "async_finalize_addon",
"sources": ["async_finalize_addon.c"],
"sources": ["async_finalize_addon.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"libraries": [],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],

View File

@@ -649,4 +649,19 @@ nativeTests.test_get_value_string = () => {
}
};
nativeTests.test_external_buffer_finalizer = async () => {
const n = 50;
for (let i = 0; i < n; i++) {
let arraybuffer = asyncFinalizeAddon.create_buffer();
let view = new Uint32Array(arraybuffer);
for (const int of view) {
// native module memsets to 5
assert(int == 0x05050505);
}
arraybuffer = undefined;
view = undefined;
await gcUntil(() => asyncFinalizeAddon.get_buffer_finalize_count() == i + 1);
}
};
module.exports = nativeTests;

View File

@@ -369,7 +369,12 @@ describe("napi", () => {
});
});
// TODO(@190n) test allocating in a finalizer from a napi module with the right version
describe("finalizers", () => {
it("runs asynchronously on the JS thread", () => {
const out = checkSameOutput("test_external_buffer_finalizer", []);
expect(out).toMatch(/^(buffer_finalizer run from js thread\r?\n)+resolved to undefined$/);
});
});
describe("napi_wrap", () => {
it("accepts the right kinds of values", () => {