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. So I have big gaps in knowledge when it comes to the inner workings of v8, libuv, and node.
I was originally just calling the JavaScript callback from the v8 garbage collection callback but that ended up with a segv after a couple calls (#648). It seems that just making a call into JavaScript while being called from a v8 callback is a bad idea. From v8 docs the callbacks shouldn't allocate objects (and I did). So I looked around and found a Nan-based example that uses libuv and Nan's AsyncResource to make the callback. This approach works.
// except from gc callback code
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>(static_cast<double>(data->gcTime)));
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);
}
Now i'm trying to figure out how to use node-addon-api do the same thing. It's not clear to me how to accomplish this - what do the uv calls map to in the node-addon-api (is it an async worker or do I need to keep using the uv calls)? Is there a different paradigm I should be using?
The net question is how do I safely call back into JavaScript from a v8 garbage collection callback? Any help is appreciated.
If you need to be notified when an object is garbage-collected, you do not need an async context. You can use Napi::Object::AddFinalizer to attach a callback to an object that gets called when the object is garbage-collected.
The finalizer callback is free to call into JS without any special setup.
@gabrielschulhof - thank you. i've been reading more docs and trying to learn more about the how the pieces fit together. i think i can get down to a pretty basic question - apologies for my scattershot above; it was poorly formed as a result of my having too many gaps in my knowledge.
using node-addon-api (or n-api) is there an equivalent to using uv functions in order to safely call javascript from (potentially) asynchronous c++, as in:
uv_async_t* async = new uv_async_t;
// set data
async->data = data;
uv_async_init(uv_default_loop(), async, asyncCB);
uv_async_send(async);
@bmacnaughton you can use Napi::AsyncWorker if you have a self-contained function you would like to run on a secondary thread provided by Node.js, and Napi::ThreadSafeFunction if you have an existing threading implementation and you would like to be able to call into JavaScript from this implementation. Napi::ThreadSafeFunction uses a uv_async_t and Napi::AsyncWorker uses a uv_work_t.
uv_async_t* async = new uv_async_t;
// set data
async->data = data;
uv_async_init(uv_default_loop(), async, asyncCB);
uv_async_send(async);
asyncCB do not invoke when call the uv_async_send in node addon c++ thread?
Hi @springjjc,
the callback will be called on the loop thread.
uv_async_send provides an eventual invocation guarantee, which means that the async callback might not be called immediately on the uv_async_send and delay might be significant if the loop thread is very busy. Also, note that multiple calls of uv_async_send might collapse into a single call of async callback on loop thread.
Most helpful comment
uv_async_sendprovides an eventual invocation guarantee, which means that the async callback might not be called immediately on theuv_async_sendand delay might be significant if the loop thread is very busy. Also, note that multiple calls ofuv_async_sendmight collapse into a single call of async callback on loop thread.