-
Notifications
You must be signed in to change notification settings - Fork 600
/
Copy pathround_trip.c
427 lines (377 loc) · 16.6 KB
/
round_trip.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#define CHECK(expr) \
{ \
if ((expr) == 0) { \
fprintf(stderr, "%s:%d: failed assertion `%s'\n", __FILE__, __LINE__, #expr); \
fflush(stderr); \
abort(); \
} \
}
#define REPORT_EVERY 1000
// An item that will be generated from the thread, passed into JavaScript, and
// ultimately marked as resolved when the JavaScript passes it back into the
// addon instance with a return value.
typedef struct ThreadItem {
// This field is read-only once set, so it need not be protected by the mutex.
int the_prime;
// This field is only accessed from the secondary thread, so it also need not
// be protected by the mutex.
struct ThreadItem* next;
// These two values must be protected by the mutex.
bool call_has_returned;
bool return_value;
} ThreadItem;
// The data associated with an instance of the addon. This takes the place of
// global static variables, while allowing multiple instances of the addon to
// co-exist.
typedef struct {
uv_mutex_t check_status_mutex;
uv_thread_t the_thread;
napi_threadsafe_function tsfn;
napi_ref thread_item_constructor;
bool js_accepts;
} AddonData;
// This function is responsible for converting the native data coming in from
// the secondary thread to JavaScript values, and for calling the JavaScript
// function. It may also be called with `env` and `js_cb` set to `NULL` when
// Node.js is terminating and there are items coming in from the secondary
// thread left to process. In that case, this function does nothing, since it is
// the secondary thread that frees the items.
static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) {
AddonData* addon_data = (AddonData*)context;
napi_value constructor;
// The semantics of this example are such that, once the JavaScript returns
// `false`, the `ThreadItem` structures can no longer be accessed, because the
// thread terminates and frees them all. Thus, we record the instant when
// JavaScript returns `false` by setting `addon_data->js_accepts` to `false`
// in `RegisterReturnValue` below, and we use the value here to decide whether
// the data coming in from the secondary thread is stale or not.
if (addon_data->js_accepts && !(env == NULL || js_cb == NULL)) {
napi_value undefined, js_thread_item;
// Retrieve the JavaScript `undefined` value. This will serve as the `this`
// value for the function call.
CHECK(napi_get_undefined(env, &undefined) == napi_ok);
// Retrieve the constructor for the JavaScript class from which the item
// holding the native data will be constructed.
CHECK(napi_get_reference_value(env,
addon_data->thread_item_constructor,
&constructor) == napi_ok);
// Construct a new instance of the JavaScript class to hold the native item.
CHECK(napi_new_instance(env,
constructor,
0,
NULL,
&js_thread_item) == napi_ok);
// Associate the native item with the newly constructed JavaScript object.
// We assume that the JavaScript side will eventually pass this JavaScript
// object back to us via `RegisterReturnValue`, which will allow the
// eventual deallocation of the native data. That's why we do not provide a
// finalizer here.
CHECK(napi_wrap(env, js_thread_item, data, NULL, NULL, NULL) == napi_ok);
// Call the JavaScript function with the item as wrapped into an instance of
// the JavaScript `ThreadItem` class and the prime.
CHECK(napi_call_function(env,
undefined,
js_cb,
1,
&js_thread_item,
NULL) == napi_ok);
}
}
// When the thread is finished we join it to prevent memory leaks. We can safely
// set `addon_data->tsfn` to NULL, because the thread-safe function will be
// cleaned up in the background in response to the secondary thread having
// called `napi_release_threadsafe_function()`.
static void ThreadFinished(napi_env env, void* data, void* context) {
(void) context;
AddonData* addon_data = (AddonData*)data;
CHECK(uv_thread_join(&(addon_data->the_thread)) == 0);
addon_data->tsfn = NULL;
}
// The secondary thread produces prime numbers using a very inefficient
// algorithm and calls into JavaScript with every REPORT_EVERYth prime number.
// After each call it checks whether any of the previous calls have produced a
// return value, and, if so, whether that return value is `false`. A `false`
// return value indicates that the JavaScript side is no longer interested in
// receiving any values and that the thread and the thread-safe function are to
// be cleaned up. On the JavaScript thread, this is marked in
// `addon_data->js_accepts`. When set to `false`, the JavaScript thread will not
// access the thread items any further, so they can be safely deleted on this
// thread.
static void PrimeThread(void* data) {
AddonData* addon_data = (AddonData*) data;
int idx_outer, idx_inner;
int prime_count = 0;
ThreadItem* first = NULL;
ThreadItem* current = NULL;
ThreadItem* previous = NULL;
ThreadItem* returned = NULL;
// Check each integer whether it's a prime.
for (idx_outer = 2 ;; idx_outer++) {
// Check whether `idx_outer` is divisible by anything up to and not
// including itself.
for (idx_inner = 2;
idx_inner < idx_outer && idx_outer % idx_inner != 0;
idx_inner++);
// If we find a prime, and it is REPORT_EVERY primes away from the previous
// prime we found, then we send it to JavaScript.
if (idx_inner >= idx_outer && (++prime_count % REPORT_EVERY) == 0) {
// Create a new thread item and attach it to the list of outstanding
// `ThreadItem` structures representing calls into JavaScript for which
// no return value has yet been established.
current = memset(malloc(sizeof(*current)), 0, sizeof(*current));
current->the_prime = idx_outer;
current->call_has_returned = false;
current->return_value = false;
current->next = first;
first = current;
// Pass the new item into JavaScript.
CHECK(napi_call_threadsafe_function(addon_data->tsfn,
first,
napi_tsfn_blocking) == napi_ok);
}
// Pass over all outstanding thread items and check whether any of them have
// returned.
for (current = first, previous = NULL, returned = NULL;
current != NULL && returned == NULL;
previous = current,
current = current->next) {
uv_mutex_lock(&(addon_data->check_status_mutex));
if (current->call_has_returned) {
// Unhook the call that has returned from the list.
if (previous != NULL) {
previous->next = current->next;
} else {
first = current->next;
}
returned = current;
}
uv_mutex_unlock(&(addon_data->check_status_mutex));
}
// Process a return value. Free the `ThreadItem` that returned it, and break
// out of the loop if the return value was `false`.
if (returned != NULL) {
// Save the return value to a local variable because we have to check it
// after having freed the structure wherein it is stored.
bool return_value = returned->return_value;
free(returned);
if (!return_value) {
break;
}
}
}
// Before terminating the thread we free the remaining queue items. `CallJs`
// will be called with pointers to these items, perhaps after this thread has
// already freed them, but that's OK, because on the JavaScript thread
// `addon_data->js_accepts` will certainly have been set to `false`, and so
// `CallJs` will not dereference the stale pointers.
for (current = first; current != NULL;) {
previous = current;
current = current->next;
free(previous);
}
// Release the thread-safe function. This causes it to be cleaned up in the
// background.
CHECK(napi_release_threadsafe_function(addon_data->tsfn,
napi_tsfn_release) == napi_ok);
}
// This binding can be called from JavaScript to start the asynchronous prime
// generator.
static napi_value StartThread(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value js_cb, work_name;
AddonData* addon_data;
// The binding accepts one parameter - the JavaScript callback function to
// call.
CHECK(napi_get_cb_info(env,
info,
&argc,
&js_cb,
NULL,
(void*)&addon_data) == napi_ok);
// We do not create a second thread if one is already running.
CHECK(addon_data->tsfn == NULL && "Work already in progress");
addon_data->js_accepts = true;
// This string describes the asynchronous work.
CHECK(napi_create_string_utf8(env,
"Thread-safe Function Round Trip Example",
NAPI_AUTO_LENGTH,
&work_name) == napi_ok);
// The thread-safe function will be created with an unlimited queue and with
// an initial thread count of 1. The secondary thread will release the
// thread-safe function, decreasing its thread count to 0, thereby setting off
// the process of cleaning up the thread-safe function.
CHECK(napi_create_threadsafe_function(env,
js_cb,
NULL,
work_name,
0,
1,
addon_data,
ThreadFinished,
addon_data,
CallJs,
&addon_data->tsfn) == napi_ok);
// Create the thread that will produce primes and that will call into
// JavaScript using the thread-safe function.
CHECK(uv_thread_create(&(addon_data->the_thread), PrimeThread, addon_data) == 0);
return NULL;
}
static bool
is_thread_item (napi_env env, napi_ref constructor_ref, napi_value value) {
bool validate;
napi_value constructor;
CHECK(napi_get_reference_value(env,
constructor_ref,
&constructor) == napi_ok);
CHECK(napi_instanceof(env, value, constructor, &validate) == napi_ok);
return validate;
}
// We use a separate binding to register a return value for a given call into
// JavaScript, represented by a `ThreadItem` object on both the JavaScript side
// and the native side. This allows the JavaScript side to asynchronously
// determine the return value.
static napi_value RegisterReturnValue(napi_env env, napi_callback_info info) {
// This function accepts two parameters:
// 1. The thread item passed into JavaScript via `CallJs`, and
// 2. The desired return value.
size_t argc = 2;
napi_value argv[2];
AddonData* addon_data;
bool return_value;
ThreadItem* item;
// Retrieve the parameters with which this function was called.
CHECK(napi_get_cb_info(env,
info,
&argc,
argv,
NULL,
(void*)&addon_data) == napi_ok);
// If this function recorded a return value of `false` before, it means that
// the thread and the associated thread-safe function are shutting down. This,
// in turn means that the wrapped `ThreadItem` that was received in the first
// argument may also be stale. Our best strategy is to do nothing and return
// to JavaScript.
if (!addon_data->js_accepts) {
return NULL;
}
CHECK(argc == 2 && "Exactly two arguments were received");
// Make sure the first parameter is an instance of the `ThreadItem` class.
// This type check ensures that there *is* a pointer stored inside the
// JavaScript object, and that the pointer is to a `ThreadItem` structure.
CHECK(is_thread_item(env, addon_data->thread_item_constructor, argv[0]));
// Retrieve the native data from the item.
CHECK(napi_unwrap(env, argv[0], (void**)&item) == napi_ok);
// Retrieve the desired return value.
CHECK(napi_get_value_bool(env, argv[1], &return_value) == napi_ok);
// Set `js_accepts` to false in case the JavaScript callback returned false.
if (addon_data->js_accepts) {
addon_data->js_accepts = return_value;
}
// Mark the thread item as resolved, and record the JavaScript return value.
uv_mutex_lock(&(addon_data->check_status_mutex));
item->call_has_returned = true;
item->return_value = return_value;
uv_mutex_unlock(&(addon_data->check_status_mutex));
return NULL;
}
// Constructor for instances of the `ThreadItem` class. This doesn't need to do
// anything since all we want the class for is to be able to type-check
// JavaScript objects that carry within them a pointer to a native `ThreadItem`
// structure.
static napi_value ThreadItemConstructor(napi_env env, napi_callback_info info) {
return NULL;
}
// Getter for the `prime` property of the `ThreadItem` class.
static napi_value GetPrime(napi_env env, napi_callback_info info) {
napi_value jsthis, prime_property;
AddonData* ad;
CHECK(napi_ok == napi_get_cb_info(env, info, 0, 0, &jsthis, (void*)&ad));
CHECK(is_thread_item(env, ad->thread_item_constructor, jsthis));
ThreadItem* item;
CHECK(napi_ok == napi_unwrap(env, jsthis, (void**)&item));
CHECK(napi_ok == napi_create_int32(env, item->the_prime, &prime_property));
return prime_property;
}
static void addon_is_unloading(const struct napi_env__ *env, void* data, void* hint) {
AddonData* addon_data = (AddonData*)data;
uv_mutex_destroy(&(addon_data->check_status_mutex));
CHECK(napi_delete_reference(env,
addon_data->thread_item_constructor) == napi_ok);
free(data);
}
// Initialize an instance of this addon. This function may be called multiple
// times if multiple instances of Node.js are running on multiple threads, or if
// there are multiple Node.js contexts running on the same thread. The return
// value and the formal parameters in comments remind us that the function body
// that follows, within which we initialize the addon, has available to it the
// variables named in the formal parameters, and that it must return a
// `napi_value`.
/*napi_value*/ NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) {
// Create the native data that will be associated with this instance of the
// addon.
AddonData* addon_data =
memset(malloc(sizeof(*addon_data)), 0, sizeof(*addon_data));
// Attach the addon data to the exports object to ensure that they are
// destroyed together.
CHECK(napi_wrap(env,
exports,
addon_data,
addon_is_unloading,
NULL,
NULL) == napi_ok);
// Initialize the various members of the `AddonData` associated with this
// addon instance.
CHECK(uv_mutex_init(&(addon_data->check_status_mutex)) == 0);
napi_value thread_item_class;
napi_property_descriptor thread_item_properties[] = {
{ "prime", 0, 0, GetPrime, 0, 0, napi_enumerable, addon_data }
};
CHECK(napi_define_class(env,
"ThreadItem",
NAPI_AUTO_LENGTH,
ThreadItemConstructor,
addon_data,
1,
thread_item_properties,
&thread_item_class) == napi_ok);
CHECK(napi_create_reference(env,
thread_item_class,
1,
&(addon_data->thread_item_constructor)) ==
napi_ok);
// Expose the two bindings this addon provides.
napi_property_descriptor export_properties[] = {
{
"startThread",
NULL,
StartThread,
NULL,
NULL,
NULL,
napi_default,
addon_data
},
{
"registerReturnValue",
NULL,
RegisterReturnValue,
NULL,
NULL,
NULL,
napi_default,
addon_data
}
};
CHECK(napi_define_properties(env,
exports,
sizeof(export_properties) /
sizeof(export_properties[0]),
export_properties) == napi_ok);
return exports;
}