nodejs / abi-stable-node

Repository used by the Node-API team to manage work related to Node-API and node-addon-api
239 stars 47 forks source link

Async callbacks when native code calls code synchronously from different thread #322

Closed TravisGesslein closed 6 years ago

TravisGesslein commented 6 years ago

I'm currently trying to write a native module for a third party library (for https://www.fmod.com).

Its C++ API has some async features that allow user-provided callbacks, but the callbacks themselves are called synchronously, just from a different thread that was spawned by the third party library. I guess a bit of pseudo-code is better to explain:

In C++, I will call the 3rd party library code like this:

callback_struct struct;
struct.callback1 = mycallback1;
//... etc.

3rdpartylibrary.doSomethingAsync(&struct);

Now, inside doSomethingAsync, the 3rd party library will spawn a new thread that will periodically call the callbacks I provided, but those callbacks themselves are called synchronously and their results are expected 'immediately:

//this is executed in a different thread than the thread that uses the napi
3rdpartylibrary::runTheAsyncThing(callback_struct *callbacks)
{
    while(taskNotFinished)
    {
         result = callbacks->callback1(arg1,arg2,...);
     }
}

So far so good. Now I want to expose both the "3rdpartylibrary.doSomethingAsync(&struct)" part as well as the actual callbacks to Javascript. So I can do something like

//This is js now
var 3rd = require("3rdpartylibrary");

var callbacks = {
callback1: function(arg1,arg2,...){ return 42; }
}

3rd.doSomethingAsync(callbacks);

Exposing those APIs and providing the callbacks already works. In native code, I give the 3rd party library a callback that internally tries to simply call the provided javascript callback in a synchronous fashion. Or at least it tries to.

void nativecallback1(arg1,arg2,..., void* userdata)
{
     //extract napi_env, etc. from userdata
     //do some setup, like converting the parameters to javascript values, and then...
     napi_call_function(...); //calls javascript callback
}

However, since that native callback is called by the 3rd party library in a different thread, I can't use napi directly in there because I will get V8 errors, and would need to schedule async work using napi_create_async_work.

However, that work is of course executed asynchronously - but the function I'm trying to launch that from needs to give results back to the native code in a synchronous manner.

I see one possible solution, but it seems kind of ugly. Inside that nativecallback1, I could schedule the async work, and then simply wait for it using some kind of multithreading signaling, where the async callback that I provided to napi_create_async_work sets off the signal:

void nativecallback1(arg1,arg2,..., void* userdata)
{
     //extract napi_env, etc. from userdata
     //do some setup, like converting the parameters to javascript values, and then...

     napi_create_async_work(...);

     wait_on_signal; //waits on signal that is fired by the napi_async_complete_callback

     return ...; //result extracted back from the void *data given to napi_create_async_work
}

As you can imagine, that is quite a lot of boilerplate just to reroute a little callback.

Do you see a simpler solution to the problem?

TravisGesslein commented 6 years ago

On that note, can I even schedule async work from a different thread? create_async_work expects two napi_values, which I can't create in that kind of thread (I think). Do I need to create the async work when the user initially sets the callback in JS code, and then only use it using the queue function from the different thread?

gabrielschulhof commented 6 years ago

I think you need https://github.com/nodejs/node/pull/17887

On Tue, May 15, 2018 at 10:59 AM, Travis Gesslein notifications@github.com wrote:

On that note, can I even schedule async work from a different thread? create_async_work expects two napi_values, which I can't create in that kind of thread (I think). Do I need to create the async work when the user initially sets the callback in JS code, and then only use it using the queue function from the different thread?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/nodejs/abi-stable-node/issues/322#issuecomment-389198936, or mute the thread https://github.com/notifications/unsubscribe-auth/AA7k0XQIDtuGn0ABsMRwdFUBI9qAhsgfks5tyu1egaJpZM4T_n2e .

TravisGesslein commented 6 years ago

Thanks. Looks like that will help. Is there a work-around until that PR ends up in the main nodej.js distribution?

gabrielschulhof commented 6 years ago

The implementation of that PR doesn't expose any new APIs but rather bundles them to cover your use case. Therefore you can use existing APIs to reconstruct the portions of the functionality you need.

On Tue, May 15, 2018 at 1:19 PM, Travis Gesslein notifications@github.com wrote:

Thanks. Looks like that will help. Is there a work-around until that PR ends up in the main nodej.js distribution?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nodejs/abi-stable-node/issues/322#issuecomment-389245681, or mute the thread https://github.com/notifications/unsubscribe-auth/AA7k0TrKgaF_Col8vHZWJwWqO6zsdJCsks5tyw4ugaJpZM4T_n2e .

TravisGesslein commented 6 years ago

Thanks. The new API looks great though, hopefully it will be merged soon.