-
Notifications
You must be signed in to change notification settings - Fork 9
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
Threadsafe Handles #32
Conversation
44da5af
to
fdfa972
Compare
text/0000-thread-safe-handles.md
Outdated
fn log(mut cx: FunctionContext) -> JsResult<JsUndefined> { | ||
let persistent_cb = cx.argument::<JsFunction>(0)?.persistent(&mut cx); | ||
|
||
// `Persistent` are reference counted and clone-able. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
|
||
```rust | ||
fn thread_callback(mut cx: FunctionContext) -> JsResult<JsUndefined> { | ||
let cb = cx.argument::<JsFunction>(0)?.persistent(&mut cx); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
- ~A global `EventQueue` is necessary for dropping `Persistent`. Should this be exposed?~ No. This is no longer required for the design. | ||
- ~The global `EventQueue` requires instance data. Are we okay using an [experimental](https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis) API?~ This is no longer required for the design. | ||
- Should `EventQueue::send` accept a closure that returns `()` instead of `NeonResult<()>`? In most cases, the user will want to allow `Throw` to become an `uncaughtException` instead of a `panic` and `NeonResult<()>` provides a small ergonomics improvement. | ||
- `persistent(&mut cx)` is a little difficult to type. Should it have a less complicated name? |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
|
||
Two new `Send` and `Sync` features are introduced: `EventQueue: Send + Sync` and `Persistent: Send`. These features work together to allow multi-threaded modules to interact with the JavaScript main thread. | ||
|
||
* `EventQueue`: Threadsafe handle that allows sending closures to be executed on the main thread. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
Two new `Send` and `Sync` features are introduced: `EventQueue: Send + Sync` and `Persistent: Send`. These features work together to allow multi-threaded modules to interact with the JavaScript main thread. | ||
|
||
* `EventQueue`: Threadsafe handle that allows sending closures to be executed on the main thread. | ||
* `Persistent<T>`: Opaque handle that can be sent across threads, with inner contents only accessible on the main thread. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
let cb = cx.argument::<JsFunction>(0)?.persistent(&mut cx); | ||
|
||
std::thread::spawn(move || { | ||
// Panics when `cb` is dropped. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
} | ||
``` | ||
|
||
While `Persistent<_>` may be shared across threads, the inner contents can only be accessed on the main thread. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
|
||
### `neon::handle::EventQueue` | ||
|
||
Once a value is wrapped in a `Persistent<_>` handle, it must be sent back to the main JavaScript thread to unwrap. `EventQueue` provides a mechanism for requesting work be peformed on the main thread. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
```rust | ||
fn thread_callback(mut cx: FunctionContext) -> JsResult<JsUndefined> { | ||
let cb = cx.argument::<JsFunction>(0)?.persistent(&mut cx); | ||
let queue = EventQueue::new(&mut cx)?; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
} | ||
``` | ||
|
||
`Persistent<_>` are clone-able and can be used many times. `EventQueue` are `Sync` and may be shared across threads. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
|
||
```rust | ||
fn thread_callback(mut cx: FunctionContext) -> JsResult<JsUndefined> { | ||
let cb = cx.argument::<JsFunction>(0)?.persistent(&mut cx); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
let queue = Arc::new(EventQueue::new(&mut cx)?); | ||
|
||
let handles = (1..=10).into_iter().map(|i| { | ||
let queue = Arc::clone(queue); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
|
||
Instances of `EventQueue` will keep the event loop running and prevent the process from exiting. The `EventQueue::unref` method is provided to change this behavior and allow the process to exit while an instance of `EventQueue` still exists. | ||
|
||
However, calls to `EventQueue::schedule` _might_ not execute before the process exits. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
text/0000-thread-safe-handles.md
Outdated
The native `napi_call_threadsafe_function` can fail in three ways: | ||
|
||
* `napi_queue_full` if the queue is full | ||
* `napi_invalid_arg` if the thread count is zero.ß This is due to misuse and statically enforced by ownership rules and the `Drop` trait. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
||
Neon already has `Task`, `EventHandler`, a [proposed](https://github.com/neon-bindings/rfcs/pull/30) `TaskBuilder` and an accepted, but currently unimplemented, [update](https://github.com/neon-bindings/rfcs/pull/28) to `EventHandler`. This is a large amount of API surface area without clear indication of what a user should use. | ||
|
||
This can be mitigated with documentation and soft deprecation of existing methods as we get a clearer picture of what a high-level, ergonomic API would look like. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Finished my review! This is going to be a great addition to the core primitives of Neon. I'm psyched. |
text/0000-thread-safe-handles.md
Outdated
|
||
/// Prevent the Node event loop from existing while this `EventQueue` exists. (Default) | ||
/// _Idempotent_ | ||
pub fn reference(&mut self); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
||
Both `ref` and `unref` mutate the behavior of the underlying threadsafe function and require exclusive references. These methods are infallible because Rust is capable of statically enforcing the invariants. We may want to optimize with `assert_debug!` on the `napi_status`. | ||
|
||
#### Future Expansion |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
be6c27a
to
81f2bab
Compare
81f2bab
to
7fcc8af
Compare
feat: Implements Threadsafe Handles as specified by neon-bindings/rfcs#32 Requires the `event-queue-api` feature flag until the RFC is accepted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's just a couple things out of date now, but otherwise I think this is probably ready for Final Comment Period.
@dherman This is close to stabilization. One change I would like to make prior to merging is the addition of a return value. Two APIs to take inspiration from: In both of these APIs, a I propose the following changes: struct Channel
pub fn send<T, F>(&self, f: F) -> JoinHandle<T>
where
T: Send + 'static,
F: FnOnce(TaskContext) -> NeonResult<T>; We don't need to initially do anything with the returned Also, I'm still not feeling certain |
This is a great idea @kjvalencik. I think I do like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JoinHandle
changes look good!
Merged in c639681 |
Two new
Send + Sync
features are introduced:EventQueue
andPersistent
. These features work together to allow multi-threaded modules to interact with the Javascript main thread.EventQueue
: Threadsafe handle that allows sending closures to be executed on the main thread.Persistent<T>
: Opaque handle that can be sent across threads, but only dereferenced on the main thread.