Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N-API Threadsafe call asynchronous javascript callback #1412

Closed
gryor opened this issue Aug 3, 2018 · 17 comments
Closed

N-API Threadsafe call asynchronous javascript callback #1412

gryor opened this issue Aug 3, 2018 · 17 comments

Comments

@gryor
Copy link

gryor commented Aug 3, 2018

Version: v10.8.0
Platform: Linux 4.17.11 x86_64 GNU/Linux

By using the new threadsafe functions I am able to call javascript from another thread and return synchronously a value (e.g. a number) from javascript to another thread.

What is the proper way to call a threadsafe function -> pass arguments to javascript -> await for a result -> return the result -> pass the result back to another thread?

@mhdawson
Copy link
Member

mhdawson commented Aug 7, 2018

@gabrielschulhof do you have an existing example you can point @wilhelmmatilainen to? Might make sense to add one to the https://github.com/nodejs/abi-stable-node-addon-examples

@gryor
Copy link
Author

gryor commented Aug 7, 2018

I have already accomplished this by getting the returned Promise and then calling its then function with success and fail handlers. I got it working.
It would be nice if there were a function to directly handle a Promise returned from javascript.

napi_promise_then(env, promise, my_success_callback, my_fail_callback);

@gabrielschulhof
Copy link

gabrielschulhof commented Aug 7, 2018

@wilhelmmatilainen you can napi_get_named_property(env, promise, "then", &then_callback), napi_get_named_property(env, promise, "catch", &catch_callback), and napi_get_undefined(env, &undefined), then napi_call_function(env, undefined, then_callback, 1, &my_success_callback, nullptr), but it was my understanding that you wish to convey the result of the call into JavaScript back to the thread which called into JavaScript.

There's no straight-forward way of doing that, because, by definition, the thread-safe function call is asynchronous. So, to convey the result of the call into JavaScript back, your best bet is to create a std::queue<void*> guarded by a node::Mutex, and have the napi_threadsafe_function_call_js store the value in the queue. The thread could then retrieve the value at its convenience.

Of course, if you have multiple secondary threads, you need to know which value on the std::queue<void*> corresponds to the result of which call into JavaScript.

@gryor
Copy link
Author

gryor commented Aug 10, 2018

That is exactly how I am doing it. Except the queue has size 1 and it is managed by node.js. Depending on what is needed, the native side blocks or keeps returning EGAIN or EWOULDBLOCK until the result is ready.
If there were more threads than the native module's and node.js, then it could probably be a linked list with a thread id and the queue you mentioned. On access it would travel the short linked list and use the queue specific to exact thread.
Or another option could be loading this simple module with only one thread (+ node.js thread) multiple times. This way the module would always have only one queue.

@gabrielschulhof
Copy link

@wilhelmmatilainen can you link to the code in question (if it's open source)?

@gryor
Copy link
Author

gryor commented Aug 11, 2018

The project is not at least yet public and there is still problems to be solved until it could be used. However, I could probably strip unnecessary code and leave the meaningful parts if needed. It is still not hard to accomplish.

@gabrielschulhof
Copy link

@wilhelmmatilainen please, if you can, that would help a great deal 🙂

@gryor
Copy link
Author

gryor commented Aug 12, 2018

Here it is: https://github.com/wilhelmmatilainen/node-addon-api-async-tsf
Please notice that I only did a fast strip down of my actual project, so there might be some unnecessary code or the overall layout might be unnecessary for other projects.

@gabrielschulhof
Copy link

@wilhelmmatilainen it looks like you need round-trip asynchronous calling, not just from the secondary thread to the JS thread.

@gryor
Copy link
Author

gryor commented Aug 12, 2018

That is correct, as I originally asked.

@gabrielschulhof
Copy link

gabrielschulhof commented Aug 20, 2018

@wilhelmmatilainen if you wish to establish the return value after multiple trips into JS, you need to use napi_wrap() to pass an object into JS and a second binding in which you receive the object back from JS, along with the return value.

secondary thread:
  - creates a pointer to a structure and stores that pointer in
    a local array. The structure contains the data to pass into JS.
    Then it calls napi_call_threadsafe_function.
  -> napi_threadsafe_function_call_js gets called on the loop thread.
    - receives the pointer from the thread and creates a
      new, empty JS object. It uses `napi_wrap()` to place the
      pointer into the JS object, and then it calls js_callback.
    - JS receives the JS object with the native pointer in it
      as well as whatever data was supposed to be part of the
      cross-thread call, and does what it does, passing the JS
      object containing the pointer to whatever callbacks might be
      executed asynchronously.
...
loop thread: some callback that establishes the return value
gets called
  -> calls a binding in your addon, and passes the JS object
     containing the pointer to the binding, along with the
     return value that was established.
     -> the binding retrieves the native pointer and converts
        the return value from a napi_value to some native
        value and writes it into the structure at the pointer.


Meanwhile, the secondary thread occasionally checks the pointer it
created and, when a flag is set inside the structure indicating that,
on the loop thread the second part of the above has happened, it
retrieves the return value and frees the pointer to the structure.

Please let me know if this answers your question!

@gabrielschulhof
Copy link

@wilhelmmatilainen is it possible that you share some details of the project in which you are using napi_threadsafe_function? Since the API is currently still experimental, we are gathering real-world usage data on it before we remove its experimental status.

While it's still experimental, we can also tweak the API. Are there any napi_threadsafe_function-related API changes you would like to see?

@gryor
Copy link
Author

gryor commented Aug 24, 2018 via email

@gabrielschulhof
Copy link

@wilhelmmatilainen I have created an example showing an asynchronous round trip:

https://github.com/gabrielschulhof/abi-stable-node-addon-examples/tree/tsfn_round_trip/thread_safe_function_round_trip/node-api

Do you think we should incorporate any of the functionality shown in the example into a new N-API?

@gabrielschulhof
Copy link

@wilhelmmatilainen the round trip example has now landed on master in nodejs/node-addon-examples:

https://github.com/nodejs/node-addon-examples/tree/master/thread_safe_function_round_trip/node-api

Does it serve your needs?

@gryor
Copy link
Author

gryor commented Oct 24, 2018

Yes and thank you for making the example.

@gryor gryor closed this as completed Oct 24, 2018
@gabrielschulhof
Copy link

Excellent 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants