Skip to content

Commit

Permalink
doc: document ThreadSafeFunction
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinEady committed Jun 26, 2019
1 parent 64a6847 commit 186ebf2
Showing 1 changed file with 121 additions and 37 deletions.
158 changes: 121 additions & 37 deletions doc/threadsafe_function.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
# ThreadSafeFunction

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 `Env`, `Value`, or `Ref` 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.

These APIs provide the type `Napi::ThreadSafeFunction` as well as APIs to create, destroy, and call objects of this type. `Napi::ThreadSafeFunction::New()` creates a persistent reference that holds a JavaScript function which can be called from multiple threads. The calls happen asynchronously. This means that values with which the JavaScript callback is to be called will be placed in a queue, and, for each value in the queue, a call will eventually be made to the JavaScript function.

`ThreadSafeFunction` objects are destroyed when every thread which uses the object has called `Release()` or has received a return status of `CLOSING` in response to a call to `BlockingCall()` or `NonBlockingCall()`. The queue is emptied before the `ThreadSafeFunction` is destroyed. It is important that `Release()` be the last API call made in conjunction with a given `ThreadSafeFunction`, because after the call completes, there is no guarantee that the `ThreadSafeFunction` is still allocated. For the same reason it is also important that no more use be made of a thread-safe function after receiving a return value of `CLOSING` in response to a call to `BlockingCall()` or `NonBlockingCall()`. Data associated with the `ThreadSafeFunction` can be freed in its `Finalizer` callback which was passed to `ThreadSafeFunction::New()`.

Once the number of threads making use of a `ThreadSafeFunction` reaches zero, no further threads can start making use of it by calling `Acquire()`. In fact, all subsequent API calls associated with it, except `Release()`, will return an error value of napi_closing.
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.

These APIs provide the type `Napi::ThreadSafeFunction` as well as APIs to
create, destroy, and call objects of this type.
`Napi::ThreadSafeFunction::New()` creates a persistent reference that holds a
JavaScript function which can be called from multiple threads. The calls happen
asynchronously. This means that values with which the JavaScript callback is to
be called will be placed in a queue, and, for each value in the queue, a call
will eventually be made to the JavaScript function.

`Napi::ThreadSafeFunction` objects are destroyed when every thread which uses
the object has called `Release()` or has received a return status of
`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`.
The queue is emptied before the `Napi::ThreadSafeFunction` is destroyed. It is
important that `Release()` be the last API call made in conjunction with a given
`Napi::ThreadSafeFunction`, because after the call completes, there is no
guarantee that the `Napi::ThreadSafeFunction` is still allocated. For the same
reason it is also important that no more use be made of a thread-safe function
after receiving a return value of `napi_closing` in response to a call to
`BlockingCall()` or `NonBlockingCall()`. Data associated with the
`Napi::ThreadSafeFunction` can be freed in its `Finalizer` callback which was
passed to `ThreadSafeFunction::New()`.

Once the number of threads making use of a `Napi::ThreadSafeFunction` reaches
zero, no further threads can start making use of it by calling `Acquire()`. In
fact, all subsequent API calls associated with it, except `Release()`, will
return an error value of `napi_closing`.

## Methods

Expand All @@ -28,13 +55,16 @@ Creates a new instance of the `Napi::ThreadSafeFunction` object.
Napi::ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn);
```
- `[in] value`: The `napi_threadsafe_function` which is a handle for an existing napi threadsafe function.
- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing
thread-safe function.
Returns a non-empty `Napi::ThreadSafeFunction` instance.
### New
Creates a new instance of the `Napi::ThreadSafeFunction` object. The `New` function has several overloads for the various optional parameters: skip the optional parameter for that specific overload.
Creates a new instance of the `Napi::ThreadSafeFunction` object. The `New`
function has several overloads for the various optional parameters: skip the
optional parameter for that specific overload.
```cpp
New(napi_env env,
Expand All @@ -43,60 +73,103 @@ New(napi_env env,
ResourceString resourceName,
size_t maxQueueSize,
size_t initialThreadCount,
DataType* data,
ContextType* context,
Finalizer finalizeCallback,
Context* context);
FinalizerDataType* data);
```

- `env`: The `napi_env` environment in which to construct the `Napi::ThreadSafeFunction` object.
- `env`: The `napi_env` environment in which to construct the
`Napi::ThreadSafeFunction` object.
- `callback`: The `Function` to call from another thread.
- `[optional] resource`: An object associated with the async work that will be passed to possible async_hooks init hooks.
- `resourceName`: A JavaScript string to provide an identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API.
- `[optional] resource`: An object associated with the async work that will be
passed to possible async_hooks init hooks.
- `resourceName`: A JavaScript string to provide an identifier for the kind of
resource that is being provided for diagnostic information exposed by the
async_hooks API.
- `maxQueueSize`: Maximum size of the queue. `0` for no limit.
- `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function.
- `initialThreadCount`: The initial number of threads, including the main
thread, which will be making use of this function.
- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`.
- `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction`
is being destroyed. This callback will be invoked on the main thread when the
thread-safe function is about to be destroyed. It receives the context and the
finalize data given during construction (if given), and provides an
opportunity for cleaning up after the threads e.g. by calling
`uv_thread_join()`. It is important that, aside from the main loop thread,
there be no threads left using the thread-safe function after the finalize
callback completes. Must implement `void operator()(Env env, DataType* data,
Context* hint)`, skipping `data` or `hint` if they are not provided.
Can be retreived via `GetContext()`.
- `[optional] data`: Data to be passed to `finalizeCallback`.
- `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It receives the context and the finalize data given during construction (if given), and provides an opportunity for cleaning up after the threads e.g. by calling `uv_thread_join()`. It is important that, aside from the main loop thread, there be no threads left using the thread-safe function after the finalize callback completes. Must implement `void operator()(Env env, DataType* data, Context* hint)`, skipping `data` or `hint` if they are not provided.
- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. Can be retreived via `GetContext()`.

Returns a non-empty `Napi::ThreadSafeFunction` instance.

### Acquire

Add a thread to this thread-safe function object, indicating that a new thread will start making use of the thread-safe function.
Add a thread to this thread-safe function object, indicating that a new thread
will start making use of the thread-safe function.

```cpp
bool Napi::ThreadSafeFunction::Acquire()
napi_status Napi::ThreadSafeFunction::Acquire()
```

Returns `true` if the thread can successfully be added to the thread count.
Returns one of:
- `napi_ok`: The thread has successfully acquired the thread-safe function
for its use.
- `napi_closing`: The thread-safe function has been marked as closing via a
previous call to `Abort()`.

### Release

Indicate that an existing thread will stop making use of the thread-safe function. A thread should call this API when it stops making use of this thread-safe function. Using any thread-safe APIs after having called this API has undefined results in the current thread, as it may have been destroyed.
Indicate that an existing thread will stop making use of the thread-safe
function. A thread should call this API when it stops making use of this
thread-safe function. Using any thread-safe APIs after having called this API
has undefined results in the current thread, as it may have been destroyed.

```cpp
bool Napi::ThreadSafeFunction::Release()
napi_status Napi::ThreadSafeFunction::Release()
```

Returns `true` if the thread-safe function has successfully released.
Returns one of:
- `napi_ok`: The thread-safe function has been successfully released.
- `napi_invalid_arg`: The thread-safe function's thread-count is zero.
- `napi_generic_failure`: A generic error occurred when attemping to release
the thread-safe function.

### Abort

"Abort" the thread-safe function. This will cause all subsequent APIs associated with the thread-safe function except `Release()` to return `napi_closing` even before its reference count reaches zero. In particular, `BlockingCall` and `NonBlockingCall()` will return `napi_closing`, thus informing the threads that it is no longer possible to make asynchronous calls to the thread-safe function. This can be used as a criterion for terminating the thread. Upon receiving a return value of `napi_closing` from a threadsafe function call a thread must make no further use of the thread-safe function because it is no longer guaranteed to be allocated.
"Abort" the thread-safe function. This will cause all subsequent APIs associated
with the thread-safe function except `Release()` to return `napi_closing` even
before its reference count reaches zero. In particular, `BlockingCall` and
`NonBlockingCall()` will return `napi_closing`, thus informing the threads that
it is no longer possible to make asynchronous calls to the thread-safe function.
This can be used as a criterion for terminating the thread. Upon receiving a
return value of `napi_closing` from a thread-safe function call a thread must
make no further use of the thread-safe function because it is no longer
guaranteed to be allocated.

```cpp
napi_status Napi::ThreadSafeFunction::Abort()
```

Returns `napi_ok` if the thread-safe function has successfully aborted.
Returns one of:
- `napi_ok`: The thread-safe function has been successfully aborted.
- `napi_invalid_arg`: The thread-safe function's thread-count is zero.
- `napi_generic_failure`: A generic error occurred when attemping to abort
the thread-safe function.

### BlockingCall / NonBlockingCall

Calls the Javascript function in a either a blocking or non-blocking fashion.
- `BlockingCall()`: the API blocks until space becomes available in the queue. Will never block if the thread-safe function was created with a maximum queue size of `0`.
- `NonBlockingCall()`: will return `napi_queue_full` if the queue was full, preventing data from being successfully added to the queue
Calls the Javascript function in either a blocking or non-blocking fashion.
- `BlockingCall()`: the API blocks until space becomes available in the queue.
Will never block if the thread-safe function was created with a maximum queue
size of `0`.
- `NonBlockingCall()`: will return `napi_queue_full` if the queue was full,
preventing data from being successfully added to the queue.

There are several overloaded implementations of `BlockingCall()` and `NonBlockingCall()` for use with optional parameters: skip the optional parameter for that specific overload.
There are several overloaded implementations of `BlockingCall()` and
`NonBlockingCall()` for use with optional parameters: skip the optional
parameter for that specific overload.

```cpp
napi_status Napi::ThreadSafeFunction::BlockingCall(DataType* data, Callback callback) const
Expand All @@ -105,14 +178,23 @@ napi_status Napi::ThreadSafeFunction::NonBlockingCall(DataType* data, Callback c
```
- `[optional] data`: Data to pass to `callback`.
- `[optional] callback`: C++ function that is invoked on the main thread. The callback receives the `ThreadSafeFunction`'s JavaScript callback function to call as an `Napi::Function` in its parameters and the `DataType*` data pointer (if provided). Must implement `void operator()(napi_env env, Function jsCallback, DataType* data)`, skipping `data` if not provided. It is not necessary to call into JavaScript via `MakeCallback()` because N-API runs `callback` in a context appropriate for callbacks.
- `[optional] callback`: C++ function that is invoked on the main thread. The
callback receives the `ThreadSafeFunction`'s JavaScript callback function to
call as an `Napi::Function` in its parameters and the `DataType*` data pointer
(if provided). Must implement `void operator()(Napi::Env env, Function
jsCallback, DataType* data)`, skipping `data` if not provided. It is not
necessary to call into JavaScript via `MakeCallback()` because N-API runs
`callback` in a context appropriate for callbacks.
Returns one of:
- `napi_ok`: The call was successfully added to the queue.
- `napi_queue_full`: The queue was full when trying to call in a non-blocking method.
- `napi_closing`: The thread-safe function is aborted and cannot accept more calls.
- `napi_queue_full`: The queue was full when trying to call in a non-blocking
method.
- `napi_closing`: The thread-safe function is aborted and cannot accept more
calls.
- `napi_invalid_arg`: The thread-safe function is closed.
- `napi_generic_failure`: A generic error occurred when attemping to add to the queue.
- `napi_generic_failure`: A generic error occurred when attemping to add to the
queue.
## Example
Expand Down Expand Up @@ -159,7 +241,8 @@ Value Start( const CallbackInfo& info )
// Create a native thread
nativeThread = std::thread( [count] {
auto callback = []( Napi::Env env, Function jsCallback, int* value ) {
// Transform native data into JS data, passing it to the provided `jsCallback` -- the TSFN's JavaScript function.
// Transform native data into JS data, passing it to the provided
// `jsCallback` -- the TSFN's JavaScript function.
jsCallback.Call( {Number::New( env, *value )} );
// We're finished with the data.
Expand Down Expand Up @@ -208,7 +291,8 @@ start(function () {
}, 5);
```

When executed, the output will show the output of `clock()` five times in one second intervals:
When executed, the output will show the value of `clock()` five times at one
second intervals:

```
JavaScript callback called with arguments [ 84745 ]
Expand Down

0 comments on commit 186ebf2

Please sign in to comment.