Integrating third party async libraries with NodeJS - c++

How do I implement Node JS addon with asnyc third part library.
I am able to implement synchronous functions but when it comes to asynchronous functions not sure how exactly it works. Should it be invoked from main loop or it should be in async_work.
The scenario is similar to example explained in http://nikhilm.github.io/uvbook/threads.html#inter-thread-communication but instead of downloading in async_work, I want to invoke asynchronous function which takes care of downloading.
Example async function : download(url, callback);
Sample Code:
Handle<Value> DNS::Download(const Arguments& args) {
HandleScope scope;
uv_loop_t *loop = uv_default_loop();
uv_async_t *uv_async_req = new uv_async_t;
uv_work_t *uv_work_req = new uv_work_t;
AsyncData *asyncData = new AsyncData;
asyncData->url = "http://..../";
uv_async_req->data = asyncData;
uv_work_req->data = asyncData;
uv_async_init(loop, uv_async_req, send_progress);
uv_queue_work(loop, uv_work_req, initiate_download, after);
//Not sure if i have to invoke download(url, callback); here itself
//or in fake_download
return scope.Close(Undefined());
}
void send_progress(uv_async_t *handle, int status /*UNUSED*/) {
AsyncData *asyncData = (AsyncData*)handle->data;
Handle<Value> values[] = {};
//Invoking js callback function.
asyncData->callback->Call(Context::GetCurrent()->Global(), 0, values);
}
void initiate_download(uv_work_t *req) {
//I would like to invoke non blocking async function here
download(url, callback);
}
void callback(status, size) {
//Send event to the send_progress
uv_async_send(&async);
}
My call back is not getting invoked at all in both the cases(Invoking in main thread or in async_work.) and JavaScript keeps waiting for the callback.
Any examples are greatly appreciated.
Thanks in advance.

Related

Boost awaitable: write into a socket and await particular response

The problem can be a bit complex. I will try to explain the best possible the situation and what tools I imagined to solve my problems.
I am writing a socket application that may write into a socket and expects a response. The protocol enable that in an easy way: each request has a "command id" that will be forwarded back into the response so we can have code that react to that particular request.
For simplicity, we will assume all communication is done using json in the socket.
First, let's assume this session type:
using json = /* assume any json lib */;
struct socket_session {
auto write(json data) -> boost::awaitable<void>;
auto read() -> boost::awaitable<json>;
private:
boost::asio::ip::tcp::socket socket;
};
Usually, I would go with a callback system that go (very) roughly like this.
using command_it_t = std::uint32_t;
// global incrementing command id
command_it_t command_id = 0;
// All callbacks associated with commands
std::unordered_map<command_id_t, std::function<void(json)>> callbacks;
void write_command_to_socket(
boost::io_context& ioc,
socket_session& session,
json command,
std::function<void(json)> callback
) {
boost::co_spawn(ioc, session->write(command), asio::detached);
callbacks.emplace(command_id++, callback);
}
// ... somewhere in the read loop, we call this:
void call_command(json response) {
if (auto const& command_id = response["command"]; command_id.is_integer()) {
if (auto const it = callbacks.find(command_id_t{command_id}); it != callbacks.end()) {
// We found the callback for this command, call it!
auto const& [id, callback] = *it;
callback(response["payload"]);
callbacks.erase(it);
}
}
}
It would be used like this:
write_command_to_socket(ioc, session, json_request, [](json response) {
// do stuff
});
As I began using coroutine more and more for asynchronous code, I noticed that it's a golden opportunity to use them in that kind of system.
Instead of sending a callback to the write function, it would return a boost::awaitable<json>, that would contain the response payload, I imagined it a bit like this:
auto const json_response = co_await write_command_to_socket(session, json_request);
Okay, here's the problem
So the first step to do that was to transform my code like this:
void write_command_to_socket(socket_session& session, json command) {
co_await session->write(command);
co_return /* response data from the read loop?? */
}
I noticed that I don't have any mean to await on the response, as it is on another async loop. I was able to imagine a system that looked like I wanted, but I have no idea how to translate my own mental model to asio with coroutines.
// Type from my mental model: an async promise
template<typename T>
struct promise {
auto get_value() -> boost::awaitable<T>;
auto write_value(T value);
};
// Instead of callbacks, my mental model needs promises structured in a similar way:
std::unordered_map<command_id_t, promise<json>> promises;
void write_command_to_socket(socket_session& session, json command) {
auto const [it, inserted] = promises.emplace(session_id++, promise<json>{});
auto const [id, promise] = *it;
co_await session->write(command);
// Here we awaits until the reader loop sets the value
auto const response_json = co_await promise.get_value();
co_return response_json;
}
// ... somewhere in the read loop
void call_command(json response) {
if (auto const& command_id = response["command"]; command_id.is_integer()) {
if(auto const it = promises.find(command_id_t{command_id}); it != promises.end()) {
auto const& [id, promise] = *it;
// Effectively calls the write_command_to_socket coroutine to continue
promise.write_value(response["payload"]);
promise.erase(it);
}
}
}
As far as I know, the "promise type" I written here as an example don't exist in boost. Without that type, I really struggle how my command system can exist. Would I need to write my own coroutine type for that kind of system? Is there a way I can just get away using boost's coroutine types?
With asio, as I said, the "promise type" don't exist. Asio instead uses continuation handlers, which are kind of callbacks that may actually call a callback or resume a coroutine.
To create such continuation handler, one must first initiate an async operation. The async operation can be resumed by another if you want, or composed of many async operation. This is done with the asio::async_initiate function, which takes some parameter reguarding the form of the continuation:
// the completion token type can be a callback,
// could be `asio::use_awaitable_t const&` or even `asio::detached_t const&`
return asio::async_initiate<CompletionToken, void(json)>(
[self = shared_from_this()](auto&& handler) {
// HERE! `handler` is a callable that resumes the coroutine!
// We can register it somewhere
callbacks.emplace(command_id, std::forward<decltype(handler)>(handler));
}
);
To resume the async operation, you simply have to call the continuation handler:
void call_command(json response) {
if (auto const& command_id = response["command"]; command_id.is_integer()) {
if (auto const it = callbacks.find(command_id_t{command_id}); it != callbacks.end()) {
// We found the continuation handler for this command, call it!
// It resumes the coroutine with the json as its result
auto const& [id, callback] = *it;
callback(response["payload"]);
callbacks.erase(it);
}
}
}
Here's the rest of the system, how it would look like (very roughtly):
using command_it_t = std::uint32_t;
// global incrementing command id
command_it_t command_id = 0;
// All callbacks associated with commands
std::unordered_map<command_id_t, moveable_function<void(json)>> callbacks;
void write_command_to_socket(
boost::io_context& ioc,
socket_session session,
json command
) -> boost::asio::awaitable<json> {
return asio::async_initiate<boost::asio::use_awaitable_t<> const&, void(json)>(
[&ioc, session](auto&& handler) {
callbacks.emplace(command_id, std::forward<decltype(handler)>(handler));
boost::asio::co_spawn(ioc, session.write(command), asio::detached);
}
);
}

how to implement node-nan callback using node-addon-api

Until now I've only implemented synchronous node-addon-api methods, i.e., a JavaScript function makes a call, work is done, and the addon returns. I have big gaps in knowledge when it comes to the inner workings of v8, libuv, and node, so please correct any obvious misconceptions.
The goal is to call a JavaScript callback when C++ garbage collection callbacks are called from v8. I originally just called the JavaScript callback from the v8 garbage collection callback but that ended up with a segv after a couple calls. It seems that just making a call into JavaScript while being called from a v8 callback has some problems (v8 docs the callbacks shouldn't allocate objects). So I looked around and found a Nan-based example that uses libuv and Nan's AsyncResource to make the callback. The following approach works using node-nan:
NAN_GC_CALLBACK(afterGC) {
uint64_t et = uv_hrtime() - gcStartTime;
// other bookkeeping for GCData_t raw.
if (doCallbacks) {
uv_async_t* async = new uv_async_t;
GCData_t* data = new GCData_t;
*data = raw;
data->gcTime = et;
async->data = data;
uv_async_init(uv_default_loop(), async, asyncCB);
uv_async_send(async);
}
}
class GCResponseResource : public Nan::AsyncResource {
public:
GCResponseResource(Local<Function> callback_)
: Nan::AsyncResource("nan:gcstats.DeferredCallback") {
callback.Reset(callback_);
}
~GCResponseResource() {
callback.Reset();
}
Nan::Persistent<Function> callback;
};
static GCResponseResource* asyncResource;
static void closeCB(uv_handle_t *handle) {
delete handle;
}
static void asyncCB(uv_async_t *handle) {
Nan::HandleScope scope;
GCData_t* data = static_cast<GCData_t*>(handle->data);
Local<Object> obj = Nan::New<Object>();
Nan::Set(obj, Nan::New("gcCount").ToLocalChecked(),
Nan::New<Number>((data->gcCount));
Nan::Set(obj, Nan::New("gcTime").ToLocalChecked(),
Nan::New<Number>(data->gcTime));
Local<Object> counts = Nan::New<v8::Object>();
for (int i = 0; i < maxTypeCount; i++) {
if (data->typeCounts[i] != 0) {
Nan::Set(counts, i, Nan::New<Number>(data->typeCounts[i]));
}
}
Nan::Set(obj, Nan::New("gcTypeCounts").ToLocalChecked(), counts);
Local<Value> arguments[] = {obj};
Local<Function> callback = Nan::New(asyncResource->callback);
v8::Local<v8::Object> target = Nan::New<v8::Object>();
asyncResource->runInAsyncScope(target, callback, 1, arguments);
delete data;
uv_close((uv_handle_t*) handle, closeCB);
}
My question is how would I do this using the node-addon-api instead of nan?
It's not clear to me what the node-addon-api equivalent of uv_async_init, uv_async_send, etc are. This is partially because it's not clear to me what underlying N-API (as opposed to node-addon-api) functions are required.
I have been unable to find an example like this. The callback example is completely synchronous. The async pi example uses a worker thread to perform a task but that seems overkill compared to the approach in the nan-based code using the uv primitives.
Your example is not really asynchronous, because the GC callbacks run in the main thread. However when the JS world is stopped because of the GC, this does not mean that it is stopped in a way allowing a callback to run - as the GC can stop it in the middle of a function.
You need a ThreadSafeFunction to do this. Look here for an example:
https://github.com/nodejs/node-addon-api/blob/main/doc/threadsafe_function.md

Using a pointer to Isolate in libuv worker thread

I try to develop an async Node.js addon that works with an Isolate.
Consider this example code:
struct Work {
uv_work_t request;
Persistent<Function> callback;
Isolate * isolate;
};
// called in worker thread
static void WorkAsync(uv_work_t *req)
{
Work *work = static_cast<Work *>(req->data);
HeapStatistics stats;
work->isolate->GetHeapStatistics(&stats);
// ... do other stuff ...
}
// called by in main thread
static void WorkAsyncComplete(uv_work_t *req, int status)
{
Isolate * isolate = Isolate::GetCurrent();
v8::HandleScope handleScope(isolate);
Work *work = static_cast<Work *>(req->data);
// ... do other stuff ...
work->callback.Reset();
delete work;
}
void RunAsync(const v8::FunctionCallbackInfo<v8::Value>&args) {
Isolate* isolate = args.GetIsolate();
Work * work = new Work();
work->request.data = work;
work->isolate = isolate;
Local<Function> callback = Local<Function>::Cast(args[0]);
work->callback.Reset(isolate, callback);
// kick of the worker thread
uv_queue_work(uv_default_loop(), &work->request, WorkAsync, WorkAsyncComplete);
args.GetReturnValue().Set(Undefined(isolate));
}
void setup(Handle <Object> exports, Handle<Object> module) {
NODE_SET_METHOD(exports, "run", RunAsync);
}
NODE_MODULE(addon, setup)
My question: is it safe to pass a pointer to current Isolate to a worker thread (for read-only purposes)?
Thanks!
Answered by Scott Frees (the author of C++ and Node.js Integration ebook: https://scottfrees.com/ebooks/nodecpp/):
To be honest, I've never tried to do that. It seems to me that it should be fine - as long as you are only making calls on the isolate and not actually modifying anything within it.
Generally, if you try to do anything in violation with the V8 threading model, it will generate an exception - so if it's not, then I'd assume it is ok.
Yes it's possible. However, you are the responsable of the safety. You need to be sure that Isolate is leaved from the parent thread and unlocked. Then it can be used (Entred) in the worker thread.

Invoke javascript callback repeatedly from C++ module in node.js

So I am writing a node.js module in C++ which processes data from a camera. I want to invoke a callback function in my app.js file whenever new data is available.
What I have at the moment
Right now I have in my node javascript file (app.js) the following code. Every second it calls a function in my C++-Module and returns the number of frames that have been processed so far:
setInterval(function () {
var counter = MyCPPModule.NumberOfFrames();
console.log(counter);
}, 1000);
In my C++ file I have the following functions.
1.) I have a function to get the number of frames - the function that gets called from javascript as above- , where frameCounter refers to a global variable.
void NumberOfFrames(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Local<Integer> retval = v8::Integer::New(isolate, frameCounter);
args.GetReturnValue().Set(retval);
}
2.) And I have a C++-callback function in the C++ module that gets triggered whenever a new frame is available (just to be clear, this callback is independent of node.js and has nothing directly to do with node.js-callbacks).
void NewFrameAvailable(char** imageBuffer, /* more arguments */)
{
// ...
frameCounter++; // increment global variable
// ...
}
All of this works fine.
What I would like to accomplish
Instead of registering a function with setInterval in my javascript code. I would like to somehow register a function with my C++ module that gets called repeatedly whenever a new frame is available from the camera. It should behave like setInterval, but instead of being triggered every second, it gets triggered when a frame is available.
The code that I am hoping for on the javascript-side would be something like:
MyCPPModule.setMyFrameInterval(function (msg) {
console.log(msg);
});
Inside the C++ Module I would like to do something like:
void NewFrameAvailable(char** imageBuffer, /* more arguments */)
{
// ...
frameCounter++; // increment global variable
Local<Function> cb = /* WHAT DO I DO HERE*/ ;
Isolate*isolate = /* WHAT DO I DO HERE */;
const unsigned argc = 1;
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, std::to_string(frameCounter)) };
cb->Call(Null(isolate), argc, argv);
// ...
}
and a function that registers the javascipt-callback with setMyFrameInterval:
void setMyFrameInterval(const FunctionCallbackInfo<Value>& args) {
Local<Function> cb = Local<Function>::Cast(args[0]); // make this somehow global?
Isolate*isolate = args.GetIsolate()
//...
}
So, how can I invoke the javascript callback from NewFrameAvailable (the C++ callback function which gets triggered when frames are available). I think I basically have to make the javascript function somehow globally available in the setMyFrameInterval function so that it is also known to the newFrameAvailable function. How do I do this?

Calling callback from node.js native code

I'm writing an add-on for node.js using c++.
here some snippets:
class Client : public node::ObjectWrap, public someObjectObserver {
public:
void onAsyncMethodEnds() {
Local<Value> argv[] = { Local<Value>::New(String::New("TheString")) };
this->callback->Call(Context::GetCurrent()->Global(), 1, argv);
}
....
private:
static v8::Handle<v8::Value> BeInitiator(const v8::Arguments& args) {
HandleScope scope;
Client* client = ObjectWrap::Unwrap<Client>(args.This());
client->someObject->asyncMethod(client, NULL);
return scope.Close(Boolean::New(true));
}
static v8::Handle<v8::Value> SetCallback(const v8::Arguments& args) {
HandleScope scope;
Client* client = ObjectWrap::Unwrap<Client>(args.This());
client->callback = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
return scope.Close(Boolean::New(true));
}
I need to save a javascript function as callback to call it later.
The Client class is an observer for another object and the javascript callback should be called from onAsyncMethodEnds.
Unfortunately when I call the function "BeInitiator" I receive "Bus error: 10" error just before the callback Call()
thanks in advice
You cannot ->Call from another thread. JavaScript and Node are single threaded and attempting to call a function from another thread amounts to trying to run two threads of JS at once.
You should either re-work your code to not do that, or you should read up on libuv's threading library. It provides uv_async_send which can be used to trigger callback in the main JS loop from a separate thread.
There are docs here: http://nikhilm.github.io/uvbook/threads.html