1
- #include < array>
2
- #include " napi.h"
3
1
#include " ../util/util.h"
2
+ #include " napi.h"
3
+ #include < array>
4
+ #include < future>
5
+ #include < iostream>
6
+ #include < thread>
7
+ #include < vector>
8
+
9
+ static constexpr auto DEFAULT_THREAD_COUNT = 10U ;
10
+ static constexpr auto DEFAULT_CALL_COUNT = 2 ;
11
+
12
+
13
+ static struct {
14
+ bool logCall = true ; // Uses JS console.log to output when the TSFN is
15
+ // processing the NonBlockingCall().
16
+ bool logThread = false ; // Uses native std::cout to output when the thread's
17
+ // NonBlockingCall() request has finished.
18
+ } DefaultOptions; // Options from Start()
19
+
20
+ /* *
21
+ * @brief Macro used specifically to support the dual CI test / documentation
22
+ * example setup. Exceptions are always thrown as JavaScript exceptions when
23
+ * running in example mode.
24
+ *
25
+ */
26
+ #define TSFN_THROW (tsfnWrap, e, ...) \
27
+ if (tsfnWrap->cppExceptions) { \
28
+ do { \
29
+ (e).ThrowAsJavaScriptException (); \
30
+ return __VA_ARGS__; \
31
+ } while (0 ); \
32
+ } else { \
33
+ NAPI_THROW (e, __VA_ARGS__); \
34
+ }
4
35
5
36
#if (NAPI_VERSION > 3)
6
37
7
38
using namespace Napi ;
8
39
9
40
namespace example {
10
41
42
+ class TSFNWrap ;
43
+
11
44
// Context of the TSFN.
12
- using Context = Reference<Napi::Value>;
45
+ using Context = TSFNWrap;
46
+
47
+ using CompletionHandler = std::function<void ()>;
13
48
14
49
// Data passed (as pointer) to [Non]BlockingCall
15
- using DataType = Promise::Deferred;
50
+ struct DataType {
51
+ // Promise::Deferred;
52
+ // CompletionHandler handler;
53
+ std::future<int > deferred;
54
+ };
16
55
17
56
// CallJs callback function
18
57
static void CallJs (Napi::Env env, Napi::Function /* jsCallback*/ ,
19
58
Context *context, DataType *data) {
20
59
if (env != nullptr ) {
21
60
if (data != nullptr ) {
22
- data->Resolve (context->Value ());
61
+ // data->Resolve(context->Value());
23
62
}
24
63
}
25
64
if (data != nullptr ) {
@@ -30,54 +69,241 @@ static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/,
30
69
// Full type of the ThreadSafeFunctionEx
31
70
using TSFN = ThreadSafeFunctionEx<Context, DataType, CallJs>;
32
71
33
- class TSFNWrap ;
72
+ struct FinalizerDataType {
73
+ std::vector<std::thread> threads;
74
+ std::unique_ptr<Promise::Deferred> deferred;
75
+ // struct {
76
+ // bool logCall = true; // Uses JS console.log to output when the TSFN is
77
+ // // processing the NonBlockingCall().
78
+ // bool logThread = false; // Uses native std::cout to output when the thread's
79
+ // // NonBlockingCall() request has finished.
80
+ // } options; // Options from Start()
81
+ };
82
+
83
+ static void threadEntry (size_t threadId, TSFN tsfn, uint32_t callCount,
84
+ bool logThread) {
85
+ using namespace std ::chrono_literals;
86
+ for (auto i = 0U ; i < callCount; ++i) {
87
+ // auto callData = new DataType();
88
+ // tsfn.NonBlockingCall(callData);
89
+ // auto result = callData->deferred.get();
90
+ // if (logThread) {
91
+ // std::cout << "Thread " << threadId << " got result " << result << "\n";
92
+ // }
93
+
94
+ // std::this_thread::sleep_for(50ms * threadId);
95
+ }
96
+ std::cout << " Thread " << threadId << " finished\n " ;
97
+ tsfn.Release ();
98
+ }
99
+
34
100
using base = tsfnutil::TSFNWrapBase<TSFNWrap, Context, TSFN>;
35
101
36
102
// A JS-accessible wrap that holds the TSFN.
37
103
class TSFNWrap : public base {
38
104
public:
39
105
TSFNWrap (const CallbackInfo &info) : base(info) {
106
+ if (info.Length () > 0 && info[0 ].IsObject ()) {
107
+ auto arg0 = info[0 ].ToObject ();
108
+ if (arg0.Has (" cppExceptions" )) {
109
+ auto cppExceptions = arg0.Get (" cppExceptions" );
110
+ if (cppExceptions.IsBoolean ()) {
111
+ cppExceptions = cppExceptions.As <Boolean>();
112
+ } else {
113
+ // We explicitly use the addon's except/noexcept settings here, since
114
+ // we don't have a valid setting.
115
+ Napi::TypeError::New (Env (), " cppExceptions is not a boolean" )
116
+ .ThrowAsJavaScriptException ();
117
+ }
118
+ }
119
+ }
120
+ }
121
+ ~TSFNWrap () {
122
+ for (auto & thread : finalizerData->threads ) {
123
+ if (thread.joinable ()) {
124
+ thread.join ();
125
+ }
126
+ }
127
+ }
128
+
129
+ static std::array<ClassPropertyDescriptor<TSFNWrap>, 3 > InstanceMethods () {
130
+ return {InstanceMethod (" call" , &TSFNWrap::Call),
131
+ InstanceMethod (" start" , &TSFNWrap::Start),
132
+ InstanceMethod (" release" , &TSFNWrap::Release)};
133
+ }
134
+
135
+ bool cppExceptions = false ;
136
+ std::shared_ptr<FinalizerDataType> finalizerData;
137
+
138
+ Napi::Value Start (const CallbackInfo &info) {
40
139
Napi::Env env = info.Env ();
41
140
42
- Context *context = new Context (Persistent (info[0 ]));
141
+ if (_tsfn) {
142
+ TSFN_THROW (this , Napi::Error::New (Env (), " TSFN already exists." ),
143
+ Value ());
144
+ }
145
+
146
+ // Creates a list to hold how many times each thread should make a call.
147
+ std::vector<int32_t > callCounts;
148
+
149
+ // The JS-provided callback to execute for each call (if provided)
150
+ Function callback;
151
+
152
+ // std::unique_ptr<FinalizerDataType> finalizerData =
153
+ // std::make_unique<FinalizerDataType>();
154
+
155
+ // finalizerData = std::shared_ptr<FinalizerDataType>(FinalizerDataType{ std::vector<std::thread>() , Promise::Deferred::New(env) });
156
+
157
+ finalizerData = std::make_shared<FinalizerDataType>();
158
+
159
+
160
+ bool logThread = DefaultOptions.logThread ;
161
+ bool logCall = DefaultOptions.logCall ;
162
+
163
+ if (info.Length () > 0 && info[0 ].IsObject ()) {
164
+ auto arg0 = info[0 ].ToObject ();
165
+ if (arg0.Has (" threads" )) {
166
+ Napi::Value threads = arg0.Get (" threads" );
167
+ if (threads.IsArray ()) {
168
+ Napi::Array threadsArray = threads.As <Napi::Array>();
169
+ for (auto i = 0U ; i < threadsArray.Length (); ++i) {
170
+ Napi::Value elem = threadsArray.Get (i);
171
+ if (elem.IsNumber ()) {
172
+ callCounts.push_back (elem.As <Number>().Int32Value ());
173
+ } else {
174
+ TSFN_THROW (this , Napi::TypeError::New (Env (), " Invalid arguments" ),
175
+ Value ());
176
+ }
177
+ }
178
+ } else if (threads.IsNumber ()) {
179
+ auto threadCount = threads.As <Number>().Int32Value ();
180
+ for (auto i = 0 ; i < threadCount; ++i) {
181
+ callCounts.push_back (DEFAULT_CALL_COUNT);
182
+ }
183
+ } else {
184
+ TSFN_THROW (this , Napi::TypeError::New (Env (), " Invalid arguments" ),
185
+ Value ());
186
+ }
187
+ }
188
+
189
+ if (arg0.Has (" callback" )) {
190
+ auto cb = arg0.Get (" callback" );
191
+ if (cb.IsFunction ()) {
192
+ callback = cb.As <Function>();
193
+ } else {
194
+ TSFN_THROW (this ,
195
+ Napi::TypeError::New (Env (), " Callback is not a function" ),
196
+ Value ());
197
+ }
198
+ }
199
+
200
+ if (arg0.Has (" logCall" )) {
201
+ auto logCallOption = arg0.Get (" logCall" );
202
+ if (logCallOption.IsBoolean ()) {
203
+ logCall = logCallOption.As <Boolean>();
204
+ } else {
205
+ TSFN_THROW (this ,
206
+ Napi::TypeError::New (Env (), " logCall is not a boolean" ),
207
+ Value ());
208
+ }
209
+ }
210
+
211
+ if (arg0.Has (" logThread" )) {
212
+ auto logThreadOption = arg0.Get (" logThread" );
213
+ if (logThreadOption.IsBoolean ()) {
214
+ logThread = logThreadOption.As <Boolean>();
215
+ } else {
216
+ TSFN_THROW (this ,
217
+ Napi::TypeError::New (Env (), " logThread is not a boolean" ),
218
+ Value ());
219
+ }
220
+ }
221
+ }
222
+
223
+
224
+ // Apply default arguments
225
+ if (callCounts.size () == 0 ) {
226
+ for (auto i = 0U ; i < DEFAULT_THREAD_COUNT; ++i) {
227
+ callCounts.push_back (DEFAULT_CALL_COUNT);
228
+ }
229
+ }
230
+
231
+ const auto threadCount = callCounts.size ();
232
+
233
+ auto *finalizerDataPtr = new std::shared_ptr<FinalizerDataType>(finalizerData);
43
234
44
235
_tsfn = TSFN::New (
45
236
env, // napi_env env,
46
237
TSFN::DefaultFunctionFactory (env), // const Function& callback,
47
238
Value (), // const Object& resource,
48
239
" Test" , // ResourceString resourceName,
49
240
0 , // size_t maxQueueSize,
50
- 1 , // size_t initialThreadCount,
51
- context, // Context* context,
52
- base:: Finalizer, // Finalizer finalizer
53
- &_deferred // FinalizerDataType data
241
+ threadCount + 1 , // size_t initialThreadCount, +1 for Node thread
242
+ this , // Context* context,
243
+ Finalizer, // Finalizer finalizer
244
+ finalizerDataPtr // FinalizerDataType* data
54
245
);
55
- }
56
246
57
- static std::array<ClassPropertyDescriptor<TSFNWrap>, 3 > InstanceMethods () {
58
- return {InstanceMethod (" call" , &TSFNWrap::Call),
59
- InstanceMethod (" getContext" , &TSFNWrap::GetContext),
60
- InstanceMethod (" release" , &TSFNWrap::Release)};
247
+ for (auto threadId = 0U ; threadId < threadCount; ++threadId) {
248
+ finalizerData->threads .push_back (
249
+ std::thread (threadEntry, threadId, _tsfn, callCounts[threadId],
250
+ logThread));
251
+ }
252
+
253
+
254
+ return String::New (env, " started" );
255
+ };
256
+
257
+ // TSFN finalizer. Resolves the Promise returned by `Release()` above.
258
+ static void Finalizer (Napi::Env env, std::shared_ptr<FinalizerDataType> *finalizeData,
259
+ Context *ctx) {
260
+ // for (auto thread : finalizeData->threads) {
261
+
262
+ for (auto &thread : (*finalizeData)->threads ) {
263
+ std::cout << " Finalizer joining thread\n " ;
264
+ if (thread.joinable ()) {
265
+ thread.join ();
266
+ }
267
+ }
268
+
269
+ delete finalizeData;
270
+
271
+ // }
272
+ // if (deferred->get()) {
273
+ // (*deferred)->Resolve(Boolean::New(env, true));
274
+ // deferred->release();
275
+
276
+ // }
61
277
}
62
278
279
+ Napi::Value Release (const CallbackInfo &info) {
280
+ if (finalizerData->deferred ) {
281
+ return finalizerData->deferred ->Promise ();
282
+ }
283
+ // return finalizerData->deferred.Promise();
284
+ auto env = info.Env ();
285
+ finalizerData->deferred .reset (new Promise::Deferred (Promise::Deferred::New (env)));
286
+ _tsfn.Release ();
287
+ return finalizerData->deferred ->Promise ();
288
+ };
289
+
63
290
Napi::Value Call (const CallbackInfo &info) {
64
- auto *callData = new DataType (info.Env ());
65
- _tsfn.NonBlockingCall (callData);
66
- return callData->Promise ();
291
+ // auto *callData = new DataType(info.Env());
292
+ // _tsfn.NonBlockingCall(callData);
293
+ // return callData->Promise();
294
+ return info.Env ().Undefined ();
67
295
};
68
296
69
297
Napi::Value GetContext (const CallbackInfo &) {
70
298
return _tsfn.GetContext ()->Value ();
71
299
};
72
300
};
73
- } // namespace context
74
-
301
+ } // namespace example
75
302
76
303
Object InitThreadSafeFunctionExExample (Env env) {
77
304
auto exports (Object::New (env));
78
305
example::TSFNWrap::Init (env, exports, " example" );
79
306
return exports;
80
307
}
81
308
82
-
83
309
#endif
0 commit comments