Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
78196e0
tsfn: implement ThreadSafeFunctionEx<ContextType>
KevinEady Mar 22, 2020
90ebd80
fix unused parameter errors
KevinEady Mar 22, 2020
fc339c4
wip
KevinEady Mar 31, 2020
fd530b2
wip
KevinEady Apr 7, 2020
561b968
make CallJS template parameter
KevinEady Apr 7, 2020
630d88b
statically check CallJs; add no-Finalizer overload
KevinEady Apr 8, 2020
9ff352c
add tsfn test with tsfn cb function call
KevinEady Apr 17, 2020
6aeecd4
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-api
KevinEady Apr 17, 2020
2da3023
test: Initial commit of TSFNEx threadsafe test
KevinEady Jun 8, 2020
91e8859
test: Modify TSFNEx threadsafe to use TSFNEx
KevinEady Jun 8, 2020
692fbe1
src,test: Add SIGTRAP to TSFNEx::CallJS
KevinEady Jun 8, 2020
0d26703
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Jun 8, 2020
89181da
test: fix TSFNEx tests
KevinEady Jun 12, 2020
8fcdb4d
test: add barebones new tsfn test for use in docs
KevinEady Jun 12, 2020
cc8de12
implement optional function callback
KevinEady Jun 13, 2020
6706f96
clean up optional callback implementation
KevinEady Jun 13, 2020
89d2dea
test: wip with tsfnex tests
KevinEady Jun 14, 2020
d2bcc03
test: napi v4,v5 all tests pass
KevinEady Jun 14, 2020
6b7a7d0
test: consolidate duplicated code
KevinEady Jun 14, 2020
7467a3f
test: basic example, standardize identifier names
KevinEady Jun 14, 2020
a01c3c8
test: refactor the the 'empty' tsfnex test
KevinEady Jun 14, 2020
b0e7817
basic multi-threading
KevinEady Jun 14, 2020
44adeea
test: wip with example
KevinEady Jun 14, 2020
c20685b
test: wip with example test
KevinEady Jun 14, 2020
3b24e74
src: consolidate duplicated tsfnex code
KevinEady Jun 15, 2020
a7a5352
test: v4,v5+ tests pass
KevinEady Jun 15, 2020
d092a32
doc: wip with tsfn documentation
KevinEady Jun 17, 2020
75dd422
doc,test: finish TSFNEx
KevinEady Jun 21, 2020
717b602
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Jul 13, 2020
0d84cf7
Test with longer timeout
KevinEady Jul 27, 2020
c8eea6b
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Jul 27, 2020
807fb27
Apply suggestions from code review
KevinEady Aug 9, 2020
f3aa955
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Aug 9, 2020
fd6a2b4
Additional changes from review
KevinEady Aug 9, 2020
ad9333e
test: tsfnex uses ported tsfn tests
KevinEady Aug 18, 2020
e223955
doc: tsfnex example uses ported tsfn example
KevinEady Aug 18, 2020
3405b2b
src,doc: final cleanup
KevinEady Aug 18, 2020
151a914
Apply documentation suggestions from code review
KevinEady Oct 9, 2020
59f27da
Fix common.gypi
KevinEady Oct 10, 2020
b54f5eb
Additional changes from review
KevinEady Oct 10, 2020
7ec9741
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Oct 29, 2020
7a13f86
doc: fix additional typo
KevinEady Oct 29, 2020
4abe7cf
test: rename tsfnex test files
KevinEady Nov 6, 2020
c24c455
Rename to TypedThreadSafeFunction
KevinEady Nov 24, 2020
559ad8c
Merge remote-tracking branch 'upstream/master' into contexted-tsfn-ap…
KevinEady Nov 24, 2020
5e5b9ce
Apply formatting changes
KevinEady Nov 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wip
  • Loading branch information
KevinEady committed Apr 7, 2020
commit fd530b217e9f7dec68c391e53af51658537a40f0
107 changes: 58 additions & 49 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4261,98 +4261,94 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) {
////////////////////////////////////////////////////////////////////////////////

// static
template <typename ContextType>
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
template <typename ResourceString, typename Finalizer, typename FinalizerDataType>
inline ThreadSafeFunctionEx<ContextType> ThreadSafeFunctionEx<ContextType>::New(napi_env env,
inline ThreadSafeFunctionEx<ContextType, DataType, CallJs> ThreadSafeFunctionEx<ContextType, DataType, CallJs>::New(napi_env env,
const Function& callback,
const Object& resource,
ResourceString resourceName,
size_t maxQueueSize,
size_t initialThreadCount,
ContextType* context,
Finalizer finalizeCallback,
FinalizerDataType* data,
ThreadSafeFunctionCallJS call_js_cb) {
FinalizerDataType* data) {
return New(env, callback, resource, resourceName, maxQueueSize,
initialThreadCount, context, finalizeCallback, data,
details::ThreadSafeFinalize<ContextType, Finalizer,
FinalizerDataType>::FinalizeFinalizeWrapperWithDataAndContext,
call_js_cb);
FinalizerDataType>::FinalizeFinalizeWrapperWithDataAndContext);
}

template <typename ContextType>
inline ThreadSafeFunctionEx<ContextType>::ThreadSafeFunctionEx()
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline ThreadSafeFunctionEx<ContextType, DataType, CallJs>::ThreadSafeFunctionEx()
: _tsfn() {
}

template <typename ContextType>
inline ThreadSafeFunctionEx<ContextType>::ThreadSafeFunctionEx(
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline ThreadSafeFunctionEx<ContextType, DataType, CallJs>::ThreadSafeFunctionEx(
napi_threadsafe_function tsfn)
: _tsfn(tsfn) {
}

template <typename ContextType>
inline ThreadSafeFunctionEx<ContextType>::operator napi_threadsafe_function() const {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline ThreadSafeFunctionEx<ContextType, DataType, CallJs>::operator napi_threadsafe_function() const {
return _tsfn;
}

template <typename ContextType>
template <typename DataType>
inline napi_status ThreadSafeFunctionEx<ContextType>::BlockingCall(
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline napi_status ThreadSafeFunctionEx<ContextType, DataType, CallJs>::BlockingCall(
DataType* data) const {
return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking);
}

template <typename ContextType>
template <typename DataType>
inline napi_status ThreadSafeFunctionEx<ContextType>::NonBlockingCall(
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline napi_status ThreadSafeFunctionEx<ContextType, DataType, CallJs>::NonBlockingCall(
DataType* data) const {
return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking);
}

template <typename ContextType>
inline void ThreadSafeFunctionEx<ContextType>::Ref(napi_env env) const {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline void ThreadSafeFunctionEx<ContextType, DataType, CallJs>::Ref(napi_env env) const {
if (_tsfn != nullptr) {
napi_status status = napi_ref_threadsafe_function(env, _tsfn);
NAPI_THROW_IF_FAILED_VOID(env, status);
}
}

template <typename ContextType>
inline void ThreadSafeFunctionEx<ContextType>::Unref(napi_env env) const {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline void ThreadSafeFunctionEx<ContextType, DataType, CallJs>::Unref(napi_env env) const {
if (_tsfn != nullptr) {
napi_status status = napi_unref_threadsafe_function(env, _tsfn);
NAPI_THROW_IF_FAILED_VOID(env, status);
}
}

template <typename ContextType>
inline napi_status ThreadSafeFunctionEx<ContextType>::Acquire() const {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline napi_status ThreadSafeFunctionEx<ContextType, DataType, CallJs>::Acquire() const {
return napi_acquire_threadsafe_function(_tsfn);
}

template <typename ContextType>
inline napi_status ThreadSafeFunctionEx<ContextType>::Release() {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline napi_status ThreadSafeFunctionEx<ContextType, DataType, CallJs>::Release() {
return napi_release_threadsafe_function(_tsfn, napi_tsfn_release);
}

template <typename ContextType>
inline napi_status ThreadSafeFunctionEx<ContextType>::Abort() {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline napi_status ThreadSafeFunctionEx<ContextType, DataType, CallJs>::Abort() {
return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort);
}

template <typename ContextType>
inline ContextType* ThreadSafeFunctionEx<ContextType>::GetContext() const {
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
inline ContextType* ThreadSafeFunctionEx<ContextType, DataType, CallJs>::GetContext() const {
void* context;
napi_status status = napi_get_threadsafe_function_context(_tsfn, &context);
NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunctionEx::GetContext", "napi_get_threadsafe_function_context");
return static_cast<ContextType*>(context);
}

// static
template <typename ContextType>
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
template <typename ResourceString, typename Finalizer, typename FinalizerDataType>
inline ThreadSafeFunctionEx<ContextType> ThreadSafeFunctionEx<ContextType>::New(napi_env env,
inline ThreadSafeFunctionEx<ContextType, DataType, CallJs> ThreadSafeFunctionEx<ContextType, DataType, CallJs>::New(napi_env env,
const Function& callback,
const Object& resource,
ResourceString resourceName,
Expand All @@ -4361,13 +4357,14 @@ inline ThreadSafeFunctionEx<ContextType> ThreadSafeFunctionEx<ContextType>::New(
ContextType* context,
Finalizer finalizeCallback,
FinalizerDataType* data,
napi_finalize wrapper,
ThreadSafeFunctionCallJS call_js_cb) {
napi_finalize wrapper) {
static_assert(details::can_make_string<ResourceString>::value
|| std::is_convertible<ResourceString, napi_value>::value,
"Resource name should be convertible to the string type");

ThreadSafeFunctionEx<ContextType> tsfn;
ThreadSafeFunctionEx<ContextType, DataType, CallJs> tsfn;
// details::ThreadSafeCallJs<ContextType, DataType, ThreadSafeFunctionCallJS> cb{call_js_cb};

auto* finalizeData = new details::ThreadSafeFinalize<ContextType, Finalizer,
FinalizerDataType>({ data, finalizeCallback });
napi_status status = napi_create_threadsafe_function(env, callback, resource,
Expand All @@ -4379,33 +4376,45 @@ inline ThreadSafeFunctionEx<ContextType> ThreadSafeFunctionEx<ContextType>::New(
// }
// call_js_cb( Napi::Env(env), Function(env, jsCallback), static_cast<ContextType*>(context), data);
// },
CallJS,
CallJsInternal,
&tsfn._tsfn);
if (status != napi_ok) {
delete finalizeData;
NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx<ContextType>());
NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx<ContextType, DataType, CallJs>());
}

return tsfn;
}

template <typename ContextType>
inline void
ThreadSafeFunctionEx<ContextType>::CallJS(napi_env env, napi_value jsCallback,
template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
void ThreadSafeFunctionEx<ContextType, DataType, CallJs>::CallJsInternal(napi_env env, napi_value jsCallback,
void *context, void *data) {
if (env == nullptr && jsCallback == nullptr) {
return;
}

if (data != nullptr) {
auto* callbackWrapper = static_cast<CallbackWrapper*>(data);
(*callbackWrapper)(env, Function(env, jsCallback));
delete callbackWrapper;
} else if (jsCallback != nullptr) {
Function(env, jsCallback).Call({});
if (CallJs == nullptr && jsCallback != nullptr) {
Function(env, jsCallback).Call(0, nullptr);
} else {
CallJs(env, Function(env, jsCallback), static_cast<ContextType*>(context), static_cast<DataType*>(data));
}
}


// template <typename ContextType, typename DataType, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*)>
// inline void
// ThreadSafeFunctionEx<ContextType, DataType, CallJs>::CallJS(napi_env env, napi_value jsCallback,
// void *context, void *data) {
// if (env == nullptr && jsCallback == nullptr) {
// return;
// }

// if (data != nullptr) {
// auto* callbackWrapper = static_cast<CallbackWrapper*>(data);
// (*callbackWrapper)(env, Function(env, jsCallback));
// delete callbackWrapper;
// } else if (jsCallback != nullptr) {
// Function(env, jsCallback).Call({});
// }
// }

////////////////////////////////////////////////////////////////////////////////
// ThreadSafeFunction class
////////////////////////////////////////////////////////////////////////////////
Expand Down
32 changes: 11 additions & 21 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2036,29 +2036,24 @@ namespace Napi {
};

#if (NAPI_VERSION > 3)


template <typename ContextType, typename DataType>
template <typename ContextType = void, typename DataType = void, void (*CallJs)(Napi::Env, Napi::Function, ContextType*, DataType*) = nullptr >
class ThreadSafeFunctionEx {
public:

using ThreadSafeFunctionCallJS = std::function<void(Napi::Env, Napi::Function, ContextType *context, DataType *data)>;

// This API may only be called from the main thread.
template <typename ResourceString, typename Finalizer, typename FinalizerDataType>
static ThreadSafeFunctionEx<ContextType> New(napi_env env,
static ThreadSafeFunctionEx<ContextType, DataType, CallJs> New(napi_env env,
const Function& callback,
const Object& resource,
ResourceString resourceName,
size_t maxQueueSize,
size_t initialThreadCount,
ContextType* context,
Finalizer finalizeCallback,
FinalizerDataType* data,
ThreadSafeFunctionCallJS call_js_cb);
FinalizerDataType* data);

ThreadSafeFunctionEx<ContextType>();
ThreadSafeFunctionEx<ContextType>(napi_threadsafe_function tsFunctionValue);
ThreadSafeFunctionEx<ContextType, DataType, CallJs>();
ThreadSafeFunctionEx<ContextType, DataType, CallJs>(napi_threadsafe_function tsFunctionValue);

operator napi_threadsafe_function() const;

Expand Down Expand Up @@ -2093,11 +2088,10 @@ namespace Napi {
ContextType* GetContext() const;

private:
// using CallbackWrapper = std::function<void(Napi::Env, Napi::Function, ContextType* context)>;

template <typename ResourceString,
typename Finalizer, typename FinalizerDataType>
static ThreadSafeFunctionEx<ContextType> New(napi_env env,
static ThreadSafeFunctionEx<ContextType, DataType, CallJs> New(napi_env env,
const Function& callback,
const Object& resource,
ResourceString resourceName,
Expand All @@ -2106,17 +2100,13 @@ namespace Napi {
ContextType* context,
Finalizer finalizeCallback,
FinalizerDataType* data,
napi_finalize wrapper,
ThreadSafeFunctionCallJS call_js_cb);

static void CallJS(napi_env env,
napi_value jsCallback,
void* context,
void* data);
napi_finalize wrapper);

static void CallJsInternal(napi_env env,
napi_value jsCallback,
void* context,
void* data);
protected:
void CallJS

napi_threadsafe_function _tsfn;
};

Expand Down
37 changes: 22 additions & 15 deletions test/threadsafe_function/threadsafe_function_ex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@

using namespace Napi;

using TSFNContext = Reference<Napi::Value>;

namespace {

// Context of our TSFN.
using TSFNContext = Reference<Napi::Value>;

// Data passed to ThreadSafeFunctionEx::[Non]BlockingCall
struct CallJsData {
CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)) { };
CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)){};

void resolve(TSFNContext* context) {
deferred.Resolve(context->Value());
};
Promise::Deferred deferred;
};

// CallJs callback function
static void CallJs(Napi::Env /*env*/, Napi::Function /*jsCallback*/,
TSFNContext *context, CallJsData *data) {
data->deferred.Resolve(context->Value());
}

// Full type of our ThreadSafeFunctionEx
using TSFN = ThreadSafeFunctionEx<TSFNContext, CallJsData, CallJs>;

// A JS-accessible wrap that holds a TSFN.
class TSFNWrap : public ObjectWrap<TSFNWrap> {
public:
static Object Init(Napi::Env env, Object exports);
Expand All @@ -25,7 +34,7 @@ class TSFNWrap : public ObjectWrap<TSFNWrap> {
Napi::Value GetContextByCall(const CallbackInfo &info) {
Napi::Env env = info.Env();
std::unique_ptr<CallJsData> callData = std::make_unique<CallJsData>(env);
auto& deferred = callData->deferred;
auto &deferred = callData->deferred;
_tsfn.BlockingCall(callData.release());
return deferred.Promise();
};
Expand All @@ -40,7 +49,7 @@ class TSFNWrap : public ObjectWrap<TSFNWrap> {
};

private:
ThreadSafeFunctionEx<TSFNContext> _tsfn;
TSFN _tsfn;
Promise::Deferred _deferred;
};

Expand All @@ -63,7 +72,7 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info)
TSFNContext *ctx = new Reference<Napi::Value>;
*ctx = Persistent(info[0]);

_tsfn = ThreadSafeFunctionEx<TSFNContext>::New(
_tsfn = ThreadSafeFunctionEx<TSFNContext, CallJsData, CallJs>::New(
info.Env(), // napi_env env,
Function::New(
env,
Expand All @@ -74,15 +83,13 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info)
1, // size_t initialThreadCount,
ctx, // ContextType* context,

[this](Napi::Env env, void*, TSFNContext *ctx) { // Finalizer finalizeCallback,
[this](Napi::Env env, void *,
TSFNContext *ctx) { // Finalizer finalizeCallback,
_deferred.Resolve(env.Undefined());
delete ctx;
},
static_cast<void*>(nullptr), // FinalizerDataType* data,
[](Napi::Env, Napi::Value, TSFNContext *context, void *data) { // call_js_cb
std::unique_ptr<CallJsData> callData(static_cast<CallJsData*>(data));
callData->resolve(context);
});
static_cast<void *>(nullptr) // FinalizerDataType* data,
);
}
} // namespace

Expand Down
7 changes: 6 additions & 1 deletion test/threadsafe_function/threadsafe_function_ex.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ const buildType = process.config.target_defaults.default_configuration;

module.exports = Promise.all([
test(require(`../build/${buildType}/binding.node`)),
test(require(`../build/${buildType}/binding_noexcept.node`))
// test(require(`../build/${buildType}/binding_noexcept.node`))
]);

async function test(binding) {
const ctx = { };
const tsfn = new binding.threadsafe_function_ex.TSFNWrap(ctx);
console.log("1");
assert(ctx === await tsfn.getContextByCall(),"getContextByCall context not equal");
console.log("2");
assert(ctx === tsfn.getContextFromTsfn(),"getContextFromTsfn context not equal");
console.log("3");
console.log("releasing");
await tsfn.release();
console.log("released!");
}