Repository navigation
Open
Description
Version
v26.13.1
Platform
Linux 12cc91c12230 7.0.14-linuxkit #1 SMP PREEMPT Sun Sep 20 16:14:09 UTC 2026 aarch64 GNU/Linux
Subsystem
napi
What steps will reproduce the bug?
addon.c
#define NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_NO_WARNING
#include <node_api.h>
#undef NDEBUG
#include <assert.h>
static int dummy;
// Does nothing at all. It never touches the environment.
static void finalize(node_api_basic_env /*env*/, void* /*data*/, void* /*hint*/) {}
// wrap(object): attach the no-op finalizer to `object`
static napi_value wrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value object;
assert(napi_get_cb_info(env, info, &argc, &object, NULL, NULL) == napi_ok);
assert(napi_wrap(env, object, &dummy, finalize, NULL, NULL) == napi_ok);
return NULL;
}
// Throws. The documentation says this is reported through `uncaughtException`.
static void call_js(napi_env env, napi_value /*js_callback*/, void* /*context*/, void* /*data*/) {
assert(napi_throw_error(env, NULL, "thrown from call_js_cb") == napi_ok);
}
// start(): invoke `call_js` once through a threadsafe function
static napi_value start(napi_env env, napi_callback_info /*info*/) {
napi_value name;
assert(napi_create_string_utf8(env, "repro", NAPI_AUTO_LENGTH, &name) == napi_ok);
napi_threadsafe_function tsfn;
assert(napi_create_threadsafe_function(env, NULL, NULL, name, 0, 1, NULL, NULL, NULL, call_js, &tsfn) == napi_ok);
assert(napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking) == napi_ok);
assert(napi_release_threadsafe_function(tsfn, napi_tsfn_release) == napi_ok);
return NULL;
}
NAPI_MODULE_INIT() {
napi_property_descriptor properties[] = {
{"wrap", NULL, wrap, NULL, NULL, NULL, napi_default, NULL},
{"start", NULL, start, NULL, NULL, NULL, napi_default, NULL},
};
assert(napi_define_properties(env, exports, 2, properties) == napi_ok);
return exports;
}test.cjs
// Run with: node --expose-gc test.cjs
const addon = require("./build/Release/addon.node");
process.on("uncaughtException", error => {
console.log("uncaughtException:", error.message);
gc();
console.log("handler finished");
});
// Wrapped objects with a no-op finalizer, immediately unreachable
for (let ii = 0; ii < 100; ++ii) {
addon.wrap({});
}
addon.start();CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(addon LANGUAGES C)
add_library(addon SHARED addon.c ${CMAKE_JS_SRC})
set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node" C_STANDARD 23)
target_include_directories(addon PRIVATE ${CMAKE_JS_INC})
target_link_libraries(addon PRIVATE ${CMAKE_JS_LIB})$ cmake-js build
// [...]
$ ~/node-v26.13.1-linux-arm64/bin/node --expose-gc test.cjs
uncaughtException: thrown from call_js_cb
FATAL ERROR: Re-entrant uncaught exception detected.
The exception handler threw an error while processing a
previous uncaught exception, likely due to an inspector
protocol issue. Aborting to prevent infinite recursion.
/workspace/napi-stale-exception/test.cjs:6
gc();
^
Error: thrown from call_js_cb
----- Native stack trace -----
1: 0x840124 [/root/node-v26.13.1-linux-arm64/bin/node]
2: 0x9cd0f8 node::errors::TriggerUncaughtException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>, bool) [/root/node-v26.13.1-linux-arm64/bin/node]
3: 0x97bf38 void napi_env__::CallIntoModule<node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*)::{lambda(napi_env__*)#1}&, node_napi_env__::CallbackIntoModule<true, node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*)::{lambda(napi_env__*)#1}>(node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*)::{lambda(napi_env__*)#1}&&)::{lambda(napi_env__*, v8::Local<v8::Value>)#1}>(node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*)::{lambda(napi_env__*)#1}&, node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*)::{lambda(napi_env__*)#1}&&) [/root/node-v26.13.1-linux-arm64/bin/node]
4: 0x97aeb4 void node_napi_env__::CallFinalizer<true>(void (*)(napi_env__*, void*, void*), void*, void*) [/root/node-v26.13.1-linux-arm64/bin/node]
5: 0x9544ec v8impl::Reference::Finalize() [/root/node-v26.13.1-linux-arm64/bin/node]
6: 0x9548c8 v8impl::ReferenceWithFinalizer::InvokeFinalizerFromGC() [/root/node-v26.13.1-linux-arm64/bin/node]
7: 0xe76f50 [/root/node-v26.13.1-linux-arm64/bin/node]
8: 0xf0509c [/root/node-v26.13.1-linux-arm64/bin/node]
9: 0xf15108 [/root/node-v26.13.1-linux-arm64/bin/node]
10: 0xf14d6c [/root/node-v26.13.1-linux-arm64/bin/node]
11: 0x19820b4 [/root/node-v26.13.1-linux-arm64/bin/node]
12: 0xf01598 [/root/node-v26.13.1-linux-arm64/bin/node]
13: 0xe59ce0 [/root/node-v26.13.1-linux-arm64/bin/node]
14: 0xe58f20 [/root/node-v26.13.1-linux-arm64/bin/node]
15: 0x19df34c [/root/node-v26.13.1-linux-arm64/bin/node]
----- JavaScript stack trace -----
1: /workspace/napi-stale-exception/test.cjs:6:2
2: emit (node:events:514:20)
3: node:internal/process/execution:192:32
Aborted ~/node-v26.13.1-linux-arm64/bin/node --expose-gc test.cjs
$ bun --expose-gc test.cjs
uncaughtException: thrown from call_js_cb
handler finished
How often does it reproduce? Is there a required condition?
It reproduces reliably
What is the expected behavior? Why is that the expected behavior?
It should not crash
What do you see instead?
It crashes
Additional information
Works on v18.18.2
Crashes on v18.19.0
Works on v20.9.0
Crashes on v20.10.0 onward
Metadata
Metadata
Assignees
Labels
No labels
Activity