|
| 1 | +# Thread-safe Functions |
| 2 | + |
| 3 | +JavaScript functions can normally only be called from a native addon's main |
| 4 | +thread. If an addon creates additional threads, then node-addon-api functions |
| 5 | +that require a `Napi::Env`, `Napi::Value`, or `Napi::Reference` must not be |
| 6 | +called from those threads. |
| 7 | + |
| 8 | +When an addon has additional threads and JavaScript functions need to be invoked |
| 9 | +based on the processing completed by those threads, those threads must |
| 10 | +communicate with the addon's main thread so that the main thread can invoke the |
| 11 | +JavaScript function on their behalf. The thread-safe function APIs provide an |
| 12 | +easy way to do this. These APIs provide two types -- |
| 13 | +[`Napi::ThreadSafeFunction`](threadsafe_function.md) and |
| 14 | +[`Napi::TypedThreadSafeFunction`](typed_threadsafe_function.md) -- as well as |
| 15 | +APIs to create, destroy, and call objects of this type. The differences between |
| 16 | +the two are subtle and are [highlighted below](#implementation-differences). |
| 17 | +Regardless of which type you choose, the APIs between the two are similar. |
| 18 | + |
| 19 | +`Napi::[Typed]ThreadSafeFunction::New()` creates a persistent reference that |
| 20 | +holds a JavaScript function which can be called from multiple threads. The calls |
| 21 | +happen asynchronously. This means that values with which the JavaScript callback |
| 22 | +is to be called will be placed in a queue, and, for each value in the queue, a |
| 23 | +call will eventually be made to the JavaScript function. |
| 24 | + |
| 25 | +`Napi::[Typed]ThreadSafeFunction` objects are destroyed when every thread which |
| 26 | +uses the object has called `Release()` or has received a return status of |
| 27 | +`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`. |
| 28 | +The queue is emptied before the `Napi::[Typed]ThreadSafeFunction` is destroyed. |
| 29 | +It is important that `Release()` be the last API call made in conjunction with a |
| 30 | +given `Napi::[Typed]ThreadSafeFunction`, because after the call completes, there |
| 31 | +is no guarantee that the `Napi::[Typed]ThreadSafeFunction` is still allocated. |
| 32 | +For the same reason it is also important that no more use be made of a |
| 33 | +thread-safe function after receiving a return value of `napi_closing` in |
| 34 | +response to a call to `BlockingCall()` or `NonBlockingCall()`. Data associated |
| 35 | +with the `Napi::[Typed]ThreadSafeFunction` can be freed in its `Finalizer` |
| 36 | +callback which was passed to `[Typed]ThreadSafeFunction::New()`. |
| 37 | + |
| 38 | +Once the number of threads making use of a `Napi::[Typed]ThreadSafeFunction` |
| 39 | +reaches zero, no further threads can start making use of it by calling |
| 40 | +`Acquire()`. In fact, all subsequent API calls associated with it, except |
| 41 | +`Release()`, will return an error value of `napi_closing`. |
| 42 | + |
| 43 | +## Implementation Differences |
| 44 | + |
| 45 | +The choice between `Napi::ThreadSafeFunction` and |
| 46 | +`Napi::TypedThreadSafeFunction` depends largely on how you plan to execute your |
| 47 | +native C++ code (the "callback") on the Node.js thread. |
| 48 | + |
| 49 | +### [`Napi::ThreadSafeFunction`](threadsafe_function.md) |
| 50 | + |
| 51 | +This API is designed without N-API 5 native support for [the optional JavaScript |
| 52 | + function callback feature](https://github.com/nodejs/node/commit/53297e66cb). |
| 53 | + `::New` methods that do not have a `Function` parameter will construct a |
| 54 | + _new_, no-op `Function` on the environment to pass to the underlying N-API |
| 55 | + call. |
| 56 | + |
| 57 | +This API has some dynamic functionality, in that: |
| 58 | +- The `[Non]BlockingCall()` methods provide a `Napi::Function` parameter as the |
| 59 | + callback to run when processing the data item on the main thread -- the |
| 60 | + `CallJs` callback. Since the callback is a parameter, it can be changed for |
| 61 | + every call. |
| 62 | +- Different C++ data types may be passed with each call of `[Non]BlockingCall()` |
| 63 | + to match the specific data type as specified in the `CallJs` callback. |
| 64 | + |
| 65 | +Note that this functionality comes with some **additional overhead** and |
| 66 | +situational **memory leaks**: |
| 67 | +- The API acts as a "broker" between the underlying `napi_threadsafe_function`, |
| 68 | + and dynamically constructs a wrapper for your callback on the heap for every |
| 69 | + call to `[Non]BlockingCall()`. |
| 70 | +- In acting in this "broker" fashion, the API will call the underlying "make |
| 71 | + call" N-API method on this packaged item. If the API has determined the |
| 72 | + thread-safe function is no longer accessible (eg. all threads have released |
| 73 | + yet there are still items on the queue), **the callback passed to |
| 74 | + [Non]BlockingCall will not execute**. This means it is impossible to perform |
| 75 | + clean-up for calls that never execute their `CallJs` callback. **This may lead |
| 76 | + to memory leaks** if you are dynamically allocating memory. |
| 77 | +- The `CallJs` does not receive the thread-safe function's context as a |
| 78 | + parameter. In order for the callback to access the context, it must have a |
| 79 | + reference to either (1) the context directly, or (2) the thread-safe function |
| 80 | + to call `GetContext()`. Furthermore, the `GetContext()` method is not |
| 81 | + _type-safe_, as the method returns an object that can be "any-casted", instead |
| 82 | + of having a static type. |
| 83 | + |
| 84 | +### [`Napi::TypedThreadSafeFunction`](typed_threadsafe_function.md) |
| 85 | + |
| 86 | +The `TypedThreadSafeFunction` class is a new implementation to address the |
| 87 | +drawbacks listed above. The API is designed with N-API 5's support of an |
| 88 | +optional function callback. The API will correctly allow developers to pass |
| 89 | +`std::nullptr` instead of a `const Function&` for the callback function |
| 90 | +specified in `::New`. It also provides helper APIs to _target_ N-API 4 and |
| 91 | +construct a no-op `Function` **or** to target N-API 5 and "construct" a |
| 92 | +`std::nullptr` callback. This allows a single codebase to use the same APIs, |
| 93 | +with just a switch of the `NAPI_VERSION` compile-time constant. |
| 94 | + |
| 95 | +The removal of the dynamic call functionality has the following implications: |
| 96 | +- The API does _not_ act as a "broker" compared to the |
| 97 | + `Napi::ThreadSafeFunction`. Once Node.js finalizes the thread-safe function, |
| 98 | + the `CallJs` callback will execute with an empty `Napi::Env` for any remaining |
| 99 | + items on the queue. This provides the ability to handle any necessary cleanup |
| 100 | + of the item's data. |
| 101 | +- The callback _does_ receive the context as a parameter, so a call to |
| 102 | + `GetContext()` is _not_ necessary. This context type is specified as the |
| 103 | + **first template argument** specified to `::New`, ensuring type safety. |
| 104 | +- The `New()` constructor accepts the `CallJs` callback as the **second type |
| 105 | + argument**. The callback must be statically defined for the API to access it. |
| 106 | + This affords the ability to statically pass the context as the correct type |
| 107 | + across all methods. |
| 108 | +- Only one C++ data type may be specified to every call to `[Non]BlockingCall()` |
| 109 | + -- the **third template argument** specified to `::New`. Any "dynamic call |
| 110 | + data" must be implemented by the user. |
| 111 | + |
| 112 | + |
| 113 | +### Usage Suggestions |
| 114 | + |
| 115 | +In summary, it may be best to use `Napi::TypedThreadSafeFunction` if: |
| 116 | + |
| 117 | +- static, compile-time support for targeting N-API 4 or 5+ with an optional |
| 118 | + JavaScript callback feature is desired; |
| 119 | +- the callback can have `static` storage class and will not change across calls |
| 120 | + to `[Non]BlockingCall()`; |
| 121 | +- cleanup of items' data is required (eg. deleting dynamically-allocated data |
| 122 | + that is created at the caller level). |
| 123 | + |
| 124 | +Otherwise, `Napi::ThreadSafeFunction` may be a better choice. |
0 commit comments