Skip to content

Commit d52e75c

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 d52e75c

File tree

3 files changed

+122
-0
lines changed

3 files changed

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