1 #include <stdlib.h>
2 #include <uv.h>
3 #include <node_api.h>
4 #include "../../js-native-api/common.h"
5
6 typedef struct {
7 napi_ref js_cb_ref;
8 napi_ref js_tsfn_finalizer_ref;
9 napi_threadsafe_function tsfn;
10 uv_thread_t thread;
11 } AddonData;
12
AsyncWorkCbExecute(napi_env env,void * data)13 static void AsyncWorkCbExecute(napi_env env, void* data) {
14 (void) env;
15 (void) data;
16 }
17
call_cb_and_delete_ref(napi_env env,napi_ref * optional_ref)18 static void call_cb_and_delete_ref(napi_env env, napi_ref* optional_ref) {
19 napi_value js_cb, undefined;
20
21 if (optional_ref == NULL) {
22 AddonData* data;
23 NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
24 optional_ref = &data->js_cb_ref;
25 }
26
27 NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env,
28 *optional_ref,
29 &js_cb));
30 NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
31 NAPI_CALL_RETURN_VOID(env, napi_call_function(env,
32 undefined,
33 js_cb,
34 0,
35 NULL,
36 NULL));
37 NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, *optional_ref));
38
39 *optional_ref = NULL;
40 }
41
AsyncWorkCbComplete(napi_env env,napi_status status,void * data)42 static void AsyncWorkCbComplete(napi_env env,
43 napi_status status,
44 void* data) {
45 (void) status;
46 (void) data;
47 call_cb_and_delete_ref(env, NULL);
48 }
49
establish_callback_ref(napi_env env,napi_callback_info info)50 static bool establish_callback_ref(napi_env env, napi_callback_info info) {
51 AddonData* data;
52 size_t argc = 1;
53 napi_value js_cb;
54
55 NAPI_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false);
56 NAPI_ASSERT_BASE(env,
57 data->js_cb_ref == NULL,
58 "reference must be NULL",
59 false);
60 NAPI_CALL_BASE(env,
61 napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL),
62 false);
63 NAPI_CALL_BASE(env,
64 napi_create_reference(env, js_cb, 1, &data->js_cb_ref),
65 false);
66
67 return true;
68 }
69
AsyncWorkCallback(napi_env env,napi_callback_info info)70 static napi_value AsyncWorkCallback(napi_env env, napi_callback_info info) {
71 if (establish_callback_ref(env, info)) {
72 napi_value resource_name;
73 napi_async_work work;
74
75 NAPI_CALL(env, napi_create_string_utf8(env,
76 "AsyncIncrement",
77 NAPI_AUTO_LENGTH,
78 &resource_name));
79 NAPI_CALL(env, napi_create_async_work(env,
80 NULL,
81 resource_name,
82 AsyncWorkCbExecute,
83 AsyncWorkCbComplete,
84 NULL,
85 &work));
86 NAPI_CALL(env, napi_queue_async_work(env, work));
87 }
88
89 return NULL;
90 }
91
TestBufferFinalizerCallback(napi_env env,void * data,void * hint)92 static void TestBufferFinalizerCallback(napi_env env, void* data, void* hint) {
93 (void) data;
94 (void) hint;
95 call_cb_and_delete_ref(env, NULL);
96 }
97
TestBufferFinalizer(napi_env env,napi_callback_info info)98 static napi_value TestBufferFinalizer(napi_env env, napi_callback_info info) {
99 napi_value buffer = NULL;
100 if (establish_callback_ref(env, info)) {
101 NAPI_CALL(env, napi_create_external_buffer(env,
102 sizeof(napi_callback),
103 TestBufferFinalizer,
104 TestBufferFinalizerCallback,
105 NULL,
106 &buffer));
107 }
108 return buffer;
109 }
110
ThreadsafeFunctionCallJS(napi_env env,napi_value tsfn_cb,void * context,void * data)111 static void ThreadsafeFunctionCallJS(napi_env env,
112 napi_value tsfn_cb,
113 void* context,
114 void* data) {
115 (void) tsfn_cb;
116 (void) context;
117 (void) data;
118 call_cb_and_delete_ref(env, NULL);
119 }
120
ThreadsafeFunctionTestThread(void * raw_data)121 static void ThreadsafeFunctionTestThread(void* raw_data) {
122 AddonData* data = raw_data;
123 napi_status status;
124
125 // No need to call `napi_acquire_threadsafe_function()` because the main
126 // thread has set the refcount to 1 and there is only this one secondary
127 // thread.
128 status = napi_call_threadsafe_function(data->tsfn,
129 ThreadsafeFunctionCallJS,
130 napi_tsfn_nonblocking);
131 if (status != napi_ok) {
132 napi_fatal_error("ThreadSafeFunctionTestThread",
133 NAPI_AUTO_LENGTH,
134 "Failed to call TSFN",
135 NAPI_AUTO_LENGTH);
136 }
137
138 status = napi_release_threadsafe_function(data->tsfn, napi_tsfn_release);
139 if (status != napi_ok) {
140 napi_fatal_error("ThreadSafeFunctionTestThread",
141 NAPI_AUTO_LENGTH,
142 "Failed to release TSFN",
143 NAPI_AUTO_LENGTH);
144 }
145
146 }
147
FinalizeThreadsafeFunction(napi_env env,void * raw,void * hint)148 static void FinalizeThreadsafeFunction(napi_env env, void* raw, void* hint) {
149 AddonData* data;
150 NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
151 NAPI_ASSERT_RETURN_VOID(env,
152 uv_thread_join(&data->thread) == 0,
153 "Failed to join the thread");
154 call_cb_and_delete_ref(env, &data->js_tsfn_finalizer_ref);
155 data->tsfn = NULL;
156 }
157
158 // Ths function accepts two arguments: the JS callback, and the finalize
159 // callback. The latter moves the test forward.
160 static napi_value
TestThreadsafeFunction(napi_env env,napi_callback_info info)161 TestThreadsafeFunction(napi_env env, napi_callback_info info) {
162 AddonData* data;
163 size_t argc = 2;
164 napi_value argv[2], resource_name;
165
166 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
167 NAPI_CALL(env, napi_get_instance_data(env, (void**)&data));
168 NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL");
169 NAPI_ASSERT(env,
170 data->js_tsfn_finalizer_ref == NULL,
171 "tsfn finalizer reference must be NULL");
172 NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref));
173 NAPI_CALL(env, napi_create_reference(env,
174 argv[1],
175 1,
176 &data->js_tsfn_finalizer_ref));
177 NAPI_CALL(env, napi_create_string_utf8(env,
178 "TSFN instance data test",
179 NAPI_AUTO_LENGTH,
180 &resource_name));
181 NAPI_CALL(env, napi_create_threadsafe_function(env,
182 NULL,
183 NULL,
184 resource_name,
185 0,
186 1,
187 NULL,
188 FinalizeThreadsafeFunction,
189 NULL,
190 ThreadsafeFunctionCallJS,
191 &data->tsfn));
192 NAPI_ASSERT(env,
193 uv_thread_create(&data->thread,
194 ThreadsafeFunctionTestThread,
195 data) == 0,
196 "uv_thread_create failed");
197
198 return NULL;
199 }
200
DeleteAddonData(napi_env env,void * raw_data,void * hint)201 static void DeleteAddonData(napi_env env, void* raw_data, void* hint) {
202 AddonData* data = raw_data;
203 if (data->js_cb_ref) {
204 NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
205 }
206 if (data->js_tsfn_finalizer_ref) {
207 NAPI_CALL_RETURN_VOID(env,
208 napi_delete_reference(env,
209 data->js_tsfn_finalizer_ref));
210 }
211 free(data);
212 }
213
Init(napi_env env,napi_value exports)214 static napi_value Init(napi_env env, napi_value exports) {
215 AddonData* data = malloc(sizeof(*data));
216 data->js_cb_ref = NULL;
217 data->js_tsfn_finalizer_ref = NULL;
218
219 NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL));
220
221 napi_property_descriptor props[] = {
222 DECLARE_NAPI_PROPERTY("asyncWorkCallback", AsyncWorkCallback),
223 DECLARE_NAPI_PROPERTY("testBufferFinalizer", TestBufferFinalizer),
224 DECLARE_NAPI_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction),
225 };
226
227 NAPI_CALL(env, napi_define_properties(env,
228 exports,
229 sizeof(props) / sizeof(*props),
230 props));
231
232 return exports;
233 }
234
235 NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
236