Skip to content

Commit

Permalink
tsfn: add wrappers for Ref and Unref
Browse files Browse the repository at this point in the history
Ref: #556 (comment)
PR-URL: #561
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
KevinEady authored and mhdawson committed Oct 23, 2019
1 parent 2e1769e commit ea9ce1c
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 0 deletions.
14 changes: 14 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4029,6 +4029,20 @@ inline napi_status ThreadSafeFunction::NonBlockingCall(
return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);
}

inline void ThreadSafeFunction::Ref(napi_env env) const {
if (_tsfn != nullptr) {
napi_status status = napi_ref_threadsafe_function(env, *_tsfn);
NAPI_THROW_IF_FAILED_VOID(env, status);
}
}

inline void ThreadSafeFunction::Unref(napi_env env) const {
if (_tsfn != nullptr) {
napi_status status = napi_unref_threadsafe_function(env, *_tsfn);
NAPI_THROW_IF_FAILED_VOID(env, status);
}
}

inline napi_status ThreadSafeFunction::Acquire() const {
return napi_acquire_threadsafe_function(*_tsfn);
}
Expand Down
6 changes: 6 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,12 @@ namespace Napi {
template <typename DataType, typename Callback>
napi_status NonBlockingCall(DataType* data, Callback callback) const;

// This API may only be called from the main thread.
void Ref(napi_env env) const;

// This API may only be called from the main thread.
void Unref(napi_env env) const;

// This API may be called from any thread.
napi_status Acquire() const;

Expand Down
2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Object InitObjectDeprecated(Env env);
Object InitPromise(Env env);
#if (NAPI_VERSION > 3)
Object InitThreadSafeFunctionPtr(Env env);
Object InitThreadSafeFunctionUnref(Env env);
Object InitThreadSafeFunction(Env env);
#endif
Object InitTypedArray(Env env);
Expand Down Expand Up @@ -83,6 +84,7 @@ Object Init(Env env, Object exports) {
exports.Set("promise", InitPromise(env));
#if (NAPI_VERSION > 3)
exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env));
exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env));
exports.Set("threadsafe_function", InitThreadSafeFunction(env));
#endif
exports.Set("typedarray", InitTypedArray(env));
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
'object/set_property.cc',
'promise.cc',
'threadsafe_function/threadsafe_function_ptr.cc',
'threadsafe_function/threadsafe_function_unref.cc',
'threadsafe_function/threadsafe_function.cc',
'typedarray.cc',
'objectwrap.cc',
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let testModules = [
'object/set_property',
'promise',
'threadsafe_function/threadsafe_function_ptr',
'threadsafe_function/threadsafe_function_unref',
'threadsafe_function/threadsafe_function',
'typedarray',
'typedarray-bigint',
Expand Down
7 changes: 7 additions & 0 deletions test/napi_child.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ exports.spawnSync = function(command, args, options) {
}
return require('child_process').spawnSync(command, args, options);
};

exports.spawn = function(command, args, options) {
if (require('../index').needsFlag) {
args.splice(0, 0, '--napi-modules');
}
return require('child_process').spawn(command, args, options);
};
41 changes: 41 additions & 0 deletions test/threadsafe_function/threadsafe_function_unref.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "napi.h"

#if (NAPI_VERSION > 3)

using namespace Napi;

namespace {

static Value TestUnref(const CallbackInfo& info) {
Napi::Env env = info.Env();
Object global = env.Global();
Object resource = info[0].As<Object>();
Function cb = info[1].As<Function>();
Function setTimeout = global.Get("setTimeout").As<Function>();
ThreadSafeFunction* tsfn = new ThreadSafeFunction;

*tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) {
delete tsfn;
});

tsfn->BlockingCall();

setTimeout.Call( global, {
Function::New(env, [tsfn](const CallbackInfo& info) {
tsfn->Unref(info.Env());
}),
Number::New(env, 100)
});

return info.Env().Undefined();
}

}

Object InitThreadSafeFunctionUnref(Env env) {
Object exports = Object::New(env);
exports["testUnref"] = Function::New(env, TestUnref);
return exports;
}

#endif
53 changes: 53 additions & 0 deletions test/threadsafe_function/threadsafe_function_unref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const assert = require('assert');
const buildType = process.config.target_defaults.default_configuration;

const isMainProcess = process.argv[1] != __filename;

/**
* In order to test that the event loop exits even with an active TSFN, we need
* to spawn a new process for the test.
* - Main process: spawns new node instance, executing this script
* - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release.
*
* Main process should expect child process to exit.
*/

if (isMainProcess) {
test(`../build/${buildType}/binding.node`);
test(`../build/${buildType}/binding_noexcept.node`);
} else {
test(process.argv[2]);
}

function test(bindingFile) {
if (isMainProcess) {
// Main process
const child = require('../napi_child').spawn(process.argv[0], [ '--expose-gc', __filename, bindingFile ], {
stdio: 'inherit',
});

let timeout = setTimeout( function() {
child.kill();
timeout = 0;
throw new Error("Expected child to die");
}, 5000);

child.on("error", (err) => {
clearTimeout(timeout);
timeout = 0;
throw new Error(err);
})

child.on("close", (code) => {
if (timeout) clearTimeout(timeout);
assert(!code, "Expected return value 0");
});

} else {
// Child process
const binding = require(bindingFile);
binding.threadsafe_function_unref.testUnref({}, () => { });
}
}

0 comments on commit ea9ce1c

Please sign in to comment.