Skip to content

Commit ea9ce1c

Browse files
KevinEadymhdawson
authored andcommitted
tsfn: add wrappers for Ref and Unref
Ref: #556 (comment) PR-URL: #561 Reviewed-By: Michael Dawson <[email protected]>
1 parent 2e1769e commit ea9ce1c

File tree

8 files changed

+125
-0
lines changed

8 files changed

+125
-0
lines changed

napi-inl.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4029,6 +4029,20 @@ inline napi_status ThreadSafeFunction::NonBlockingCall(
40294029
return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);
40304030
}
40314031

4032+
inline void ThreadSafeFunction::Ref(napi_env env) const {
4033+
if (_tsfn != nullptr) {
4034+
napi_status status = napi_ref_threadsafe_function(env, *_tsfn);
4035+
NAPI_THROW_IF_FAILED_VOID(env, status);
4036+
}
4037+
}
4038+
4039+
inline void ThreadSafeFunction::Unref(napi_env env) const {
4040+
if (_tsfn != nullptr) {
4041+
napi_status status = napi_unref_threadsafe_function(env, *_tsfn);
4042+
NAPI_THROW_IF_FAILED_VOID(env, status);
4043+
}
4044+
}
4045+
40324046
inline napi_status ThreadSafeFunction::Acquire() const {
40334047
return napi_acquire_threadsafe_function(*_tsfn);
40344048
}

napi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,12 @@ namespace Napi {
20112011
template <typename DataType, typename Callback>
20122012
napi_status NonBlockingCall(DataType* data, Callback callback) const;
20132013

2014+
// This API may only be called from the main thread.
2015+
void Ref(napi_env env) const;
2016+
2017+
// This API may only be called from the main thread.
2018+
void Unref(napi_env env) const;
2019+
20142020
// This API may be called from any thread.
20152021
napi_status Acquire() const;
20162022

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Object InitObjectDeprecated(Env env);
3838
Object InitPromise(Env env);
3939
#if (NAPI_VERSION > 3)
4040
Object InitThreadSafeFunctionPtr(Env env);
41+
Object InitThreadSafeFunctionUnref(Env env);
4142
Object InitThreadSafeFunction(Env env);
4243
#endif
4344
Object InitTypedArray(Env env);
@@ -83,6 +84,7 @@ Object Init(Env env, Object exports) {
8384
exports.Set("promise", InitPromise(env));
8485
#if (NAPI_VERSION > 3)
8586
exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env));
87+
exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env));
8688
exports.Set("threadsafe_function", InitThreadSafeFunction(env));
8789
#endif
8890
exports.Set("typedarray", InitTypedArray(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'object/set_property.cc',
3535
'promise.cc',
3636
'threadsafe_function/threadsafe_function_ptr.cc',
37+
'threadsafe_function/threadsafe_function_unref.cc',
3738
'threadsafe_function/threadsafe_function.cc',
3839
'typedarray.cc',
3940
'objectwrap.cc',

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let testModules = [
3838
'object/set_property',
3939
'promise',
4040
'threadsafe_function/threadsafe_function_ptr',
41+
'threadsafe_function/threadsafe_function_unref',
4142
'threadsafe_function/threadsafe_function',
4243
'typedarray',
4344
'typedarray-bigint',

test/napi_child.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@ exports.spawnSync = function(command, args, options) {
55
}
66
return require('child_process').spawnSync(command, args, options);
77
};
8+
9+
exports.spawn = function(command, args, options) {
10+
if (require('../index').needsFlag) {
11+
args.splice(0, 0, '--napi-modules');
12+
}
13+
return require('child_process').spawn(command, args, options);
14+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "napi.h"
2+
3+
#if (NAPI_VERSION > 3)
4+
5+
using namespace Napi;
6+
7+
namespace {
8+
9+
static Value TestUnref(const CallbackInfo& info) {
10+
Napi::Env env = info.Env();
11+
Object global = env.Global();
12+
Object resource = info[0].As<Object>();
13+
Function cb = info[1].As<Function>();
14+
Function setTimeout = global.Get("setTimeout").As<Function>();
15+
ThreadSafeFunction* tsfn = new ThreadSafeFunction;
16+
17+
*tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) {
18+
delete tsfn;
19+
});
20+
21+
tsfn->BlockingCall();
22+
23+
setTimeout.Call( global, {
24+
Function::New(env, [tsfn](const CallbackInfo& info) {
25+
tsfn->Unref(info.Env());
26+
}),
27+
Number::New(env, 100)
28+
});
29+
30+
return info.Env().Undefined();
31+
}
32+
33+
}
34+
35+
Object InitThreadSafeFunctionUnref(Env env) {
36+
Object exports = Object::New(env);
37+
exports["testUnref"] = Function::New(env, TestUnref);
38+
return exports;
39+
}
40+
41+
#endif
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const buildType = process.config.target_defaults.default_configuration;
5+
6+
const isMainProcess = process.argv[1] != __filename;
7+
8+
/**
9+
* In order to test that the event loop exits even with an active TSFN, we need
10+
* to spawn a new process for the test.
11+
* - Main process: spawns new node instance, executing this script
12+
* - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release.
13+
*
14+
* Main process should expect child process to exit.
15+
*/
16+
17+
if (isMainProcess) {
18+
test(`../build/${buildType}/binding.node`);
19+
test(`../build/${buildType}/binding_noexcept.node`);
20+
} else {
21+
test(process.argv[2]);
22+
}
23+
24+
function test(bindingFile) {
25+
if (isMainProcess) {
26+
// Main process
27+
const child = require('../napi_child').spawn(process.argv[0], [ '--expose-gc', __filename, bindingFile ], {
28+
stdio: 'inherit',
29+
});
30+
31+
let timeout = setTimeout( function() {
32+
child.kill();
33+
timeout = 0;
34+
throw new Error("Expected child to die");
35+
}, 5000);
36+
37+
child.on("error", (err) => {
38+
clearTimeout(timeout);
39+
timeout = 0;
40+
throw new Error(err);
41+
})
42+
43+
child.on("close", (code) => {
44+
if (timeout) clearTimeout(timeout);
45+
assert(!code, "Expected return value 0");
46+
});
47+
48+
} else {
49+
// Child process
50+
const binding = require(bindingFile);
51+
binding.threadsafe_function_unref.testUnref({}, () => { });
52+
}
53+
}

0 commit comments

Comments
 (0)