-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Tracking Issue for LocalWaker #118959
Comments
…ulacrum Add LocalWaker and ContextBuilder types to core, and LocalWake trait to alloc. Implementation for rust-lang#118959.
…ulacrum Add LocalWaker and ContextBuilder types to core, and LocalWake trait to alloc. Implementation for rust-lang#118959.
…ulacrum Add LocalWaker and ContextBuilder types to core, and LocalWake trait to alloc. Implementation for rust-lang#118959.
Rollup merge of rust-lang#118960 - tvallotton:local_waker, r=Mark-Simulacrum Add LocalWaker and ContextBuilder types to core, and LocalWake trait to alloc. Implementation for rust-lang#118959.
@zzwxh
I agree, it would be best if users could panic on the call to
The feature used to work exactly like this, offering a |
I'm not sure you know what you're saying here. "Just" deprecating a crucial API for all |
@zzwxh
Yes, this is what I said.
No, |
@zzwxh it's not ok that you deleted your comment. It's hard to read back a discussion that way. |
…ulacrum Add LocalWaker and ContextBuilder types to core, and LocalWake trait to alloc. Implementation for rust-lang#118959.
Add `Context::ext` This change enables `Context` to carry arbitrary extension data via a single `&mut dyn Any` field. ```rust #![feature(context_ext)] impl Context { fn ext(&mut self) -> &mut dyn Any; } impl ContextBuilder { fn ext(self, data: &'a mut dyn Any) -> Self; fn from(cx: &'a mut Context<'_>) -> Self; fn waker(self, waker: &'a Waker) -> Self; } ``` Basic usage: ```rust struct MyExtensionData { executor_name: String, } let mut ext = MyExtensionData { executor_name: "foo".to_string(), }; let mut cx = ContextBuilder::from_waker(&waker).ext(&mut ext).build(); if let Some(ext) = cx.ext().downcast_mut::<MyExtensionData>() { println!("{}", ext.executor_name); } ``` Currently, `Context` only carries a `Waker`, but there is interest in having it carry other kinds of data. Examples include [LocalWaker](rust-lang#118959), [a reactor interface](rust-lang/libs-team#347), and [multiple arbitrary values by type](https://docs.rs/context-rs/latest/context_rs/). There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared via `Context`, if it were possible. The `ext` field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data. Dedicated fields for specific kinds of data could still be added directly on `Context` when we have sufficient experience or understanding about the problem they are solving, such as with `LocalWaker`. The `ext` field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven. Both the provider and consumer of the extension data must be aware of the concrete type behind the `Any`. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures. ## Passing interfaces Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to `Any`. However, one gotcha is `Any` cannot contain non-static references. This means one cannot simply do: ```rust struct Extensions<'a> { interface1: &'a mut dyn Trait1, interface2: &'a mut dyn Trait2, } let mut ext = Extensions { interface1: &mut impl1, interface2: &mut impl2, }; let ext: &mut dyn Any = &mut ext; ``` To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example: ```rust pub struct Extensions { interface1: *mut dyn Trait1, interface2: *mut dyn Trait2, } impl Extensions { pub fn new<'a>( interface1: &'a mut (dyn Trait1 + 'static), interface2: &'a mut (dyn Trait2 + 'static), scratch: &'a mut MaybeUninit<Self>, ) -> &'a mut Self { scratch.write(Self { interface1, interface2, }) } pub fn interface1(&mut self) -> &mut dyn Trait1 { unsafe { self.interface1.as_mut().unwrap() } } pub fn interface2(&mut self) -> &mut dyn Trait2 { unsafe { self.interface2.as_mut().unwrap() } } } let mut scratch = MaybeUninit::uninit(); let ext: &mut Extensions = Extensions::new(&mut impl1, &mut impl2, &mut scratch); // ext can now be casted to `&mut dyn Any` and back, and used safely let ext: &mut dyn Any = ext; ``` ## Context inheritance Sometimes when futures poll other futures they want to provide their own `Waker` which requires creating their own `Context`. Unfortunately, polling sub-futures with a fresh `Context` means any properties on the original `Context` won't get propagated along to the sub-futures. To help with this, some additional methods are added to `ContextBuilder`. Here's how to derive a new `Context` from another, overriding only the `Waker`: ```rust let mut cx = ContextBuilder::from(parent_cx).waker(&new_waker).build(); ```
Rollup merge of rust-lang#123203 - jkarneges:context-ext, r=Amanieu Add `Context::ext` This change enables `Context` to carry arbitrary extension data via a single `&mut dyn Any` field. ```rust #![feature(context_ext)] impl Context { fn ext(&mut self) -> &mut dyn Any; } impl ContextBuilder { fn ext(self, data: &'a mut dyn Any) -> Self; fn from(cx: &'a mut Context<'_>) -> Self; fn waker(self, waker: &'a Waker) -> Self; } ``` Basic usage: ```rust struct MyExtensionData { executor_name: String, } let mut ext = MyExtensionData { executor_name: "foo".to_string(), }; let mut cx = ContextBuilder::from_waker(&waker).ext(&mut ext).build(); if let Some(ext) = cx.ext().downcast_mut::<MyExtensionData>() { println!("{}", ext.executor_name); } ``` Currently, `Context` only carries a `Waker`, but there is interest in having it carry other kinds of data. Examples include [LocalWaker](rust-lang#118959), [a reactor interface](rust-lang/libs-team#347), and [multiple arbitrary values by type](https://docs.rs/context-rs/latest/context_rs/). There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared via `Context`, if it were possible. The `ext` field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data. Dedicated fields for specific kinds of data could still be added directly on `Context` when we have sufficient experience or understanding about the problem they are solving, such as with `LocalWaker`. The `ext` field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven. Both the provider and consumer of the extension data must be aware of the concrete type behind the `Any`. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures. ## Passing interfaces Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to `Any`. However, one gotcha is `Any` cannot contain non-static references. This means one cannot simply do: ```rust struct Extensions<'a> { interface1: &'a mut dyn Trait1, interface2: &'a mut dyn Trait2, } let mut ext = Extensions { interface1: &mut impl1, interface2: &mut impl2, }; let ext: &mut dyn Any = &mut ext; ``` To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example: ```rust pub struct Extensions { interface1: *mut dyn Trait1, interface2: *mut dyn Trait2, } impl Extensions { pub fn new<'a>( interface1: &'a mut (dyn Trait1 + 'static), interface2: &'a mut (dyn Trait2 + 'static), scratch: &'a mut MaybeUninit<Self>, ) -> &'a mut Self { scratch.write(Self { interface1, interface2, }) } pub fn interface1(&mut self) -> &mut dyn Trait1 { unsafe { self.interface1.as_mut().unwrap() } } pub fn interface2(&mut self) -> &mut dyn Trait2 { unsafe { self.interface2.as_mut().unwrap() } } } let mut scratch = MaybeUninit::uninit(); let ext: &mut Extensions = Extensions::new(&mut impl1, &mut impl2, &mut scratch); // ext can now be casted to `&mut dyn Any` and back, and used safely let ext: &mut dyn Any = ext; ``` ## Context inheritance Sometimes when futures poll other futures they want to provide their own `Waker` which requires creating their own `Context`. Unfortunately, polling sub-futures with a fresh `Context` means any properties on the original `Context` won't get propagated along to the sub-futures. To help with this, some additional methods are added to `ContextBuilder`. Here's how to derive a new `Context` from another, overriding only the `Waker`: ```rust let mut cx = ContextBuilder::from(parent_cx).waker(&new_waker).build(); ```
Add `Context::ext` This change enables `Context` to carry arbitrary extension data via a single `&mut dyn Any` field. ```rust #![feature(context_ext)] impl Context { fn ext(&mut self) -> &mut dyn Any; } impl ContextBuilder { fn ext(self, data: &'a mut dyn Any) -> Self; fn from(cx: &'a mut Context<'_>) -> Self; fn waker(self, waker: &'a Waker) -> Self; } ``` Basic usage: ```rust struct MyExtensionData { executor_name: String, } let mut ext = MyExtensionData { executor_name: "foo".to_string(), }; let mut cx = ContextBuilder::from_waker(&waker).ext(&mut ext).build(); if let Some(ext) = cx.ext().downcast_mut::<MyExtensionData>() { println!("{}", ext.executor_name); } ``` Currently, `Context` only carries a `Waker`, but there is interest in having it carry other kinds of data. Examples include [LocalWaker](rust-lang/rust#118959), [a reactor interface](rust-lang/libs-team#347), and [multiple arbitrary values by type](https://docs.rs/context-rs/latest/context_rs/). There is also a general practice in the ecosystem of sharing data between executors and futures via thread-locals or globals that would arguably be better shared via `Context`, if it were possible. The `ext` field would provide a low friction (to stabilization) solution to enable experimentation. It would enable experimenting with what kinds of data we want to carry as well as with what data structures we may want to use to carry such data. Dedicated fields for specific kinds of data could still be added directly on `Context` when we have sufficient experience or understanding about the problem they are solving, such as with `LocalWaker`. The `ext` field would be for data for which we don't have such experience or understanding, and that could be graduated to dedicated fields once proven. Both the provider and consumer of the extension data must be aware of the concrete type behind the `Any`. This means it is not possible for the field to carry an abstract interface. However, the field can carry a concrete type which in turn carries an interface. There are different ways one can imagine an interface-carrying concrete type to work, hence the benefit of being able to experiment with such data structures. ## Passing interfaces Interfaces can be placed in a concrete type, such as a struct, and then that type can be casted to `Any`. However, one gotcha is `Any` cannot contain non-static references. This means one cannot simply do: ```rust struct Extensions<'a> { interface1: &'a mut dyn Trait1, interface2: &'a mut dyn Trait2, } let mut ext = Extensions { interface1: &mut impl1, interface2: &mut impl2, }; let ext: &mut dyn Any = &mut ext; ``` To work around this without boxing, unsafe code can be used to create a safe projection using accessors. For example: ```rust pub struct Extensions { interface1: *mut dyn Trait1, interface2: *mut dyn Trait2, } impl Extensions { pub fn new<'a>( interface1: &'a mut (dyn Trait1 + 'static), interface2: &'a mut (dyn Trait2 + 'static), scratch: &'a mut MaybeUninit<Self>, ) -> &'a mut Self { scratch.write(Self { interface1, interface2, }) } pub fn interface1(&mut self) -> &mut dyn Trait1 { unsafe { self.interface1.as_mut().unwrap() } } pub fn interface2(&mut self) -> &mut dyn Trait2 { unsafe { self.interface2.as_mut().unwrap() } } } let mut scratch = MaybeUninit::uninit(); let ext: &mut Extensions = Extensions::new(&mut impl1, &mut impl2, &mut scratch); // ext can now be casted to `&mut dyn Any` and back, and used safely let ext: &mut dyn Any = ext; ``` ## Context inheritance Sometimes when futures poll other futures they want to provide their own `Waker` which requires creating their own `Context`. Unfortunately, polling sub-futures with a fresh `Context` means any properties on the original `Context` won't get propagated along to the sub-futures. To help with this, some additional methods are added to `ContextBuilder`. Here's how to derive a new `Context` from another, overriding only the `Waker`: ```rust let mut cx = ContextBuilder::from(parent_cx).waker(&new_waker).build(); ```
It would be nice to start work towards stabilisation for this feature as it currently exists (an opt-in performance optimisation) and move the discussion about optional wakers to its own issue. |
@raftario Do you know if there is any user of this feature in the current moment, or if there are crate authors waiting for this feature to be stabilized? I don't think stabilization can be justified without any users. |
While it's not an hard blocker for me at the time, I am really interested in this feature! I am working on a runtime for I am wrapping a However, I am not sure I understand how you would build a local-only |
I'm also personally interested in this as I've been working on a thread-per-core runtime where tasks never leave the thread that spawned them, and it would be much easier if I didn't have to worry about task wakers being sent to other threads, even when none of the futures the runtime itself provides need it. I can also imagine some of the existing thread-per-core runtimes could use this feature quite extensively for similar reasons. |
It’s not fine, because the implementor of the executor is not necessarily the implementor of all futures that need to obtain a waker; in particular, these types of futures will be broken:
In order for these situations to be detected instead of turning into lost wakeup bugs, you would have to write a Personally, I think that |
That's what I thought.. thanks for clarifying! I share your opinion, we shouldn't stabilise the feature as it stands today. That also means, we would require current users of I think putting Send and Sync constraints on |
I'm personally of the opposite opinion; I don't think this feature will ever have a path to stabilization as anything more than an opt-in optimisation. Let's start with the proposition to make Any future which can, at runtime, use either a On the other hand, a future that does need the The other option is to add a generic parameter to So unless I'm missing a secret third option this leaves us with the current API. Existing executors can either continue to provide only a New executors can choose to only provide a working |
I'm with @raftario here. The feature as it's stands can be used as a mere optimization, or as a replacement of waker. It is up to the crate author to decide how to use the feature, and both uses are valid. LocalWaker only executors will always be incompatible with the rest of the ecosystem, regardless of what kind of API we offer. So I don't think we should worry too much about that use case. |
This feature would be very useful for io_uring based runtimes that are strongly biased towards thread-per-core and benefit from Would be great to stabilise it sooner rather than later as a optimisation/replacement of a |
Driving by as the mythical user of this API, writing an executor which would use the API, writing a code comment why I'm doing something else, so that I have a link to link to. I mostly agree with @raftario 's view. IMO at this point it makes sense for "Rust generally" to pursue multithreaded async for wide compatibility (futures that are Send, Wakers that are Send/Sync, etc.) and to choose designs that basically don't break existing code. But it also makes sense for "minority Rust" to pursue a local-only flavor, and I think the lack of that as an option in the ecosystem has a real impact. By analogy, "Rust generally" is std, while "minority Rust" is written to no_std (or restricts its use of std to only certain topics). And the analogy goes pretty deep: no_std exists in a context where spawning another thread may not even be possible, meanwhile at the same time you are targeting low-power embedded hardware...
Personally, I come from the opposite presumption. If we consider a platform without threading, from this point of view it is the threadsafe abstractions which are not useful. Of course they are useful for "general Rust". However considering our (non-general) platform they solve a "problem" which is imaginary because we have no threads and no race conditions can ever occur. Meanwhile the cost of those abstractions, both in complexity for the programmer and at runtime, is not imaginary. Meanwhile we are also trying to code to low-performance hardware, and so optimization is less a parenthetical remark and more of a hard requirement of a systems language.
So, my main opinion is. Most of the real-world code I write has these properties:
Application A: when I implement a Future, there may be a fast implementation that assumes I'm executing on the right thread ( Application B: Meanwhile when I write an executor, there may be a fast way that assumes I'm on the right thread (
Something I have not seen discussed is that we have the same problem about which waker to use inside the stable Waker API, just with instances instead of types. That is: it might be fine to re-use an old So to my mind, the obvious short-term consequence of shipping this API is it will be adopted as part of a runtime check in exactly two places: 1) atomic-waker, where we encode the canonical runtime checks for Send futures. 2) Some other crate, let's call it And then in the long term, the two crates are effectively some kind of consensus has to how people actually write futures and the API they actually want, and then that consensus can be merged into the stdlib, raise tide for all boats, etc. Whereas at the moment, the only consensus that can be formed in stable Rust is along the lines of Waker and multithreaded executors. |
Feature gate:
#![feature(local_waker)]
This is a tracking issue for support for local wakers on
Context
. This allows libraries to hold non thread safe data on their wakers, guaranteeing at compile time that the wakers will not be sent across threads. It includes aContextBuilder
type for building contexts.Public API
Steps / History
Unresolved Questions
Relevant links
Sync
#66481Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: