-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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: stabilize std::task
and std::future::Future
#2592
Conversation
cc @rust-lang/lang -- I haven't tagged this as T-lang, since the Lang Team already approved the async/await RFC and this is "just" about library APIs. But of course y'all should feel free to weigh in. cc @Nemo157 @MajorBreakfast @tinaun @carllerche @seanmonstar @olix0r |
@cramertj can you comment on |
cc @rust-lang/libs, please take a look! |
@cramertj I only briefly mentioned Fuchsia in the RFC, but it might be helpful for you/your team to leave some commentary here about your experience with the various iterations of the futures APIs. |
Although this isn't strictly relevant to the technical merits of the proposed APIs, considering the sheer scope and history of what we're talking about adding to std it seems worth asking: Are there any blog posts discussing Fuchsia's experience in more detail? This is the only part of the historical context I was completely unaware of, and I couldn't find any place that talks about it. EDIT: I swear I started typing this before aturon's last comment 😅 |
Thanks for putting this together. My experience with the proposed If the proposed Because of this, my plan for Tokio will be to stick on Also, most of Tokio would require the I understand the desire to drive progress forward. As I said, as far as I can tell, the proposed Edit: I should clarify, Tokio will add support for |
@carllerche You and I have talked about this a bunch on other channels, so I'll be repeating myself, but I want to write a response here so that everyone else is on the same page as well. There are indeed limitations with async/await today, due not so much to the feature itself as the lack of impl-trait-in-traits (or existential types) working sufficiently well (as well as, ultimately, GATs). They limit the ability to move foundational libraries to use async/await internally, and that's part of the reason we're not ready to stabilize the syntax itself yet. However, to be clear, none of these limitations connect to the The 0.1/0.3 compatibility system, which allows for fine-grained/incremental migration, ends up doing a lot to lower the stakes. For example, it's already fairly painless to write code for hyper using I think everything else you raise is discussed in the RFC as well, so I don't have more to add there! |
What’s the rationale for having both task and future modules? Since future only includes Future, it seems that having two modules doesn’t pull its weight. Are we expecting to move a bunch of future stuff into std in the future? |
@nrc I'm not sure but perhaps the |
If Future is a trait then why isn't this example in the RFC
written as
? |
A small bikeshed: I'm not sure I totally understand all the layers of |
text/0000-futures.md
Outdated
onto a single operating system thread. | ||
|
||
To perform this cooperative scheduling we use a technique sometimes referred to | ||
as a "trampoline". When a task would otherwise need to block waiting for some |
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.
Is this the same "trampoline" concept which is used in the context of avoiding stack overflows for recursive calls?
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.
Yep!
@ivandardi that's related to the async/await syntax rather than the library code provided here. The design as I understand it is that |
Yes, both modules are expected to grow substantially over time. The futures crate contains a similar module hierarchy with a much richer set of APIs. In addition, there will eventually be a |
text/0000-futures.md
Outdated
an API with greater flexibility for the cases where `Arc` is problematic. | ||
|
||
In general async values are not coupled to any particular executor, so we use trait | ||
objects to handle waking. These come in two forms: `Waker` for the general case, and |
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.
These don't look like trait objects to me.
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.
They're trait objects internally.
text/0000-futures.md
Outdated
Task execution always happens in the context of a `LocalWaker` that can be used to | ||
wake the task up locally, or converted into a `Waker` that can be sent to other threads. | ||
|
||
It's possible to construct a `Waker` using `From<Arc<dyn Wake>>`. |
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.
Not right now, because dyn Wake
is not Wake
.
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.
Should be Arc<impl Wake>
I suppose.
text/0000-futures.md
Outdated
/// - [`Poll::Ready(val)`] with the result `val` of this future if it | ||
/// finished successfully. | ||
/// | ||
/// Once a future has finished, clients should not `poll` it again. |
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.
No behavior is specified for when clients do do that. I think we should say something. For example, "implementors may panic".
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 we could even go a bit stronger than that, “implementors should panic, but clients may not rely on this”. All async fn
futures guarantee this, and I believe so do the current futures 0.3
adaptors.
I think it would also be good to mention that calling poll
again must not cause memory unsafety. The current mention that it can do anything at all makes it seem like it is allowed to have undefined behaviour, but since this is not an unsafe fn
the implementer cannot rely on the client’s behaviour for memory safety purposes.
text/0000-futures.md
Outdated
When a task returns `Poll::Ready`, the executor knows the task has completed and | ||
can be dropped. | ||
|
||
### Waking up |
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.
It's unclear at first glance which of the code blocks starting with trait Wake
/struct ExecutorInner
/struct Waker
are proposed for stabilization.
(Probably none of you know me, yet I'd still like to offer my opinion, if that is appropriate.) It is my understanding that the purpose of having an unstable API is that the ecosystem can experiment with it to ultimately avoid stabilizing a bad API. I don't see that this has happened here. Tokio has created a shim that essentially wraps an "std future" into a "0.1 future" with the only purpose of allowing async/await style futures. Apart from that, I haven't seen any experimentation with the std::future API. If tokio (as indicated above) is not even planning to use the new API instead of the old futures 0.1 API, then stabilizing it as-is will IMO be very bad for the ecosystem. The situation for std::task is worse: From what I can see, it hasn't been used at all. The tokio shim merely provides a noop waker to satisfy std::future's poll signature, but that waker cannot be used and even panics when you try to. I'd like to see any implementation that actually uses std::task - I've been following TWIR all year and haven't found anything. I cannot see that there are comprehensive examples in the docs for std::task, or any reference implementations that show how the system is meant to be used as a whole. My information is probably incomplete, so please tell me if I am missing anything. As a side note, I started implementing an "as simple as possible" task executor based on the std APIs, just to understand them and play with them. I found the std::task stuff really complicated, and quickly realized that it still couldn't do everything I needed - most importantly, I needed to access some of the internal data in my Wake implementation, but this was not possible with LocalWaker. I would have to resort to storing information in thread-locals again, which defies the purpose of having the waker passed as an argument to poll. |
Tokio is not the only user of futures, Fuchsia uses this API, including writing their own executor.
… On Nov 11, 2018, at 1:28 PM, brain0 ***@***.***> wrote:
(Probably none of you know me, yet I'd still like to offer my opinion, if that is appropriate.)
It is my understanding that the purpose of having an unstable API is that the ecosystem can experiment with it to ultimately avoid stabilizing a bad API. I don't see that this has happened here. Tokio has created a shim that essentially wraps an "std future" into a "0.1 future" with the only purpose of allowing async/await style futures. Apart from that, I haven't seen any experimentation with the std::future API. If tokio (as indicated above) is not even planning to use the new API instead of the old futures 0.1 API, then stabilizing it as-is will IMO be very bad for the ecosystem.
The situation for std::task is worse: From what I can see, it hasn't been used at all. The tokio shim merely provides a noop waker to satisfy std::future's poll signature, but that waker cannot be used and even panics when you try to. I'd like to see any implementation that actually uses std::task - I've been following TWIR all year and haven't found anything. I cannot see that there are comprehensive examples in the docs for std::task, or any reference implementations that show how the system is meant to be used as a whole.
My information is probably incomplete, so please tell me if I am missing anything.
As a side note, I started implementing an "as simple as possible" task executor based on the std APIs, just to understand them and play with them. I found the std::task stuff really complicated, and quickly realized that it still couldn't do everything I needed - most importantly, I needed to access some of the internal data in my Wake implementation, but this was not possible with LocalWaker. I would have to resort to storing information in thread-locals again, which defies the purpose of having the waker passed as an argument to poll.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Right, sorry, I must have overlooked that, it's even mentioned in the RFC. Is that stuff open source? I'd love to look at it. |
Someone on reddit found this: https://fuchsia.googlesource.com/garnet/+/master/public/rust/fuchsia-async/src/ (executor.rs is interesting, for example). |
@brain0 after the weekend, I expect that @cramertj (or others from the Fuchsia team) will write in with more extensive detail about their experiences. The RFC also went to some length to lay out the historical context. These APIs have seen plenty of use, both on top of Tokio/Hyper (through various shims) in e.g. web framework code, in embedded settings, and in custom operating systems (Fuchsia). Could you spell out your concern re: the task system? It'd be helpful to keep discussion focused on specifics if possible. |
@aturon First things first: Last weekend, I decided to try out the std::task and std::future system by implementing the simplest task executor I could think of, then combine that with mio. Of course, the result would not be as feature-rich or performant as tokio, but it would demonstrate the new APIs. There were lots of little details that felt "weird" about the task system:
Sorry for the rather verbose reply, I hope it still helped you understand my concerns. |
I was wondering if Waker should provide this as well. Doing so could potentially allow a strategy for hooking in the tokio reactor, etc: waker.downcast_ref::<tokio_reactor::Reactor>() Something like that might work instead of a set of thread-locals. (I haven't thought it through though). |
Update the future/task API This change updates the future and task API as discussed in the stabilization RFC at rust-lang/rfcs#2592. Changes: - Replacing UnsafeWake with RawWaker and RawWakerVtable - Removal of LocalWaker - Removal of Arc-based Wake trait
@carllerche That seems like an interesting idea, though I'm not sure exactly how it works e.g. when passing through a |
@cramertj Thinking more about how a downcasting strategy would work, it seems critical to me to not directly pass The reason being, when calling the future w/ the Tokio runtime context (reactor, timer, ...), this info can only stay on the stack. The handle that gets cloned to notify should not be the one that gets downcast. It would be very difficult to support this as well as confusing IMO. I suspect that the Tokio runtime context is not the only thing that should not be propagated when cloning the waker. I would strongly recommend switching back to |
Another example, if there is a task-local storage implementation, the necessary context would need to be passed in via the arg to Future::poll. It is also clear that task-local storage is task local and should not be sent to other tasks. The |
@carllerche Regarding downcasting/thread-locals/etc: If it's about propagating runtime characteristics into all futures, to e.g. enable a user to use call I am personally also an advocate of threading through these kinds of dependencies (in terms of Back to integrating reactor-specific behavior into Waker or Context: What would happen if the task is not polled from a tokio executor, but the user still calls the function which performs the downcast: The cast would obviously fail. In the same fashion as a TLS lookup would fail. Would tokio then implement a fallback, which e.g. references a global runtime instead of the current one? Or would it return an error? |
To clarify, I am not proposing to add any additional functionality in this RFC. I am showing how the current RFC is not forwards compatible with potential changes that could be good to make at a future time. For now, I would suggest bringing back a Context that only is able to provide a waker. |
I think @Matthias247 enumerated some good reasons why the poll parameter specifically shouldn't be a generic extension point, i.e. to avoid hidden dependencies. I don't think anything's changed since this was discussed in depth when the transition away from |
They seem like fair reasons for not wanting them. Though, Carl showed a couple reasons why it'd be impossible to add them if desired in the future. I didn't notice any real discussion about removing Context originally, other than the only thing it had was a Waker. Preparing forwards-compatible APIs is pretty important, especially in libstd where breaking changes are impossible. |
Yes, I'm not here to litigate whether or not X feature is good or not. We specifically don't want to do that now in order to ship. I am pointing out that the current design prohibits future changes and |
There has to be a line drawn somewhere against complicating interfaces just in case we some day change our minds about how they should work; otherwise, you can justify arbitrarily large amounts of indirection and abstraction. |
There hasn't been any action in nearly a month. What are the next steps to merging this into stable? |
Co-Authored-By: cramertj <cramertaylorj@gmail.com>
This RFC has been merged! The remaining unresolved items should be discussed on the tracking issue. |
woo!!! |
Relevant Rust RFC: <rust-lang/rfcs#2592>
Update the future/task API This change updates the future and task API as discussed in the stabilization RFC at rust-lang/rfcs#2592. Changes: - Replacing UnsafeWake with RawWaker and RawWakerVtable - Removal of LocalWaker - Removal of Arc-based Wake trait
This RFC proposes to stabilize the library component for the first-class
async
/await
syntax. In particular, it would stabilize:std
-level task system, i.e.std::task::*
.Future
API, i.e.core::future::Future
andstd::future::Future
.It does not propose to stabilize any of the
async
/await
syntax itself, which will be proposed in a separate step. It also does not cover stabilization of thePin
APIs, which has already been proposed elsewhere.This is a revised and significantly slimmed down version of the earlier futures RFC, which was postponed until more experience was gained on nightly.
Rendered
RFC status
The following need to be addressed prior to stabilization:
std::task
andstd::future::Future
#2592 (comment)std::task
andstd::future::Future
#2592 (comment)std::task
andstd::future::Future
#2592 (comment)