|
| 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 | +} |
0 commit comments