The situation is as follows: my JavaScript shall start an external hardware process via a node-addon-api call into a C++ library. That hardware process runs for an indeterminate amount of time and is (somehow) handled by the library thread which has nothing to do with Node. After the external process has finished I want to execute a JavaScript callback to the object which made the start call. What I found online was roughly equivalent to this:
#include <napi.h>
class MyAsyncWorker;
MyAsyncWorker *gAsyncWorker{};
bool gDelete;
class MyAsyncWorker : public Napi::AsyncWorker {
int work_units{0};
public:
MyAsyncWorker(Napi::Function& callback)
: Napi::AsyncWorker(callback)
{}
void DoWork() { // called by library thread
++work_units;
}
void Execute() override { // called by a Node thread?
while (!gDelete) {
if (work_units > 50) {
OnOK();
gDelete = true;
} else { using namespace std::chrono_literals;
std::this_thread::sleep_for(10ms);
}
}
}
void OnOK() override {
Napi::HandleScope scope(Env());
Callback().Call({Env().Undefined()});
}
};
Napi::Value JS_start_async_worker(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function callback = info[0].As<Napi::Function>();
gAsyncWorker = new MyAsyncWorker(callback);
gAsyncWorker->Queue();
return env.Undefined();
}
bool hardware_has_data() { return std::rand() < 0.01; }
void library_thread() {
while(true) {
if (!gDelete) {
if(gAsyncWorker && hardware_has_data())
gAsyncWorker->DoWork();
} else {
delete gAsyncWorker;
gAsyncWorker = nullptr;
gDelete = false;
}
}
}
std::thread l(library_thread);
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "start_async_worker"), Napi::Function::New(env, JS_start_async_worker));
return exports;
}
NODE_API_MODULE(addon, Init)
Disclaimer: please ignore the shoddy inter-thread primitives (none to be exact), I just sketched those to keep the example short.
The JavaScript would look something like this:
someObject.start_async_worker(()->{ console.out("done"); });
My question is if the constructs dealing with MyAsyncWorker regarding its lifetime and thread safety under Node are legal.
Am I allowed to generate an
AsyncWorkerwith a global lifetime like above or will this break?As far as I understand this mechanism, an extra thread will be created for my
AsyncWorker::Execute()when I pass it to Node via->Queue(). But what if one has thousands of such asynchronous workers? The library can handle it because it doesn't need threads for each, how would you offload that burden from Node?How do I correctly destroy the
AsyncWorkerobject?