-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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 context_ext
#123392
Comments
…-context, r=Amanieu Wrap Context.ext in AssertUnwindSafe Fixes rust-lang#125193 Alternative to rust-lang#125377 Relevant to rust-lang#123392 I believe this approach is justifiable due to the fact that this function is unstable API and we have been considering trying to dispose of the notion of "unwind safety". Making a more long-term decision should be considered carefully as part of stabilizing `fn ext`, if ever. r? `@Amanieu`
Rollup merge of rust-lang#125392 - workingjubilee:unwind-a-problem-in-context, r=Amanieu Wrap Context.ext in AssertUnwindSafe Fixes rust-lang#125193 Alternative to rust-lang#125377 Relevant to rust-lang#123392 I believe this approach is justifiable due to the fact that this function is unstable API and we have been considering trying to dispose of the notion of "unwind safety". Making a more long-term decision should be considered carefully as part of stabilizing `fn ext`, if ever. r? `@Amanieu`
… r=Amanieu Wrap Context.ext in AssertUnwindSafe Fixes rust-lang/rust#125193 Alternative to rust-lang/rust#125377 Relevant to rust-lang/rust#123392 I believe this approach is justifiable due to the fact that this function is unstable API and we have been considering trying to dispose of the notion of "unwind safety". Making a more long-term decision should be considered carefully as part of stabilizing `fn ext`, if ever. r? `@Amanieu`
One of the concerns raised was about the soundness of trying to force the I believe this may be theoretically achievable by requiring any references contained within the // SAFETY: only implement on dyn types that don't expose
// lifetimes held by the type. i.e. traits with methods
// that only work with argument lifetimes. this allows
// us to safely cast any held lifetimes, because there
// is no way to move-in, move-out, or obtain a reference
// to data with any such held lifetimes
pub unsafe trait DynWithStaticInterface {
type Static: ?Sized + 'static;
type Clamped<'a>: ?Sized
where
Self: 'a;
// return type is safe to use because it does not expose
// whatever lifetimes were converted to 'static
fn to_static(&mut self) -> &mut Self::Static;
// return type is safe to use because it does not expose
// whatever lifetimes were converted to 'a
fn to_clamped<'a>(&'a mut self) -> &'a mut Self::Clamped<'a>;
} Since we are working with dyn references, we need the trait to be object-safe. This means we cannot allow casting to arbitrary lifetimes. Fortunately, we only ever need to cast to static or to the lifetime of self (which we here refer to as a "clamped" lifetime), and so we can provide methods for exactly those. Implementing on a dyn type could look like this: use std::mem;
trait StringRef {
fn get(&mut self) -> &mut String;
}
// SAFETY: implementing on a dyn type that does not expose any lifetimes held by the type
unsafe impl DynWithStaticInterface for (dyn StringRef + '_) {
type Static = dyn StringRef + 'static;
type Clamped<'a> = dyn StringRef + 'a;
fn to_static(&mut self) -> &mut Self::Static {
unsafe { mem::transmute(self) }
}
fn to_clamped<'a>(&'a mut self) -> &'a mut Self::Clamped<'a> {
unsafe { mem::transmute(self) }
}
}
struct StringSource<'a> {
s: &'a mut String,
}
impl<'a> StringSource<'a> {
fn new(s: &'a mut String) -> Self {
Self { s }
}
}
impl<'a> StringRef for StringSource<'a> {
fn get(&mut self) -> &mut String {
self.s
}
} Above we have a non-static concrete type holding data with lifetime The #![feature(local_waker)]
#![feature(context_ext)]
struct ContainerPrivate<T: ?Sized>(*mut T);
pub struct Container<'a, T: ?Sized, U: ?Sized> {
p: ContainerPrivate<T>,
_marker: PhantomData<&'a mut U>,
}
impl<'a, T: DynWithStaticInterface + ?Sized> Container<'a, T::Static, T> {
pub fn new(r: &'a mut T) -> Self {
Self {
p: ContainerPrivate(r.to_static()),
_marker: PhantomData,
}
}
}
impl<T: ?Sized + 'static, U: ?Sized> Container<'_, T, U> {
pub fn as_any<'a>(&'a mut self) -> &'a mut (dyn Any + 'static) {
&mut self.p
}
}
pub fn ctx_downcast<'a, T: DynWithStaticInterface + ?Sized + 'static>(
cx: &mut Context<'a>,
) -> Option<&'a mut T::Clamped<'a>> {
let a = cx.ext();
let p = match a.downcast_mut::<ContainerPrivate<T>>() {
Some(p) => p,
None => return None,
};
// SAFETY: the inner pointer is guaranteed to be valid because
// p has the same lifetime as the reference that was
// originally casted to the pointer
let r: &'a mut T = unsafe { p.0.as_mut().unwrap() };
Some(r.to_clamped())
} With the above API, it becomes possible for the Context extension to carry non-static data: #![feature(noop_waker)]
use std::task::{ContextBuilder, Waker};
let mut data = String::from("hello");
{
let mut s = StringSource::new(&mut data);
let mut c = Container::new(&mut s as &mut dyn StringRef);
let mut cx = ContextBuilder::from_waker(&Waker::noop())
.ext(c.as_any())
.build();
let r = ctx_downcast::<dyn StringRef>(&mut cx).unwrap();
r.get().push_str(" world");
}
assert_eq!(&data, "hello world"); This is a bit more complicated than I would have liked, but I can't think of a better way to do it, and I can't think of a better data structure than |
I should note that passing Single owner: let mut data = String::from("hello");
{
let mut cx = ContextBuilder::from_waker(&Waker::noop())
.ext(&mut data)
.build();
let data = cx.ext().downcast_mut::<String>().unwrap();
data.push_str(" world");
}
assert_eq!(&data, "hello world"); With let mut data = Rc::new(RefCell::new(String::from("hello")));
{
let mut cx = ContextBuilder::from_waker(&Waker::noop())
.ext(&mut data)
.build();
let data = cx.ext().downcast_ref::<Rc<RefCell<String>>>().unwrap();
data.borrow_mut().push_str(" world");
}
assert_eq!(&*data.borrow(), "hello world"); I would guess many executors could reasonably use something like Passing non-static references in extension data may be more interesting for combinators or nested executors, i.e. things that may want to avoid allocations and can't easily pre-allocate. |
I would like to move this towards stabilization. The goal of this feature is to enable experimentation of Using this feature, I've successfully implemented my own extension, To respond to the unresolved questions:
I believe the approach I described earlier is sound. Would love feedback.
Specifically, @joshtriplett earlier wrote:
It's one field. Is this saying we should not allow
After trying to shoehorn non-static references into it, I can agree with this statement. It kind of is what it is though.
True and of course not what is being proposed.
It's an optional feature with no cost if not used, other than increasing the size of I see a future where there are many extensions that executors and futures can opt-in to, that are best negotiated via the
Unless there's more to it, I assume this a weighing between using thread-local/global storage vs slightly increasing the size of
Does anyone know how meaningful this is? |
@rustbot labels +AsyncAwait-Triaged We discussed this in the WG-async meeting today. We have enough reservations about this approach that we'd like to explore two main other approaches to solving this problem first. One alternative is the The idea there would be to broaden the scope of that producer/consumer API from just The other approach is making For this one, in particular, we'd need to do a lot of feasibility and migration analysis. In general, we're interested in looking into and comparing these approaches. We find the problem that the context extension is trying to solve highly motivating, but we want to be sure to find the right solution. |
Feature gate:
#![feature(context_ext)]
This is a tracking issue for allowing
std::task::Context
to carry arbitrary extension data.Public API
Steps / History
Context::ext
#123203Unresolved Questions
(dyn Any + 'static)
may contain interior mutability #125193 (the issue is closed but there still is a FIXME in the code)Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: