Skip to content

Commit e9fa1ea

Browse files
KevinEadymhdawson
authored andcommitted
doc: document ThreadSafeFunction (#494)
* doc: document ThreadSafeFunction PR-URL: #494 Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent cab3b1e commit e9fa1ea

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed

doc/threadsafe_function.md

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
# ThreadSafeFunction
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.
13+
14+
These APIs provide the type `Napi::ThreadSafeFunction` as well as APIs to
15+
create, destroy, and call objects of this type.
16+
`Napi::ThreadSafeFunction::New()` creates a persistent reference that holds a
17+
JavaScript function which can be called from multiple threads. The calls happen
18+
asynchronously. This means that values with which the JavaScript callback is to
19+
be called will be placed in a queue, and, for each value in the queue, a call
20+
will eventually be made to the JavaScript function.
21+
22+
`Napi::ThreadSafeFunction` objects are destroyed when every thread which uses
23+
the object has called `Release()` or has received a return status of
24+
`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`.
25+
The queue is emptied before the `Napi::ThreadSafeFunction` is destroyed. It is
26+
important that `Release()` be the last API call made in conjunction with a given
27+
`Napi::ThreadSafeFunction`, because after the call completes, there is no
28+
guarantee that the `Napi::ThreadSafeFunction` is still allocated. For the same
29+
reason it is also important that no more use be made of a thread-safe function
30+
after receiving a return value of `napi_closing` in response to a call to
31+
`BlockingCall()` or `NonBlockingCall()`. Data associated with the
32+
`Napi::ThreadSafeFunction` can be freed in its `Finalizer` callback which was
33+
passed to `ThreadSafeFunction::New()`.
34+
35+
Once the number of threads making use of a `Napi::ThreadSafeFunction` reaches
36+
zero, no further threads can start making use of it by calling `Acquire()`. In
37+
fact, all subsequent API calls associated with it, except `Release()`, will
38+
return an error value of `napi_closing`.
39+
40+
## Methods
41+
42+
### Constructor
43+
44+
Creates a new empty instance of `Napi::ThreadSafeFunction`.
45+
46+
```cpp
47+
Napi::Function::ThreadSafeFunction();
48+
```
49+
50+
### Constructor
51+
52+
Creates a new instance of the `Napi::ThreadSafeFunction` object.
53+
54+
```cpp
55+
Napi::ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn);
56+
```
57+
58+
- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing
59+
thread-safe function.
60+
61+
Returns a non-empty `Napi::ThreadSafeFunction` instance.
62+
63+
### New
64+
65+
Creates a new instance of the `Napi::ThreadSafeFunction` object. The `New`
66+
function has several overloads for the various optional parameters: skip the
67+
optional parameter for that specific overload.
68+
69+
```cpp
70+
New(napi_env env,
71+
const Function& callback,
72+
const Object& resource,
73+
ResourceString resourceName,
74+
size_t maxQueueSize,
75+
size_t initialThreadCount,
76+
ContextType* context,
77+
Finalizer finalizeCallback,
78+
FinalizerDataType* data);
79+
```
80+
81+
- `env`: The `napi_env` environment in which to construct the
82+
`Napi::ThreadSafeFunction` object.
83+
- `callback`: The `Function` to call from another thread.
84+
- `[optional] resource`: An object associated with the async work that will be
85+
passed to possible async_hooks init hooks.
86+
- `resourceName`: A JavaScript string to provide an identifier for the kind of
87+
resource that is being provided for diagnostic information exposed by the
88+
async_hooks API.
89+
- `maxQueueSize`: Maximum size of the queue. `0` for no limit.
90+
- `initialThreadCount`: The initial number of threads, including the main
91+
thread, which will be making use of this function.
92+
- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`.
93+
- `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction`
94+
is being destroyed. This callback will be invoked on the main thread when the
95+
thread-safe function is about to be destroyed. It receives the context and the
96+
finalize data given during construction (if given), and provides an
97+
opportunity for cleaning up after the threads e.g. by calling
98+
`uv_thread_join()`. It is important that, aside from the main loop thread,
99+
there be no threads left using the thread-safe function after the finalize
100+
callback completes. Must implement `void operator()(Env env, DataType* data,
101+
Context* hint)`, skipping `data` or `hint` if they are not provided.
102+
Can be retreived via `GetContext()`.
103+
- `[optional] data`: Data to be passed to `finalizeCallback`.
104+
105+
Returns a non-empty `Napi::ThreadSafeFunction` instance.
106+
107+
### Acquire
108+
109+
Add a thread to this thread-safe function object, indicating that a new thread
110+
will start making use of the thread-safe function.
111+
112+
```cpp
113+
napi_status Napi::ThreadSafeFunction::Acquire()
114+
```
115+
116+
Returns one of:
117+
- `napi_ok`: The thread has successfully acquired the thread-safe function
118+
for its use.
119+
- `napi_closing`: The thread-safe function has been marked as closing via a
120+
previous call to `Abort()`.
121+
122+
### Release
123+
124+
Indicate that an existing thread will stop making use of the thread-safe
125+
function. A thread should call this API when it stops making use of this
126+
thread-safe function. Using any thread-safe APIs after having called this API
127+
has undefined results in the current thread, as it may have been destroyed.
128+
129+
```cpp
130+
napi_status Napi::ThreadSafeFunction::Release()
131+
```
132+
133+
Returns one of:
134+
- `napi_ok`: The thread-safe function has been successfully released.
135+
- `napi_invalid_arg`: The thread-safe function's thread-count is zero.
136+
- `napi_generic_failure`: A generic error occurred when attemping to release
137+
the thread-safe function.
138+
139+
### Abort
140+
141+
"Abort" the thread-safe function. This will cause all subsequent APIs associated
142+
with the thread-safe function except `Release()` to return `napi_closing` even
143+
before its reference count reaches zero. In particular, `BlockingCall` and
144+
`NonBlockingCall()` will return `napi_closing`, thus informing the threads that
145+
it is no longer possible to make asynchronous calls to the thread-safe function.
146+
This can be used as a criterion for terminating the thread. Upon receiving a
147+
return value of `napi_closing` from a thread-safe function call a thread must
148+
make no further use of the thread-safe function because it is no longer
149+
guaranteed to be allocated.
150+
151+
```cpp
152+
napi_status Napi::ThreadSafeFunction::Abort()
153+
```
154+
155+
Returns one of:
156+
- `napi_ok`: The thread-safe function has been successfully aborted.
157+
- `napi_invalid_arg`: The thread-safe function's thread-count is zero.
158+
- `napi_generic_failure`: A generic error occurred when attemping to abort
159+
the thread-safe function.
160+
161+
### BlockingCall / NonBlockingCall
162+
163+
Calls the Javascript function in either a blocking or non-blocking fashion.
164+
- `BlockingCall()`: the API blocks until space becomes available in the queue.
165+
Will never block if the thread-safe function was created with a maximum queue
166+
size of `0`.
167+
- `NonBlockingCall()`: will return `napi_queue_full` if the queue was full,
168+
preventing data from being successfully added to the queue.
169+
170+
There are several overloaded implementations of `BlockingCall()` and
171+
`NonBlockingCall()` for use with optional parameters: skip the optional
172+
parameter for that specific overload.
173+
174+
```cpp
175+
napi_status Napi::ThreadSafeFunction::BlockingCall(DataType* data, Callback callback) const
176+
177+
napi_status Napi::ThreadSafeFunction::NonBlockingCall(DataType* data, Callback callback) const
178+
```
179+
180+
- `[optional] data`: Data to pass to `callback`.
181+
- `[optional] callback`: C++ function that is invoked on the main thread. The
182+
callback receives the `ThreadSafeFunction`'s JavaScript callback function to
183+
call as an `Napi::Function` in its parameters and the `DataType*` data pointer
184+
(if provided). Must implement `void operator()(Napi::Env env, Function
185+
jsCallback, DataType* data)`, skipping `data` if not provided. It is not
186+
necessary to call into JavaScript via `MakeCallback()` because N-API runs
187+
`callback` in a context appropriate for callbacks.
188+
189+
Returns one of:
190+
- `napi_ok`: The call was successfully added to the queue.
191+
- `napi_queue_full`: The queue was full when trying to call in a non-blocking
192+
method.
193+
- `napi_closing`: The thread-safe function is aborted and cannot accept more
194+
calls.
195+
- `napi_invalid_arg`: The thread-safe function is closed.
196+
- `napi_generic_failure`: A generic error occurred when attemping to add to the
197+
queue.
198+
199+
## Example
200+
201+
```cpp
202+
#include <chrono>
203+
#include <thread>
204+
#include <napi.h>
205+
206+
using namespace Napi;
207+
208+
std::thread nativeThread;
209+
ThreadSafeFunction tsfn;
210+
211+
Value Start( const CallbackInfo& info )
212+
{
213+
Napi::Env env = info.Env();
214+
215+
if ( info.Length() < 2 )
216+
{
217+
throw TypeError::New( env, "Expected two arguments" );
218+
}
219+
else if ( !info[0].IsFunction() )
220+
{
221+
throw TypeError::New( env, "Expected first arg to be function" );
222+
}
223+
else if ( !info[1].IsNumber() )
224+
{
225+
throw TypeError::New( env, "Expected second arg to be number" );
226+
}
227+
228+
int count = info[1].As<Number>().Int32Value();
229+
230+
// Create a ThreadSafeFunction
231+
tsfn = ThreadSafeFunction::New(
232+
env,
233+
info[0].As<Function>(), // JavaScript function called asynchronously
234+
"Resource Name", // Name
235+
0, // Unlimited queue
236+
1, // Only one thread will use this initially
237+
[]( Napi::Env ) { // Finalizer used to clean threads up
238+
nativeThread.join();
239+
} );
240+
241+
// Create a native thread
242+
nativeThread = std::thread( [count] {
243+
auto callback = []( Napi::Env env, Function jsCallback, int* value ) {
244+
// Transform native data into JS data, passing it to the provided
245+
// `jsCallback` -- the TSFN's JavaScript function.
246+
jsCallback.Call( {Number::New( env, *value )} );
247+
248+
// We're finished with the data.
249+
delete value;
250+
};
251+
252+
for ( int i = 0; i < count; i++ )
253+
{
254+
// Create new data
255+
int* value = new int( clock() );
256+
257+
// Perform a blocking call
258+
napi_status status = tsfn.BlockingCall( value, callback );
259+
if ( status != napi_ok )
260+
{
261+
// Handle error
262+
break;
263+
}
264+
265+
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
266+
}
267+
268+
// Release the thread-safe function
269+
tsfn.Release();
270+
} );
271+
272+
return Boolean::New(env, true);
273+
}
274+
275+
Napi::Object Init( Napi::Env env, Object exports )
276+
{
277+
exports.Set( "start", Function::New( env, Start ) );
278+
return exports;
279+
}
280+
281+
NODE_API_MODULE( clock, Init )
282+
```
283+
284+
The above code can be used from JavaScript as follows:
285+
286+
```js
287+
const { start } = require('bindings')('clock');
288+
289+
start(function () {
290+
console.log("JavaScript callback called with arguments", Array.from(arguments));
291+
}, 5);
292+
```
293+
294+
When executed, the output will show the value of `clock()` five times at one
295+
second intervals:
296+
297+
```
298+
JavaScript callback called with arguments [ 84745 ]
299+
JavaScript callback called with arguments [ 103211 ]
300+
JavaScript callback called with arguments [ 104516 ]
301+
JavaScript callback called with arguments [ 105104 ]
302+
JavaScript callback called with arguments [ 105691 ]
303+
```

0 commit comments

Comments
 (0)