| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
This PR does not address this issue. @bnoordhuis suggested that I add a mutex to allow the secondary thread to wait for the call to happen on the loop thread. I can do that, however,
This PR addresses this issue, because it leaves the creation/destruction of the vector holding the napi_value items that are used as JavaScript function arguments to the user, and does not accept any per-call void* pointers. The expectation is that the call_js_cb will be able to create from the original void* with which the thread-safe function was created the JavaScript function arguments needed for this call. OTOH, this API has no control over the way the JavaScript function is called, which might actually be a good thing, because the user may do more than just make a function call on the loop thread. The PR takes care to make sure that the call_js_cb runs in a callback context.
This PR addresses this issue because it leaves the calling and return value processing up to the user. I also made another PR which keeps the marshalling and the return value processing separate. Note that the situation wherein the callback(s) on the loop thread may never get called is one that cannot be avoided in either PR. I mean, that's the nature of the call being asynchronous, right? If you make a call from the secondary thread and the loop thread cleans up by calling napi_delete_threadsafe_function() (and uv_async_destroy() as a result of that) before the corresponding call happens on the loop thread then UV removes the pending call from the loop, AFAICT. @bnoordhuis right? |
Sorry, something went wrong.
|
@gabrielschulhof I think it would be great to allow user to provide additional data/context information in napi_call_threadsafe_function . Imagine if you are receiving callbacks from an async non-JS thread and it sends you a bunch of data (say network data buffer pointer, buffer size, etc) that you won't know ahead of time, and you would have to send those data to the threadsafe function. |
Sorry, something went wrong.
|
hmm.. looks like napi_call_threadsafe_function wraps over uv_async_send which seems to be why the limitation is there. This is really unfortunate as this forces everyone to implement their own event queue.... Hopefully libuv can fix it. Another option is for n-api to implement a event queue so that we don't have to. |
Sorry, something went wrong.
|
The idea with `napi_call_threadsafe_function()` is that the secondary
thread first calls `napi_get_threadsafe_function_data()`, manipulates the
data at that pointer, then calls `napi_call_threadsafe_function()`. When
the callback gets called on the loop thread, it also calls
`napi_get_threadsafe_function_data()`, retrieves the data created by the
secondary thread, and calls into JS.
This way, each call into JS can be different from the other.
We did indeed discuss adding a queue to the implementation so as to ensure
a one-to-one ratio of calls on the secondary thread to calls on the loop
thread. I'd still rather leave the data management to the implementation,
though.
|
Sorry, something went wrong.
As you've pointed out later, this only works with one call at a time. In the case of having multiple async threads sending events in parallel, the data needs to be a queue otherwise you would be overriding each other. Or even one thread sending events in parallel to libuv event loop - resulting in a race condition.
The problem is that this pattern forces applications with the async event pattern into doing data management work that the API should be doing. The underlying issue seems to be that uv_async_send unfortunately doesn't allow you to pass additional void *. This is a major oversight in libuv in my opinion. For any kind of callback user should be able to pass their own data with each invocation. Without this support, user would be forced into using a queue to compensate for the missing functionality. Ideally uv_async_send should really support sending void * with the handle. N-api should just be consuming it. |
Sorry, something went wrong.
|
It looks like a queue of void* pointers as well as a one-to-one ratio of secondary-thread-to-loop-thread calls is needed. This means that:
|
Sorry, something went wrong.
|
Yes, having that would be super awesome. Thanks! |
Sorry, something went wrong.
|
@yizhang82 @simon-p-r how does the newest incarnation look? I've added the queue, the max_queue_size, and blocking and non-blocking addition. |
Sorry, something went wrong.
There was a problem hiding this comment.
Nit: Can you leave a blank line after each heading?
Sorry, something went wrong.
There was a problem hiding this comment.
Can we use the helpers from node_mutex.h for these things? They really improve readability for C++, all these gotos are really hard to wrap your head around…
Sorry, something went wrong.
There was a problem hiding this comment.
Those abort if the mutex is not created successfully. N-API returns napi_generic_failure.
Sorry, something went wrong.
|
@addaleax I have replaced the gotos with concentric if-statements. |
Sorry, something went wrong.
There was a problem hiding this comment.
What will happen if the queue is not empty and one calls delete?
Sorry, something went wrong.
There was a problem hiding this comment.
Then the queue will be deleted shallowly and there will be no further calls into JS. At this point, the secondary threads should already be stopped, if the napi_threadsafe_function was used correctly.
Sorry, something went wrong.
There was a problem hiding this comment.
Will this result in a crash when there are pending items? Taking the mutex might help.
Sorry, something went wrong.
There was a problem hiding this comment.
It probably makes sense to wait for the queue to drain:
Sorry, something went wrong.
There was a problem hiding this comment.
This shouldn't result in a crash if the napi_threadsafe_function is used as documented - that is, the threads are terminated before napi_delete_threadsafe_function() is called. For that same reason I need not lock the mutex. napi_delete_threadsafe_function() must always be called from the loop thread, so it's synchronous wrt. the idle callback and async callback. So, after napi_delete_threadsafe_function() completes libuv guarantees that there will be no more calls to the idle handler which would access the queue nor to the async callback which would set up an idle handler.
The items in the queue would indeed leak in such a case.
I don't think it's necessarily a good idea to wait for the queue to drain. I think being able to abort is a good thing - perhaps if there's a fatal error somewhere. In normal usage, the user would be aware of the consequences of deleting the thread-safe function.
So, I think we should either document that it's the user's responsibility to avoid leaks perhaps by making the last item on the queue be a terminator, or provide a delete-once-empty API in addition to napi_delete_threadsafe_function(), like napi_delete_threadsafe_function_deferred().
Sorry, something went wrong.
There was a problem hiding this comment.
I might be missing it - is there a place ts_fn gets deleted?
Sorry, something went wrong.
There was a problem hiding this comment.
ts_fn == handle in the innermost lambda. @bnoordhuis advised me to stop making the assumption throughout the code that &ts_fn->async == ts_fn, but I don't believe I can avoid making that assumption when deleting the handle, because all I get from libuv is a uv_handle_t *handle for the innermost lambda.
Sorry, something went wrong.
There was a problem hiding this comment.
Actually, I found a way of removing the assumption.
Sorry, something went wrong.
Sorry, something went wrong.
Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent<v8::Function>` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue<void*>` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: nodejs/help#1035 Re: #20964 Fixes: #13512 PR-URL: #17887 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent<v8::Function>` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue<void*>` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: nodejs/help#1035 Re: #20964 Fixes: #13512 PR-URL: #17887 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
private field 'async_context' is not used [-Wunused-private-field] PR-URL: nodejs#21597 Refs: nodejs#17887 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Notable changes:
* build:
* Node.js should now be about 60% faster to startup than the previous version,
thanks to the use V8's code cache feature for core modules. [#21405](#21405)
* dns:
* An experimental promisified version of the dns module is now available. Give
it a try with `require('dns').promises`. [#21264](#21264)
* fs:
* `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
* `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
specification ([reference](tc39/ecma262#1220)).
Since Node.js now has experimental support for worker threads, we are being
proactive and added a `notify` alias, while emitting a warning if
`wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
* Add API for asynchronous functions. [#17887](#17887)
* util:
* `util.inspect` is now able to return a result instead of throwing when the
maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
* Add `script.createCachedData()`. This API replaces the `produceCachedData`
option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
* Support for relative paths has been added to the `Worker` constructor. Paths
are interpreted relative to the current working directory. [#21407](#21407)
PR-URL: #21629
Notable changes:
* dns:
* An experimental promisified version of the dns module is now available. Give
it a try with `require('dns').promises`. [#21264](#21264)
* fs:
* `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
* `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
specification ([reference](tc39/ecma262#1220)).
Since Node.js now has experimental support for worker threads, we are being
proactive and added a `notify` alias, while emitting a warning if
`wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
* Add API for asynchronous functions. [#17887](#17887)
* util:
* `util.inspect` is now able to return a result instead of throwing when the
maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
* Add `script.createCachedData()`. This API replaces the `produceCachedData`
option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
* Support for relative paths has been added to the `Worker` constructor. Paths
are interpreted relative to the current working directory. [#21407](#21407)
PR-URL: #21629
Notable changes:
* dns:
* An experimental promisified version of the dns module is now available. Give
it a try with `require('dns').promises`. [#21264](#21264)
* fs:
* `fs.lchown` has been undeprecated now that libuv supports it. [#21498](#21498)
* lib:
* `Atomics.wake` is being renamed to `Atomics.notify` in the ECMAScript
specification ([reference](tc39/ecma262#1220)).
Since Node.js now has experimental support for worker threads, we are being
proactive and added a `notify` alias, while emitting a warning if
`wake` is used. [#21413](#21413) [#21518](#21518)
* n-api:
* Add API for asynchronous functions. [#17887](#17887)
* util:
* `util.inspect` is now able to return a result instead of throwing when the
maximum call stack size is exceeded during inspection. [#20725](#20725)
* vm:
* Add `script.createCachedData()`. This API replaces the `produceCachedData`
option of the `Script` constructor that is now deprecated. [#20300](#20300)
* worker:
* Support for relative paths has been added to the `Worker` constructor. Paths
are interpreted relative to the current working directory. [#21407](#21407)
PR-URL: #21629
Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent<v8::Function>` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue<void*>` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: nodejs/help#1035 Re: nodejs#20964 Fixes: nodejs#13512 PR-URL: nodejs#17887 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
private field 'async_context' is not used [-Wunused-private-field] PR-URL: nodejs#21597 Refs: nodejs#17887 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent<v8::Function>` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue<void*>` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: nodejs/help#1035 Re: #20964 Fixes: #13512 Backport-PR-URL: #25002 PR-URL: #17887 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent<v8::Function>` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue<void*>` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: nodejs/help#1035 Re: #20964 Fixes: #13512 Backport-PR-URL: #25002 PR-URL: #17887 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
| Back | FazBrowse Home | New Git URL |
Bundle a uv_async_t and a napi_ref to make it possible to call into
JS from another thread. The API accepts a void data pointer and a
callback which will be invoked on the loop thread and which will
receive the napi_value resulting from the napi_ref so as to perform
the call into JS. The callback is run inside a node::CallbackScope.
Fixes: #13512
Checklist
Affected core subsystem(s)
n-api
This is an alternative, and perhaps simpler implementation of napi_threadsafe_function, alternative to #17809