Skip to content

Commit 3ff5d15

Browse files
committed
test: add node-api tsfn test with freed context after abort
Add a test where a threadsafe function's `call_js` callback uses its context which is freed in the threadsafe function's finalizer.
1 parent e522e44 commit 3ff5d15

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include <js_native_api.h>
2+
#include <node_api.h>
3+
#include <node_api_types.h>
4+
5+
#include <cstdio>
6+
#include <functional>
7+
#include <type_traits>
8+
9+
template <typename R, auto func, typename... Args>
10+
inline auto call(const char* name, Args&&... args) -> R {
11+
napi_status status;
12+
if constexpr (std::is_same_v<R, void>) {
13+
status = func(std::forward<Args>(args)...);
14+
if (status == napi_ok) {
15+
return;
16+
}
17+
} else {
18+
R ret;
19+
status = func(std::forward<Args>(args)..., &ret);
20+
if (status == napi_ok) {
21+
return ret;
22+
}
23+
}
24+
std::fprintf(stderr, "%s: %d\n", name, status);
25+
std::abort();
26+
}
27+
28+
#define NAPI_CALL(ret_type, func, ...) \
29+
call<ret_type, func>(#func, ##__VA_ARGS__)
30+
31+
class Context {
32+
public:
33+
~Context() { std::fprintf(stderr, "Context: destructor called\n"); }
34+
35+
std::function<int*(int)> create = [](int value) {
36+
std::fprintf(stderr, "Context: create called\n");
37+
return new int(value);
38+
};
39+
40+
std::function<int(void*)> get = [](void* ptr) {
41+
std::fprintf(stderr, "Context: get called\n");
42+
return *static_cast<int*>(ptr);
43+
};
44+
45+
std::function<void(void*)> deleter = [](void* ptr) {
46+
std::fprintf(stderr, "Context: deleter called\n");
47+
delete static_cast<int*>(ptr);
48+
};
49+
};
50+
51+
void tsfn_callback(napi_env env, napi_value js_cb, void* ctx_p, void* data) {
52+
auto ctx = static_cast<Context*>(ctx_p);
53+
std::fprintf(stderr, "tsfn_callback: env=%p data=%d\n", env, ctx->get(data));
54+
ctx->deleter(data);
55+
}
56+
57+
void tsfn_finalize(napi_env env, void* finalize_data, void* finalize_hint) {
58+
auto ctx = static_cast<Context*>(finalize_hint);
59+
std::fprintf(stderr,
60+
"tsfn_finalize: env=%p finalize_data=%p finalize_hint=%p\n",
61+
env,
62+
finalize_data,
63+
finalize_hint);
64+
delete ctx;
65+
}
66+
67+
auto run(napi_env env, napi_callback_info info) -> napi_value {
68+
auto global = NAPI_CALL(napi_value, napi_get_global, env);
69+
auto undefined = NAPI_CALL(napi_value, napi_get_undefined, env);
70+
auto ctx = new Context();
71+
auto tsfn = NAPI_CALL(napi_threadsafe_function,
72+
napi_create_threadsafe_function,
73+
env,
74+
nullptr,
75+
global,
76+
undefined,
77+
0,
78+
1 /* initial_thread_count */,
79+
nullptr,
80+
tsfn_finalize,
81+
ctx,
82+
tsfn_callback);
83+
84+
NAPI_CALL(void,
85+
napi_call_threadsafe_function,
86+
tsfn,
87+
ctx->create(1),
88+
napi_tsfn_blocking);
89+
90+
NAPI_CALL(void, napi_unref_threadsafe_function, env, tsfn);
91+
92+
NAPI_CALL(void,
93+
napi_release_threadsafe_function,
94+
tsfn,
95+
napi_threadsafe_function_release_mode::napi_tsfn_abort);
96+
return NAPI_CALL(napi_value, napi_get_undefined, env);
97+
}
98+
99+
napi_value init(napi_env env, napi_value exports) {
100+
return NAPI_CALL(
101+
napi_value, napi_create_function, env, nullptr, 0, run, nullptr);
102+
}
103+
104+
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "binding",
5+
"sources": ["binding.cc"],
6+
"cflags_cc": ["--std=c++20"],
7+
'cflags!': [ '-fno-exceptions', '-fno-rtti' ],
8+
'cflags_cc!': [ '-fno-exceptions', '-fno-rtti' ],
9+
}
10+
]
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const binding = require(`./build/${common.buildType}/binding`);
5+
6+
binding();

0 commit comments

Comments
 (0)