Skip to content

Commit 41adbff

Browse files
add tsfn inside object wrap example (#284)
1 parent 3eaf06f commit 41adbff

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "tsfn_object_wrap",
5+
"sources": [
6+
"tsfn_object_wrap.cc",
7+
],
8+
"defines": [
9+
"NAPI_DISABLE_CPP_EXCEPTIONS",
10+
"NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS"
11+
],
12+
"include_dirs": [
13+
"<!@(node -p \"require('node-addon-api').include\")"
14+
],
15+
}
16+
]
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "tsfn-test",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"dependencies": {
11+
"bindings": "*",
12+
"node-addon-api": "^7.0.0"
13+
}
14+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <inttypes.h>
2+
#include <napi.h>
3+
#include <thread>
4+
5+
// A secondary thread increments a value starting from 0 and calls a JS callback
6+
// on the JavaScript main thread.
7+
8+
// The ObjectWrap subclass whose instances own such a secondary thread.
9+
class TsfnObjectWrap : public Napi::ObjectWrap<TsfnObjectWrap> {
10+
public:
11+
static Napi::Object Init(Napi::Env env) {
12+
return DefineClass(env, "TsfnObjectWrap", {});
13+
}
14+
15+
TsfnObjectWrap(const Napi::CallbackInfo& info)
16+
: Napi::ObjectWrap<TsfnObjectWrap>(info) {
17+
// Whenever we construct a new instance of type TsfnObjectWrap, we construct
18+
// a thread-safe function that can be called from a secondary thread and
19+
// which provides a value to the JavaScript main thread.
20+
_tsfn = Napi::ThreadSafeFunction::New(
21+
info.Env(), info[0].As<Napi::Function>(), "TsfnObjectWrap", 1, 2);
22+
23+
_thread = std::thread(&TsfnObjectWrap::Thread, std::ref(_tsfn));
24+
}
25+
26+
~TsfnObjectWrap() {
27+
_tsfn.Abort();
28+
_thread.join();
29+
}
30+
31+
private:
32+
// This is the secondary thread.
33+
static void Thread(const Napi::ThreadSafeFunction& tsfn) {
34+
int64_t the_value = 0;
35+
int64_t buffer[3] = {0, 0, 0};
36+
int idx = 0;
37+
38+
// Since we're calling the JavaScript main thread in a blocking fashion,
39+
// a buffer of three values is sufficient for synchronizing with the main
40+
// thread without losing a single value and without having to allocate an
41+
// integer on the heap for each call into JavaScript.
42+
while (true) {
43+
buffer[idx] = the_value;
44+
idx = (idx + 1) % 3;
45+
int64_t* value_ref = &buffer[idx];
46+
napi_status status = tsfn.BlockingCall(
47+
value_ref, [](Napi::Env env, Napi::Function fn, int64_t* data) {
48+
int64_t native_value = *data;
49+
Napi::Value result =
50+
fn.Call({Napi::Number::New(env, native_value)});
51+
if (result.IsEmpty()) {
52+
printf("main with %" PRId64 ": result was empty!\n",
53+
native_value);
54+
} else {
55+
printf("main with %" PRId64 ": Done!\n",
56+
result.As<Napi::Number>().Int64Value());
57+
}
58+
});
59+
// We break out of the infinite loop when we're informed that the thread-
60+
// safe function is being torn down.
61+
if (status == napi_closing) {
62+
break;
63+
}
64+
the_value++;
65+
}
66+
}
67+
68+
Napi::ThreadSafeFunction _tsfn;
69+
std::thread _thread;
70+
};
71+
72+
// Boilerplate code to define an add-on that consists of the above class.
73+
Napi::Object TsfnObjectWrapExampleInit(Napi::Env env, Napi::Object exports) {
74+
return TsfnObjectWrap::Init(env);
75+
}
76+
77+
NODE_API_MODULE(TsfnObjectWrapExample, TsfnObjectWrapExampleInit)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const TsfnObjectWrap = require('bindings')('tsfn_object_wrap.node')
2+
const x = new TsfnObjectWrap((value) => {
3+
// Do something, anything, with x to keep it in scope, otherwise the instance
4+
// will be collected and the process will exit.
5+
x.someProperty = value;
6+
7+
console.log('JS1: Called with ' + value);
8+
return -value;
9+
});
10+
11+
const y = new TsfnObjectWrap((value) => {
12+
// Do something, anything, with y to keep it in scope, otherwise the instance
13+
// will be collected and the process will exit.
14+
y.someProperty = value;
15+
16+
console.log('JS2: Called with ' + value);
17+
return -value;
18+
});

0 commit comments

Comments
 (0)