Node-addon-api: question: how to use node-addon-api to handle callback like nan

Created on 22 Jan 2020  Â·  6Comments  Â·  Source: nodejs/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. 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.

Most helpful comment

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.

All 6 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fivdi picture fivdi  Â·  8Comments

timarandras picture timarandras  Â·  4Comments

jeffrson picture jeffrson  Â·  4Comments

NickNaso picture NickNaso  Â·  8Comments

langxiong picture langxiong  Â·  5Comments