JavaScript functions can normally only be called from a native addon’s main thread. If an addon creates additional threads, then node-addon-api functions that require a Napi::Env
, Napi::Value
, or Napi::Reference
must not be called from those threads.
When an addon has additional threads and JavaScript functions need to be invoked based on the processing completed by those threads, those threads must communicate with the addon’s main thread so that the main thread can invoke the JavaScript function on their behalf. The thread-safe function APIs provide an easy way to do this.
A thread-safe function is created on the main thread via ThreadSafeFunction::New:
1 | New(napi_env env, |
A thread-safe function encapsulates:
NonBlockingCall()
is controlled via the maxQueueSize
parameter (specify 0
for unlimited queue)callback
parameter). This function is either (a) automatically ran with no arguments when called via the no-argument [Non]BlockingCall()
overloads, or (b) passed as an argument to the callback function provided in the [Non]BlockingCall(DataType* data, Callback callback)
overloads.context
parameter) to associate with the thread-safe function.finalizeCallback
parameter) to run at destruction of the thread-safe function, when all threads have finished using it.data
parameter) to provide to the finalizer callback.Threads may call into JavaScript via [Non]BlockingCall
. This will add an entry to the underlying thread-safe function’s queue, to be handled asynchronously on the main thread during its processing of the event loop.
Multiple threads can utilize the thread-safe function simultaneously. The thread-safe function manages its lifecycle through counting the number of threads actively utilizing it. This number starts at the initial thread count parameter in New()
, increased via Acquire()
, and decreased via Released()
. Once the number of active threads reaches zero, the thread-safe function is destroyed, running the finalizer callback on the main thread if provided.
Here are two general approaches to using thread-safe functions within applications:
If the amount of threads is known at thread-safe function creation, set the initial_thread_count
parameter to this number in the call to New()
. Each thread will have its own access to the thread-safe function until it calls Release()
. Once all threads have made a call to Release()
, the thread-safe function is destroyed.
Another common use-case is to dynamically create and destroy threads based on various logic at run-time. One way to handle this scenario is to expose several native JavaScript functions that interact with the thread-safe function APIs by:
New()
with initial thread count of 1
.Acquire()
and creating a new native thread. The new thread can now use [Non]BlockingCall()
.Abort()
and have each thread either call [Non]BlockingCall()
or Release()
Release()
in order to decrease the active thread count to 0
.This example node-addon-api module creates exposes a single function that creates a thread-safe function and a native thread. The function returns a promise that resolves after the native thread calls into JavaScript ten times.
1 | { |
1 |
|
1 | const { createTSFN } = require('bindings')('tsfn_example'); |
Running the above script will provide output similar to:
1 | 2019-11-25T22:14:56.175Z 0 |
By default, Node will wait until a thread-safe function is finalized before cleaning up and exiting. See Thread Management. This behavior can be changed via a call to Unref()
, permitting Node to clean up without waiting for the thread count to reach zero. A call to Ref()
will will return the threadsafe function to the previous exit behavior, requiring it to be Release()
ed and/or Abort()
ed by all threads utilizing it.
napi_closing
from a call to [Non]BlockingCall()
, does it still need to call Release()
?No. A return value of napi_closing
should signify to the thread that the thread-safe function can no longer be utilized. This includes the call to Release()
.