Skip to content

Commit 75dd422

Browse files
committed
doc,test: finish TSFNEx
1 parent d092a32 commit 75dd422

File tree

8 files changed

+1064
-411
lines changed

8 files changed

+1064
-411
lines changed

doc/threadsafe.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ situational **memory leaks**:
6969
callback on the heap for every call to `[Non]BlockingCall()`.
7070
- In acting in this "middle-man" fashion, the API will call the underlying "make
7171
call" N-API method on this packaged item. If the API has determined the
72-
threadsafe function is no longer accessible (eg. all threads have Released yet
72+
thread-safe function is no longer accessible (eg. all threads have released yet
7373
there are still items on the queue), **the callback passed to
7474
[Non]BlockingCall will not execute**. This means it is impossible to perform
7575
clean-up for calls that never execute their `CallJs` callback. **This may lead
7676
to memory leaks** if you are dynamically allocating memory.
77-
- The `CallJs` does not receive the threadsafe function's context as a
77+
- The `CallJs` does not receive the thread-safe function's context as a
7878
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 threadsafe function
79+
reference to either (1) the context directly, or (2) the thread-safe function
8080
to call `GetContext()`. Furthermore, the `GetContext()` method is not
8181
_type-safe_, as the method returns an object that can be "any-casted", instead
8282
of having a static type.
@@ -94,7 +94,7 @@ with just a switch of the `NAPI_VERSION` compile-time constant.
9494

9595
The removal of the dynamic call functionality has the additional side effects:
9696
- The API does _not_ act as a "middle-man" compared to the non-`Ex`. Once Node
97-
finalizes the threadsafe function, the `CallJs` callback will execute with an
97+
finalizes the thread-safe function, the `CallJs` callback will execute with an
9898
empty `Napi::Env` for any remaining items on the queue. This provides the the
9999
ability to handle any necessary clean up of the item's data.
100100
- The callback _does_ receive the context as a parameter, so a call to

doc/threadsafe_function_ex.md

Lines changed: 83 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
# TODO
2-
- Document new N-API 5+ only methods
3-
- Continue with examples
4-
51
# ThreadSafeFunctionEx
62

73
The `Napi::ThreadSafeFunctionEx` type provides APIs for threads to communicate
84
with the addon's main thread to invoke JavaScript functions on their behalf. The
95
type is a three-argument templated class, each argument representing the type
106
of:
11-
- `ContextType = std::nullptr_t`: The threadsafe function's context. By default,
7+
- `ContextType = std::nullptr_t`: The thread-safe function's context. By default,
128
a TSFN has no context.
139
- `DataType = void*`: The data to use in the native callback. By default, a TSFN
1410
can accept *any* data type.
15-
- `Callback = void*(Napi::Env, Napi::Function, ContextType*, DataType*)`: The
16-
callback to run for each item added to the queue.
11+
- `Callback = void*(Napi::Env, Napi::Function jsCallback, ContextType*,
12+
DataType*)`: The callback to run for each item added to the queue. If no
13+
`Callback` is given, the API will call the function `jsCallback` with no
14+
arguments.
1715

1816
Documentation can be found for an [overview of the API](threadsafe.md), as well
1917
as [differences between the two thread-safe function
@@ -40,46 +38,20 @@ Napi::ThreadSafeFunctionEx<ContextType, DataType, Callback>::ThreadSafeFunctionE
4038
- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing
4139
thread-safe function.
4240

43-
Returns a non-empty `Napi::ThreadSafeFunctionEx` instance.
44-
45-
### New
46-
47-
Creates a new instance of the `Napi::ThreadSafeFunctionEx` object.
48-
49-
```cpp
50-
New(napi_env env,
51-
const Function& callback,
52-
const Object& resource,
53-
ResourceString resourceName,
54-
size_t maxQueueSize,
55-
size_t initialThreadCount,
56-
ContextType* context = nullptr);
57-
```
58-
59-
- `env`: The `napi_env` environment in which to construct the
60-
`Napi::ThreadSafeFunction` object.
61-
- `callback`: The `Function` to call from another thread.
62-
- `resource`: An object associated with the async work that will be passed to
63-
possible async_hooks init hooks.
64-
- `resourceName`: A JavaScript string to provide an identifier for the kind of
65-
resource that is being provided for diagnostic information exposed by the
66-
async_hooks API.
67-
- `maxQueueSize`: Maximum size of the queue. `0` for no limit.
68-
- `initialThreadCount`: The initial number of threads, including the main
69-
thread, which will be making use of this function.
70-
- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`.
71-
Can be retreived via `GetContext()`.
72-
73-
Returns a non-empty `Napi::ThreadSafeFunction` instance.
41+
Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. To ensure the API
42+
statically handles the correct return type for `GetContext()` and
43+
`[Non]BlockingCall()`, pass the proper type arguments to
44+
`Napi::ThreadSafeFunctionEx`.
7445

7546
### New
7647

77-
Creates a new instance of the `Napi::ThreadSafeFunctionEx` object with a
78-
finalizer that runs when the object is being destroyed.
48+
Creates a new instance of the `Napi::ThreadSafeFunctionEx` object. The `New`
49+
function has several overloads for the various optional parameters: skip the
50+
optional parameter for that specific overload.
7951

8052
```cpp
8153
New(napi_env env,
82-
const Function& callback,
54+
CallbackType callback,
8355
const Object& resource,
8456
ResourceString resourceName,
8557
size_t maxQueueSize,
@@ -91,9 +63,9 @@ New(napi_env env,
9163
9264
- `env`: The `napi_env` environment in which to construct the
9365
`Napi::ThreadSafeFunction` object.
94-
- `callback`: The `Function` to call from another thread.
95-
- `resource`: An object associated with the async work that will be passed to
96-
possible async_hooks init hooks.
66+
- `[optional] callback`: The `Function` to call from another thread.
67+
- `[optional] resource`: An object associated with the async work that will be
68+
passed to possible async_hooks init hooks.
9769
- `resourceName`: A JavaScript string to provide an identifier for the kind of
9870
resource that is being provided for diagnostic information exposed by the
9971
async_hooks API.
@@ -102,19 +74,31 @@ New(napi_env env,
10274
thread, which will be making use of this function.
10375
- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`.
10476
Can be retreived via `GetContext()`.
105-
- `finalizeCallback`: Function to call when the `ThreadSafeFunctionEx` is being
106-
destroyed. This callback will be invoked on the main thread when the
107-
thread-safe function is about to be destroyed. It receives the context and the
108-
finalize data given during construction (if given), and provides an
109-
opportunity for cleaning up after the threads e.g. by calling
110-
`uv_thread_join()`. It is important that, aside from the main loop thread,
111-
there be no threads left using the thread-safe function after the finalize
112-
callback completes. Must implement `void operator()(Env env, DataType* data,
113-
ContextType* hint)`.
77+
- `[optional] finalizeCallback`: Function to call when the
78+
`ThreadSafeFunctionEx` is being destroyed. This callback will be invoked on
79+
the main thread when the thread-safe function is about to be destroyed. It
80+
receives the context and the finalize data given during construction (if
81+
given), and provides an opportunity for cleaning up after the threads e.g. by
82+
calling `uv_thread_join()`. It is important that, aside from the main loop
83+
thread, there be no threads left using the thread-safe function after the
84+
finalize callback completes. Must implement `void operator()(Env env,
85+
DataType* data, ContextType* hint)`.
11486
- `[optional] data`: Data to be passed to `finalizeCallback`.
11587
11688
Returns a non-empty `Napi::ThreadSafeFunctionEx` instance.
11789
90+
Depending on the targetted `NAPI_VERSION`, the API has different implementations
91+
for `CallbackType callback`.
92+
93+
When targetting version 4, `CallbackType` is:
94+
- `const Function&`
95+
- skipped, in which case the API creates a new no-op `Function`
96+
97+
When targetting version 5+, `CallbackType` is:
98+
- `const Function&`
99+
- `std::nullptr_t`
100+
- skipped, in which case the API passes `std::nullptr`
101+
118102
### Acquire
119103
120104
Add a thread to this thread-safe function object, indicating that a new thread
@@ -206,113 +190,54 @@ Returns one of:
206190

207191
## Example
208192

209-
```cpp
210-
#include <chrono>
211-
#include <thread>
212-
#include <napi.h>
213-
214-
using namespace Napi;
215-
216-
std::thread nativeThread;
217-
218-
struct ContextType {
219-
int threadId;
220-
};
221-
222-
using DataType = int;
223-
224-
using ThreadSafeFunctionEx = tsfn;
225-
226-
Value Start( const CallbackInfo& info )
227-
{
228-
Napi::Env env = info.Env();
229-
230-
if ( info.Length() < 2 )
231-
{
232-
throw TypeError::New( env, "Expected two arguments" );
233-
}
234-
else if ( !info[0].IsFunction() )
235-
{
236-
throw TypeError::New( env, "Expected first arg to be function" );
237-
}
238-
else if ( !info[1].IsNumber() )
239-
{
240-
throw TypeError::New( env, "Expected second arg to be number" );
241-
}
242-
243-
int count = info[1].As<Number>().Int32Value();
244-
245-
// Create a ThreadSafeFunction
246-
tsfn = ThreadSafeFunction::New(
247-
env,
248-
info[0].As<Function>(), // JavaScript function called asynchronously
249-
"Resource Name", // Name
250-
0, // Unlimited queue
251-
1, // Only one thread will use this initially
252-
[]( Napi::Env ) { // Finalizer used to clean threads up
253-
nativeThread.join();
254-
} );
255-
256-
// Create a native thread
257-
nativeThread = std::thread( [count] {
258-
auto callback = []( Napi::Env env, Function jsCallback, int* value ) {
259-
// Transform native data into JS data, passing it to the provided
260-
// `jsCallback` -- the TSFN's JavaScript function.
261-
jsCallback.Call( {Number::New( env, *value )} );
262-
263-
// We're finished with the data.
264-
delete value;
265-
};
266-
267-
for ( int i = 0; i < count; i++ )
268-
{
269-
// Create new data
270-
int* value = new int( clock() );
271-
272-
// Perform a blocking call
273-
napi_status status = tsfn.BlockingCall( value, callback );
274-
if ( status != napi_ok )
275-
{
276-
// Handle error
277-
break;
278-
}
279-
280-
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
281-
}
282-
283-
// Release the thread-safe function
284-
tsfn.Release();
285-
} );
286-
287-
return Boolean::New(env, true);
288-
}
289-
290-
Napi::Object Init( Napi::Env env, Object exports )
291-
{
292-
exports.Set( "start", Function::New( env, Start ) );
293-
return exports;
294-
}
295-
296-
NODE_API_MODULE( clock, Init )
297-
```
298-
299-
The above code can be used from JavaScript as follows:
193+
For an in-line documented example, please see the ThreadSafeFunctionEx CI tests hosted here.
194+
- [test/threadsafe_function_ex/test/example.js](../test/threadsafe_function_ex/test/example.js)
195+
- [test/threadsafe_function_ex/test/example.cc](../test/threadsafe_function_ex/test/example.cc)
300196

301-
```js
302-
const { start } = require('bindings')('clock');
303-
304-
start(function () {
305-
console.log("JavaScript callback called with arguments", Array.from(arguments));
306-
}, 5);
307-
```
197+
The example will create multiple set of threads. Each thread calls into
198+
JavaScript with a numeric `base` value (deterministically calculated by the
199+
thread id), with Node returning either a `number` or `Promise<number>` that
200+
resolves to `base * base`.
308201

309-
When executed, the output will show the value of `clock()` five times at one
310-
second intervals:
202+
From the root of the `node-addon-api` repository:
311203

312204
```
313-
JavaScript callback called with arguments [ 84745 ]
314-
JavaScript callback called with arguments [ 103211 ]
315-
JavaScript callback called with arguments [ 104516 ]
316-
JavaScript callback called with arguments [ 105104 ]
317-
JavaScript callback called with arguments [ 105691 ]
205+
Usage: node ./test/threadsafe_function_ex/test/example.js [options]
206+
207+
-c, --calls <calls> The number of calls each thread should make (number[]).
208+
-a, --acquire [factor] Acquire a new set of `factor` call threads, using the
209+
same `calls` definition.
210+
-d, --call-delay <call-delays> The delay on callback resolution that each thread should
211+
have (number[]). This is achieved via a delayed Promise
212+
resolution in the JavaScript callback provided to the
213+
TSFN. Using large delays here will cause all threads to
214+
bottle-neck.
215+
-D, --thread-delay <thread-delays> The delay that each thread should have prior to making a
216+
call (number[]). Using large delays here will cause the
217+
individual thread to bottle-neck.
218+
-l, --log-call Display console.log-based logging messages.
219+
-L, --log-thread Display std::cout-based logging messages.
220+
-n, --no-callback Do not use a JavaScript callback.
221+
-e, --callback-error [thread[.call]] Cause an error to occur in the JavaScript callback for
222+
the given thread's call (if provided; first thread's
223+
first call otherwise).
224+
225+
When not provided:
226+
- <calls> defaults to [1,2,3,4,5]
227+
- [factor] defaults to 1
228+
- <call-delays> defaults to [400,200,100,50,0]
229+
- <thread-delays> defaults to [400,200,100,50,0]
230+
231+
232+
Examples:
233+
234+
-c [1,2,3] -l -L
235+
236+
Creates three threads that makes one, two, and three calls each, respectively.
237+
238+
-c [5,5] -d [5000,5000] -D [0,0] -l -L
239+
240+
Creates two threads that make five calls each. In this scenario, the threads will be
241+
blocked primarily on waiting for the callback to resolve, as each thread's call takes
242+
5000 milliseconds.
318243
```

test/binding.gyp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
'target_defaults': {
33
'includes': ['../common.gypi'],
44
'sources': [
5+
'threadsafe_function_ex/test/basic.cc',
6+
'threadsafe_function_ex/test/example.cc',
7+
'threadsafe_function_ex/test/threadsafe.cc',
58
'addon_data.cc',
69
'arraybuffer.cc',
710
'asynccontext.cc',
@@ -35,9 +38,7 @@
3538
'object/set_property.cc',
3639
'promise.cc',
3740
'run_script.cc',
38-
'threadsafe_function_ex/test/basic.cc',
39-
'threadsafe_function_ex/test/example.cc',
40-
'threadsafe_function_ex/test/threadsafe.cc',
41+
4142
'threadsafe_function/threadsafe_function_ctx.cc',
4243
'threadsafe_function/threadsafe_function_existing_tsfn.cc',
4344
'threadsafe_function/threadsafe_function_ptr.cc',

0 commit comments

Comments
 (0)