node/test/node-api/test_threadsafe_function_shutdown/binding.cc
Mika Fischer bff6ea49b8 node-api: fix data race and use-after-free in napi_threadsafe_function
Other threads can still hold a valid handle to the tsfn after
finalization if finalization was triggered by
- release with napi_tsfn_abort, or
- environment shutdown

Handle this by:
- protecting finalization itself with the mutex
- if necessary, delay deletion after finalization to when thread_count
  drops to 0
- releasing all resources as soon as possible before deletion

Fixes: https://github.com/nodejs/node/issues/55706
PR-URL: https://github.com/nodejs/node/pull/55877
Co-Authored-By: Gabriel Schulhof <gabrielschulhof@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2025-11-27 11:30:06 +00:00

83 lines
2.6 KiB
C++

#include <js_native_api.h>
#include <node_api.h>
#include <node_api_types.h>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <thread> // NOLINT(build/c++11)
#include <type_traits>
#include <utility>
template <typename R, auto func, typename... Args>
inline auto call(const char* name, Args&&... args) -> R {
napi_status status;
if constexpr (std::is_same_v<R, void>) {
status = func(std::forward<Args>(args)...);
if (status == napi_ok) {
return;
}
} else {
R ret;
status = func(std::forward<Args>(args)..., &ret);
if (status == napi_ok) {
return ret;
}
}
std::fprintf(stderr, "%s: %d\n", name, status);
std::abort();
}
#define NAPI_CALL(ret_type, func, ...) \
call<ret_type, func>(#func, ##__VA_ARGS__)
void thread_func(napi_threadsafe_function tsfn) {
fprintf(stderr, "thread_func: starting\n");
auto status =
napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_blocking);
while (status == napi_ok) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
status = napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_blocking);
}
fprintf(stderr, "thread_func: Got status %d, exiting...\n", status);
}
void tsfn_callback(napi_env env, napi_value js_cb, void* ctx, void* data) {
if (env == nullptr) {
fprintf(stderr, "tsfn_callback: env=%p\n", env);
}
}
void tsfn_finalize(napi_env env, void* finalize_data, void* finalize_hint) {
fprintf(stderr, "tsfn_finalize: env=%p\n", env);
}
auto run(napi_env env, napi_callback_info info) -> napi_value {
auto global = NAPI_CALL(napi_value, napi_get_global, env);
auto undefined = NAPI_CALL(napi_value, napi_get_undefined, env);
auto n_threads = 32;
auto tsfn = NAPI_CALL(napi_threadsafe_function,
napi_create_threadsafe_function,
env,
nullptr,
global,
undefined,
0,
n_threads,
nullptr,
tsfn_finalize,
nullptr,
tsfn_callback);
for (auto i = 0; i < n_threads; ++i) {
std::thread([tsfn] { thread_func(tsfn); }).detach();
}
NAPI_CALL(void, napi_unref_threadsafe_function, env, tsfn);
return NAPI_CALL(napi_value, napi_get_undefined, env);
}
napi_value init(napi_env env, napi_value exports) {
return NAPI_CALL(
napi_value, napi_create_function, env, nullptr, 0, run, nullptr);
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)