Search code examples
javascriptc++node.jsmultithreadingv8

Callback NodeJS Javascript function from multithreaded C++ addon


I have a multithreaded C++ addon that does some background processing and I need to have it periodically callback to a Javascript function that I wrote in my NodeJS server.

I understand that this involves using uv_async_send(), since it needs to get executed in the main thread, but thus far I haven't been able to figure out how to do it.

Is there a simple example out there that I've missed?


Solution

  • Finally, this was not too difficult once I understood what the the uv_* functions do:

    1) Expose a function in the addon to allow Node to set the Javascript cb that will be periodically called back to:

    Callback* cbPeriodic; // keep cbPeriodic somewhere
    NAN_METHOD(setPeriodicCb) {
        cbPeriodic = new Callback(info[0].As<Function>());
        //...
    }
    

    2) Init UV with an uv_async_t instance and a function that will be executed in the main thread by UV when our worker thread calls uv_async_send()

    uv_async_t async; // keep this instance around for as long as we might need to do the periodic callback
    uv_loop_t* loop = uv_default_loop();
    uv_async_init(loop, &async, asyncmsg);
    
    void asyncmsg(uv_async_t* handle) {
      // Called by UV in main thread after our worker thread calls uv_async_send()
      //    I.e. it's safe to callback to the CB we defined in node!
      Nan::HandleScope scope;
      v8::Isolate* isolate = v8::Isolate::GetCurrent();
      Local<Value> argv[] = { v8::String::NewFromUtf8(isolate, "Hello world") };
      cbPeriodic->Call(1, argv);
    }
    

    3) Call uv_async_send from a worker thread, passing our async instance above, whenever we need to do the periodic callback

    uv_async_send(&async);
    

    4) Finally, when we no longer need to execute the callback ever again, clean things up:

    uv_close((uv_handle_t*) &async, NULL);
    

    Addendum:

    Since I wrote this answer I've run into other issues and finally learned some lessons about libuv. For completeness, you should understand:

    • Aside from uv_async_send, all libuv functions may only be called from the the main loop thread! (I had seen it mentioned that the others were not thread-safe, but this is too weak of a statement.) Even, for example, uv_async_init and uv_close must be called from the main loop thread.

    • If your uv_async_t instance is dynamically allocated, note that you may not free up memory until uv_close makes its callback to let you know that it is safe to do so.

    I.e.:

    auto async = new uv_async_t();
    ...
    uv_close((uv_handle_t*)async, [](uv_handle_t* handle) {
        delete handle;
    });