-
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
RFC: TaskBuilder #30
RFC: TaskBuilder #30
Conversation
76bb37f
to
a51dd8d
Compare
For me, this is more straightforward in terms of "what must I implement?" for a new user, yet terser. |
@qm3ster Thanks for taking a look! I don't think there will be issues with lifetimes in the API itself, but, I do think there's potential to make issues with lifetimes in the user's code more difficult to read. Error messages on closures can be pretty gnarly, especially when inference failed. For example, I could see a new user failing to It may be even less obvious because trivial examples (like mine above) will only use types that are |
I was accidentally considering someone new to Neon but experienced with Rust closures. |
That's an excellent point. New users are likely to copy paste an example. As long as all of our examples include If you use Thanks for the thoughtful response! |
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.
This RFC looks awesome. I think it's going to be a massive improvement to the Task
API ergonomics, and a big selling point for Neon. Imagine how impressive it will be to show off how trivially you can execute lightning-fast Rust computations on a background thread!
There were a few details I didn't fully understand, so I made a few suggestions for clarifications.
text/0000-task-builder.md
Outdated
Instead of introducing a breaking change, a new API is introduced with an _owned_ `self`. The following addtional changes are made: | ||
|
||
* The new API relies on closures instead of a `trait`; thanks to many improvements in the borrow checker and closure ergonomics | ||
* The new API returns a single `Output` generic rather than separate `Output` and `Error` types. This simplifies infallible tasks. |
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.
Mention that it's still more expressive, e.g.: "This simplifies infallible tasks and is still expressive enough to allow users to use a Result
type to represent fallible tasks."
text/0000-task-builder.md
Outdated
|
||
An implementation of `BaseTask` is added for `T: Task` to provide backwards compatibility to existing implementations of `Task`. Since `complete` is an associated method, `self` is threaded through the `Output` parameter. | ||
|
||
The existing `Task` trait will be _deprecated_. |
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.
Let's mention why (it's less convenient and less powerful) and also that we won't remove it until Neon users have had enough time to migrate to the new API.
|
||
### `TaskBuilder` | ||
|
||
The `TaskBuilder` struct contains two pieces of key data: |
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.
Can you include the full TaskBuilder
type declaration and the signatures of the methods in the RFC?
|
||
The closure is expected to return _another closure_. This higher-order method will be familiar to many Javascript developers. The returned closure is the `complete` method and will be performed back on the main Javascript thread. In the `complete` closure, the user may convert Rust types back to Javascript types. | ||
|
||
Finally, a `schedule` method is provided to submit the task to the queue. For conveinance, it returns `JsUndefined`, matching Javascript semantics for most asynchronous methods. |
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.
I don't think I understand this. The schedule
method returns Handle<JsUndefined>
? Or the JS callback is wrapped to discard its result and return undefined? Or something else? It might be helpful to include the type signature.
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.
I'll write more examples. schedule
returns undefined
to remove a little more boilerplate of creating that value since that's the most common pattern.
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.
pub fn perform_closure_task(mut cx: FunctionContext) -> JsResult<JsValue> {
let n = cx.argument::<JsNumber>(0)?.value();
let cb = cx.argument::<JsFunction>(1)?;
cx.task(move || {
let result = n + 1.0;
move |mut cx| Ok(cx.number(result))
})
.schedule(cb)
}
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.
I need to update the RFC, but, I made it return a JsUndefined
upcast to JsValue
. This makes it fit a few more situations. For example, class methods always return JsValue
.
text/0000-task-builder.md
Outdated
|
||
#### Limitation | ||
|
||
Ideally we would have a more simple struct type and only call out the specific trait bounds on `fn schedule`. |
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.
I think this section would be easier to understand if we had the real type declaration that this RFC is proposing documented above, to compare and contrast with.
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.
I'm going to try a little bit more to fix this issue before resigning to the more limited version.
@qm3ster I'm curious about this comment:
What is missing from the RFC that doesn't solve those problems? I understood this to be strictly more powerful than an |
@dherman Ah, I just meant that it is more flexible than that, but also would still be boilerplate and readability improvement even if it gave only an immutable reference. |
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
The `Context` trait provides a `.task()` method for performing asynchronous tasks on a background thread and calling back to javascript on completion. |
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.
We discussed this with the core team yesterday: I think it's important for the name of this method to indicate that it's being scheduled in Node's core thread pool, so it's clear this isn't just an arbitrary thread. In particular this helps people understand that it's competing with Node's I/O threads for processing time.
@goto-bus-stop proposed the name .node_task()
, which I like. I don't think we want to expose libuv in the name, because that's too low-level. And it's not necessarily critical to be explicit about the pool itself, which is also a bit of a low-level detail. But the point is this is a task that's part of Node's internal task scheduler. Also the word "task" does seem to be consistent with the language used by the relevant N-API APIs' documentation (the APIs themselves use the term "work" which is a little awkward here, but the prose underneath uses the word "task"), so it seems reasonably idiomatic for the Node platform.
A thought from a call with @kjvalencik today: can we refactor this API to look a little closer to the threadsafe handles API, for a more consistent overall design? |
Superseded by #35 |
The
Task
API was introduced in Neonv0.4.0
and has proven to be incredibly valuable to developers. It empowers users to execute long running tasks on the libuv thread pool without blocking the main javascript thread.The
TaskBuilder
API expands upon this concept with an improved ownership model and ergonomics.Rendered
Proof of concept implementation