node-api: execute tsfn finalizer after queue drains when aborted · nodejs/node@87443b4
1+#include <js_native_api.h>
2+#include <node_api.h>
3+#include <node_api_types.h>
4+5+#include <assert.h>
6+#include <cstdio>
7+#include <cstdlib>
8+#include <type_traits>
9+#include <utility>
10+11+template <typename R, auto func, typename... Args>
12+inline auto call(const char* name, Args&&... args) -> R {
13+ napi_status status;
14+if constexpr (std::is_same_v<R, void>) {
15+ status = func(std::forward<Args>(args)...);
16+if (status == napi_ok) {
17+return;
18+ }
19+ } else {
20+ R ret;
21+ status = func(std::forward<Args>(args)..., &ret);
22+if (status == napi_ok) {
23+return ret;
24+ }
25+ }
26+std::fprintf(stderr, "%s: %d\n", name, status);
27+std::abort();
28+}
29+30+#define NAPI_CALL(ret_type, func, ...) \
31+ call<ret_type, func>(#func, ##__VA_ARGS__)
32+33+class Context {
34+public:
35+enum class State { kCreated, kCalled } state = State::kCreated;
36+};
37+38+void tsfn_callback(napi_env env,
39+ napi_value js_cb,
40+void* ctx_p,
41+void* /* data */) {
42+auto ctx = static_cast<Context*>(ctx_p);
43+assert(ctx->state == Context::State::kCreated);
44+ ctx->state = Context::State::kCalled;
45+}
46+47+void tsfn_finalize(napi_env env,
48+void* /* finalize_data */,
49+void* finalize_hint) {
50+auto ctx = static_cast<Context*>(finalize_hint);
51+assert(ctx->state == Context::State::kCalled);
52+delete ctx;
53+}
54+55+auto run(napi_env env, napi_callback_info info) -> napi_value {
56+auto global = NAPI_CALL(napi_value, napi_get_global, env);
57+auto undefined = NAPI_CALL(napi_value, napi_get_undefined, env);
58+auto ctx = new Context();
59+auto tsfn = NAPI_CALL(napi_threadsafe_function,
60+ napi_create_threadsafe_function,
61+ env,
62+nullptr,
63+ global,
64+ undefined,
65+0,
66+1 /* initial_thread_count */,
67+nullptr,
68+ tsfn_finalize,
69+ ctx,
70+ tsfn_callback);
71+72+NAPI_CALL(
73+void, napi_call_threadsafe_function, tsfn, nullptr, napi_tsfn_blocking);
74+75+NAPI_CALL(void, napi_unref_threadsafe_function, env, tsfn);
76+77+NAPI_CALL(void,
78+ napi_release_threadsafe_function,
79+ tsfn,
80+ napi_threadsafe_function_release_mode::napi_tsfn_abort);
81+return NAPI_CALL(napi_value, napi_get_undefined, env);
82+}
83+84+napi_value init(napi_env env, napi_value exports) {
85+return NAPI_CALL(
86+ napi_value, napi_create_function, env, nullptr, 0, run, nullptr);
87+}
88+89+NAPI_MODULE(NODE_GYP_MODULE_NAME, init)