Skip to content

Commit df75e08

Browse files
KevinEadymhdawson
authored andcommitted
tsfn: support direct calls to underlying napi_tsfn
support direct calls to underlying napi_tsfn Fixes: #556 PR-URL: #58 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]>
1 parent 295e560 commit df75e08

File tree

7 files changed

+166
-1
lines changed

7 files changed

+166
-1
lines changed

doc/threadsafe_function.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ Napi::ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn);
5858
- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing
5959
thread-safe function.
6060
61-
Returns a non-empty `Napi::ThreadSafeFunction` instance.
61+
Returns a non-empty `Napi::ThreadSafeFunction` instance. When using this
62+
constructor, only use the `Blocking(void*)` / `NonBlocking(void*)` overloads;
63+
the `Callback` and templated `data*` overloads should _not_ be used. See below
64+
for additional details.
6265
6366
### New
6467
@@ -171,6 +174,9 @@ There are several overloaded implementations of `BlockingCall()` and
171174
`NonBlockingCall()` for use with optional parameters: skip the optional
172175
parameter for that specific overload.
173176

177+
**These specific function overloads should only be used on a `ThreadSafeFunction`
178+
created via `ThreadSafeFunction::New`.**
179+
174180
```cpp
175181
napi_status Napi::ThreadSafeFunction::BlockingCall(DataType* data, Callback callback) const
176182

@@ -186,6 +192,17 @@ napi_status Napi::ThreadSafeFunction::NonBlockingCall(DataType* data, Callback c
186192
necessary to call into JavaScript via `MakeCallback()` because N-API runs
187193
`callback` in a context appropriate for callbacks.
188194
195+
**These specific function overloads should only be used on a `ThreadSafeFunction`
196+
created via `ThreadSafeFunction(napi_threadsafe_function)`.**
197+
198+
```cpp
199+
napi_status Napi::ThreadSafeFunction::BlockingCall(void* data) const
200+
201+
napi_status Napi::ThreadSafeFunction::NonBlockingCall(void* data) const
202+
```
203+
- `data`: Data to pass to `call_js_cb` specified when creating the thread-safe
204+
function via `napi_create_threadsafe_function`.
205+
189206
Returns one of:
190207
- `napi_ok`: The call was successfully added to the queue.
191208
- `napi_queue_full`: The queue was full when trying to call in a non-blocking

napi-inl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4043,6 +4043,12 @@ inline napi_status ThreadSafeFunction::BlockingCall() const {
40434043
return CallInternal(nullptr, napi_tsfn_blocking);
40444044
}
40454045

4046+
template <>
4047+
inline napi_status ThreadSafeFunction::BlockingCall(
4048+
void* data) const {
4049+
return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking);
4050+
}
4051+
40464052
template <typename Callback>
40474053
inline napi_status ThreadSafeFunction::BlockingCall(
40484054
Callback callback) const {
@@ -4062,6 +4068,12 @@ inline napi_status ThreadSafeFunction::NonBlockingCall() const {
40624068
return CallInternal(nullptr, napi_tsfn_nonblocking);
40634069
}
40644070

4071+
template <>
4072+
inline napi_status ThreadSafeFunction::NonBlockingCall(
4073+
void* data) const {
4074+
return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking);
4075+
}
4076+
40654077
template <typename Callback>
40664078
inline napi_status ThreadSafeFunction::NonBlockingCall(
40674079
Callback callback) const {

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Object InitObjectDeprecated(Env env);
4040
#endif // !NODE_ADDON_API_DISABLE_DEPRECATED
4141
Object InitPromise(Env env);
4242
#if (NAPI_VERSION > 3)
43+
Object InitThreadSafeFunctionExistingTsfn(Env env);
4344
Object InitThreadSafeFunctionPtr(Env env);
4445
Object InitThreadSafeFunctionSum(Env env);
4546
Object InitThreadSafeFunctionUnref(Env env);
@@ -91,6 +92,7 @@ Object Init(Env env, Object exports) {
9192
#endif // !NODE_ADDON_API_DISABLE_DEPRECATED
9293
exports.Set("promise", InitPromise(env));
9394
#if (NAPI_VERSION > 3)
95+
exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env));
9496
exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env));
9597
exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env));
9698
exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
'object/object.cc',
3737
'object/set_property.cc',
3838
'promise.cc',
39+
'threadsafe_function/threadsafe_function_existing_tsfn.cc',
3940
'threadsafe_function/threadsafe_function_ptr.cc',
4041
'threadsafe_function/threadsafe_function_sum.cc',
4142
'threadsafe_function/threadsafe_function_unref.cc',

test/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ let testModules = [
3939
'object/object_deprecated',
4040
'object/set_property',
4141
'promise',
42+
'threadsafe_function/threadsafe_function_existing_tsfn',
4243
'threadsafe_function/threadsafe_function_ptr',
4344
'threadsafe_function/threadsafe_function_sum',
4445
'threadsafe_function/threadsafe_function_unref',
@@ -68,6 +69,7 @@ if (napiVersion < 3) {
6869

6970
if (napiVersion < 4) {
7071
testModules.splice(testModules.indexOf('asyncprogressworker'), 1);
72+
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1);
7173
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1);
7274
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1);
7375
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include "napi.h"
2+
#include <cstdlib>
3+
4+
#if (NAPI_VERSION > 3)
5+
6+
using namespace Napi;
7+
8+
namespace {
9+
10+
struct TestContext {
11+
TestContext(Promise::Deferred &&deferred)
12+
: deferred(std::move(deferred)), callData(nullptr){};
13+
14+
napi_threadsafe_function tsfn;
15+
Promise::Deferred deferred;
16+
double *callData;
17+
18+
~TestContext() {
19+
if (callData != nullptr)
20+
delete callData;
21+
};
22+
};
23+
24+
void FinalizeCB(napi_env env, void * /*finalizeData */, void *context) {
25+
TestContext *testContext = static_cast<TestContext *>(context);
26+
if (testContext->callData != nullptr) {
27+
testContext->deferred.Resolve(Number::New(env, *testContext->callData));
28+
} else {
29+
testContext->deferred.Resolve(Napi::Env(env).Undefined());
30+
}
31+
delete testContext;
32+
}
33+
34+
void CallJSWithData(napi_env env, napi_value /* callback */, void *context,
35+
void *data) {
36+
TestContext *testContext = static_cast<TestContext *>(context);
37+
testContext->callData = static_cast<double *>(data);
38+
39+
napi_status status =
40+
napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release);
41+
42+
NAPI_THROW_IF_FAILED_VOID(env, status);
43+
}
44+
45+
void CallJSNoData(napi_env env, napi_value /* callback */, void *context,
46+
void * /*data*/) {
47+
TestContext *testContext = static_cast<TestContext *>(context);
48+
testContext->callData = nullptr;
49+
50+
napi_status status =
51+
napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release);
52+
53+
NAPI_THROW_IF_FAILED_VOID(env, status);
54+
}
55+
56+
static Value TestCall(const CallbackInfo &info) {
57+
Napi::Env env = info.Env();
58+
bool isBlocking = false;
59+
bool hasData = false;
60+
if (info.Length() > 0) {
61+
Object opts = info[0].As<Object>();
62+
if (opts.Has("blocking")) {
63+
isBlocking = opts.Get("blocking").ToBoolean();
64+
}
65+
if (opts.Has("data")) {
66+
hasData = opts.Get("data").ToBoolean();
67+
}
68+
}
69+
70+
// Allow optional callback passed from JS. Useful for testing.
71+
Function cb = Function::New(env, [](const CallbackInfo & /*info*/) {});
72+
73+
TestContext *testContext = new TestContext(Napi::Promise::Deferred(env));
74+
75+
napi_status status = napi_create_threadsafe_function(
76+
env, cb, Object::New(env), String::New(env, "Test"), 0, 1,
77+
nullptr, /*finalize data*/
78+
FinalizeCB, testContext, hasData ? CallJSWithData : CallJSNoData,
79+
&testContext->tsfn);
80+
81+
NAPI_THROW_IF_FAILED(env, status, Value());
82+
83+
ThreadSafeFunction wrapped = ThreadSafeFunction(testContext->tsfn);
84+
85+
// Test the four napi_threadsafe_function direct-accessing calls
86+
if (isBlocking) {
87+
if (hasData) {
88+
wrapped.BlockingCall(static_cast<void *>(new double(std::rand())));
89+
} else {
90+
wrapped.BlockingCall(static_cast<void *>(nullptr));
91+
}
92+
} else {
93+
if (hasData) {
94+
wrapped.NonBlockingCall(static_cast<void *>(new double(std::rand())));
95+
} else {
96+
wrapped.NonBlockingCall(static_cast<void *>(nullptr));
97+
}
98+
}
99+
100+
return testContext->deferred.Promise();
101+
}
102+
103+
} // namespace
104+
105+
Object InitThreadSafeFunctionExistingTsfn(Env env) {
106+
Object exports = Object::New(env);
107+
exports["testCall"] = Function::New(env, TestCall);
108+
109+
return exports;
110+
}
111+
112+
#endif
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
const buildType = process.config.target_defaults.default_configuration;
6+
7+
module.exports = Promise.all([
8+
test(require(`../build/${buildType}/binding.node`)),
9+
test(require(`../build/${buildType}/binding_noexcept.node`))
10+
]);
11+
12+
async function test(binding) {
13+
const testCall = binding.threadsafe_function_existing_tsfn.testCall;
14+
15+
assert(typeof await testCall({ blocking: true, data: true }) === "number");
16+
assert(typeof await testCall({ blocking: true, data: false }) === "undefined");
17+
assert(typeof await testCall({ blocking: false, data: true }) === "number");
18+
assert(typeof await testCall({ blocking: false, data: false }) === "undefined");
19+
}

0 commit comments

Comments
 (0)