Skip to content

Commit d03c3d4

Browse files
author
Gabriel Schulhof
committed
Add async work thread-safe function example
Add an example that calls into JavaScript from the `Execute` callback of a `napi_async_work` item by using a `napi_threadsafe_function`. PR-URL: nodejs#68 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
1 parent 3d3c62a commit d03c3d4

4 files changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
#define NAPI_EXPERIMENTAL
4+
#include <node_api.h>
5+
6+
// Limit ourselves to this many primes, starting at 2
7+
#define PRIME_COUNT 100000
8+
#define REPORT_EVERY 1000
9+
10+
typedef struct {
11+
napi_async_work work;
12+
napi_threadsafe_function tsfn;
13+
} AddonData;
14+
15+
16+
// This function is responsible for converting data coming in from the worker
17+
// thread to napi_value items that can be passed into JavaScript, and for
18+
// calling the JavaScript function.
19+
static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) {
20+
// This parameter is not used.
21+
(void) context;
22+
23+
// Retrieve the prime from the item created by the worker thread.
24+
int the_prime = *(int*)data;
25+
26+
// env and js_cb may both be NULL if Node.js is in its cleanup phase, and
27+
// items are left over from earlier thread-safe calls from the worker thread.
28+
// When env is NULL, we simply skip over the call into Javascript and free the
29+
// items.
30+
if (env != NULL) {
31+
napi_value undefined, js_the_prime;
32+
33+
// Convert the integer to a napi_value.
34+
assert(napi_create_int32(env, the_prime, &js_the_prime) == napi_ok);
35+
36+
// Retrieve the JavaScript `undefined` value so we can use it as the `this`
37+
// value of the JavaScript function call.
38+
assert(napi_get_undefined(env, &undefined) == napi_ok);
39+
40+
// Call the JavaScript function and pass it the prime that the secondary
41+
// thread found.
42+
assert(napi_call_function(env,
43+
undefined,
44+
js_cb,
45+
1,
46+
&js_the_prime,
47+
NULL) == napi_ok);
48+
}
49+
50+
// Free the item created by the worker thread.
51+
free(data);
52+
}
53+
54+
// This function runs on a worker thread. It has no access to the JavaScript
55+
// environment except through the thread-safe function.
56+
static void ExecuteWork(napi_env env, void* data) {
57+
AddonData* addon_data = (AddonData*)data;
58+
int idx_inner, idx_outer;
59+
int prime_count = 0;
60+
61+
// We bracket the use of the thread-safe function by this thread by a call to
62+
// napi_acquire_threadsafe_function() here, and by a call to
63+
// napi_release_threadsafe_function() immediately prior to thread exit.
64+
assert(napi_acquire_threadsafe_function(addon_data->tsfn) == napi_ok);
65+
66+
// Find the first 1000 prime numbers using an extremely inefficient algorithm.
67+
for (idx_outer = 2; prime_count < PRIME_COUNT; idx_outer++) {
68+
for (idx_inner = 2; idx_inner < idx_outer; idx_inner++) {
69+
if (idx_outer % idx_inner == 0) {
70+
break;
71+
}
72+
}
73+
if (idx_inner < idx_outer) {
74+
continue;
75+
}
76+
77+
// We found a prime. If it's the tenth since the last time we sent one to
78+
// JavaScript, send it to JavaScript.
79+
if (!(++prime_count % REPORT_EVERY)) {
80+
81+
// Save the prime number to the heap. The JavaScript marshaller (CallJs)
82+
// will free this item after having sent it to JavaScript.
83+
int* the_prime = malloc(sizeof(*the_prime));
84+
*the_prime = idx_outer;
85+
86+
// Initiate the call into JavaScript. The call into JavaScript will not
87+
// have happened when this function returns, but it will be queued.
88+
assert(napi_call_threadsafe_function(addon_data->tsfn,
89+
the_prime,
90+
napi_tsfn_blocking) == napi_ok);
91+
}
92+
}
93+
94+
// Indicate that this thread will make no further use of the thread-safe function.
95+
assert(napi_release_threadsafe_function(addon_data->tsfn,
96+
napi_tsfn_release) == napi_ok);
97+
}
98+
99+
// This function runs on the main thread after `ExecuteWork` exits.
100+
static void WorkComplete(napi_env env, napi_status status, void* data) {
101+
AddonData* addon_data = (AddonData*)data;
102+
103+
// Clean up the thread-safe function and the work item associated with this
104+
// run.
105+
assert(napi_release_threadsafe_function(addon_data->tsfn,
106+
napi_tsfn_release) == napi_ok);
107+
assert(napi_delete_async_work(env, addon_data->work) == napi_ok);
108+
109+
// Set both values to NULL so JavaScript can order a new run of the thread.
110+
addon_data->work = NULL;
111+
addon_data->tsfn = NULL;
112+
}
113+
114+
// Create a thread-safe function and an async queue work item. We pass the
115+
// thread-safe function to the async queue work item so the latter might have a
116+
// chance to call into JavaScript from the worker thread on which the
117+
// ExecuteWork callback runs.
118+
static napi_value StartThread(napi_env env, napi_callback_info info) {
119+
size_t argc = 1;
120+
napi_value js_cb, work_name;
121+
AddonData* addon_data;
122+
123+
// Retrieve the JavaScript callback we should call with items generated by the
124+
// worker thread, and the per-addon data.
125+
assert(napi_get_cb_info(env,
126+
info,
127+
&argc,
128+
&js_cb,
129+
NULL,
130+
(void**)(&addon_data)) == napi_ok);
131+
132+
// Ensure that no work is currently in progress.
133+
assert(addon_data->work == NULL && "Only one work item must exist at a time");
134+
135+
// Create a string to describe this asynchronous operation.
136+
assert(napi_create_string_utf8(env,
137+
"N-API Thread-safe Call from Async Work Item",
138+
NAPI_AUTO_LENGTH,
139+
&work_name) == napi_ok);
140+
141+
// Convert the callback retrieved from JavaScript into a thread-safe function
142+
// which we can call from a worker thread.
143+
assert(napi_create_threadsafe_function(env,
144+
js_cb,
145+
NULL,
146+
work_name,
147+
0,
148+
1,
149+
NULL,
150+
NULL,
151+
NULL,
152+
CallJs,
153+
&(addon_data->tsfn)) == napi_ok);
154+
155+
// Create an async work item, passing in the addon data, which will give the
156+
// worker thread access to the above-created thread-safe function.
157+
assert(napi_create_async_work(env,
158+
NULL,
159+
work_name,
160+
ExecuteWork,
161+
WorkComplete,
162+
addon_data,
163+
&(addon_data->work)) == napi_ok);
164+
165+
// Queue the work item for execution.
166+
assert(napi_queue_async_work(env, addon_data->work) == napi_ok);
167+
168+
// This causes `undefined` to be returned to JavaScript.
169+
return NULL;
170+
}
171+
172+
// Free the per-addon-instance data.
173+
static void addon_getting_unloaded(napi_env env, void* data, void* hint) {
174+
AddonData* addon_data = (AddonData*)data;
175+
assert(addon_data->work == NULL &&
176+
"No work item in progress at module unload");
177+
free(addon_data);
178+
}
179+
180+
// The commented-out return type and the commented out formal function
181+
// parameters below help us keep in mind the signature of the addon
182+
// initialization function. We write the body as though the return value were as
183+
// commented below and as though there were parameters passed in as commented
184+
// below.
185+
/*napi_value*/ NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) {
186+
187+
// Define addon-level data associated with this instance of the addon.
188+
AddonData* addon_data = (AddonData*)malloc(sizeof(*addon_data));
189+
addon_data->work = NULL;
190+
191+
// Define the properties that will be set on exports.
192+
napi_property_descriptor start_work = {
193+
"startThread",
194+
NULL,
195+
StartThread,
196+
NULL,
197+
NULL,
198+
NULL,
199+
napi_default,
200+
addon_data
201+
};
202+
203+
// Decorate exports with the above-defined properties.
204+
assert(napi_define_properties(env, exports, 1, &start_work) == napi_ok);
205+
206+
// Associate the addon data with the exports object, to make sure that when
207+
// the addon gets unloaded our data gets freed.
208+
assert(napi_wrap(env,
209+
exports,
210+
addon_data,
211+
addon_getting_unloaded,
212+
NULL,
213+
NULL) == napi_ok);
214+
215+
// Return the decorated exports object.
216+
return exports;
217+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.c' ]
6+
}
7+
]
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Use the "bindings" package to locate the native bindings.
2+
const binding = require('bindings')('binding');
3+
4+
// Call the function "startThread" which the native bindings library exposes.
5+
// The function accepts a callback which it will call from the worker thread and
6+
// into which it will pass prime numbers. This callback simply prints them out.
7+
binding.startThread((thePrime) =>
8+
console.log("Received prime from secondary thread: " + thePrime));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "async_work_thread_safe_function",
3+
"version": "0.0.0",
4+
"description": "Calling into JS from the thread pool",
5+
"main": "index.js",
6+
"private": true,
7+
"dependencies": {
8+
"bindings": "~1.2.1"
9+
},
10+
"scripts": {
11+
"test": "node index.js"
12+
},
13+
"gypfile": true
14+
}

0 commit comments

Comments
 (0)