-
Notifications
You must be signed in to change notification settings - Fork 650
[WIP] Explicit task::Context argument to poll. #744
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
Conversation
Echoing this in the right place this time: So I'm playing around converting the code base, and I have hit a roadblock with trying to implement Basically, this: let (res, future) = catch_unwind(|| (future.poll(), future))?; Becomes: let (res, future) = catch_unwind(|| (future.poll(ctx), future))?; And |
I'm working on |
After reading up this, if we can reason that let (res, future) = catch_unwind(panic::AssertUnwindSafe(|| (future.poll(ctx), future)))?; As to whether they are unwind safe seems to depend on how a specific NotifyHandle is implemented, and how task-local storage works (is it still platform dependent in 0.2?). It would be nice to make sure this is documented for NotifyHandle implementors, and make sure that the task-local APIs we provide guarantee unwind safety. |
My goal so far has been to make the 'interesting' commit as short as possible, so it's easier to discuss/review. It currently compiles, though the tests don't pass (well, more accurately, the tests have regressed compared to the penultimate commit. |
futures-executor/src/task_runner.rs
Outdated
@@ -298,6 +298,7 @@ impl Future for SpawnedFuture { | |||
type Error = bool; | |||
|
|||
fn poll(&mut self, ctx: &mut task::Context) -> Poll<bool, bool> { | |||
self.inner.with_task_data(|f| f.poll(ctx)) | |||
//self.inner.with_task_data(|f| f.poll(ctx)) | |||
self.inner.get_mut().poll(ctx) |
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 not sure I understand why this was implemented this way to begin with. It seems like it would be a noop, but I have no idea where the current task gets set.
@@ -45,7 +45,6 @@ impl<S> Stream for CatchUnwind<S> | |||
} | |||
CatchUnwindState::Stream(stream) => stream, | |||
}; | |||
// FIXME: fix passing the actual context here. | |||
let res = catch_unwind(|| (stream.poll(&mut task::Context::panicking()), stream)); |
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 needs more thought.
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.
As mentioned in this comment, unwind safety seems to have been deferred to implementors of NotifyHandle and the platform-specific task-local storage implementation. This also was the case for TLS-based contexts. So it seem ignoring this using panic::AssertUnwindSafe
is the way to go, and making sure panic safety is a documented requirement for NotifyHandle
, UnsafeNotify
, and task-local.
@@ -30,7 +30,6 @@ impl<F> Future for CatchUnwind<F> | |||
|
|||
fn poll(&mut self, _ctx: &mut task::Context) -> Poll<Self::Item, Self::Error> { | |||
let mut future = self.future.take().expect("cannot poll twice"); | |||
// FIXME: fix passing the actual context here. | |||
let (res, future) = catch_unwind(|| (future.poll(&mut task::Context::panicking()), future))?; |
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 needs more thought.
@@ -266,30 +199,16 @@ impl<T: ?Sized> Spawn<T> { | |||
self.enter(BorrowedUnpark::new(&mk, id), f) | |||
} | |||
|
|||
/// TODO: dox | |||
pub fn with_task_data<F, R>(&mut self, f: F) -> R |
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 RFC doesn't actually state what the changes to all of the API's should be, but this one doesn't seem useful anymore, given that the context explicitly contains task 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.
👍
futures-core/src/task_impl/mod.rs
Outdated
/// Returns a `Context` which will panic when `waker` is called. | ||
/// This is useful when calling `poll` on a `Future` which doesn't | ||
/// access the context. | ||
pub fn panicking() -> Context { | ||
Context { waker: None } | ||
pub fn panicking() -> Context<'a> { |
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 function seems questionable, but it did help migrate the tests for futures which don't access the context at all.
futures-core/src/task_impl/mod.rs
Outdated
/// the future as notifications arrive, until the future terminates. | ||
/// | ||
/// The poll function accepts a context, which is usually provided by the executor. | ||
pub struct Context<'a>(Option<ContextInner<'a>>); |
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 interaction between Spawn
and Context
make having Context
own the task-local data difficult, hence the lifetime.
futures-core/src/task_impl/mod.rs
Outdated
/// the future as notifications arrive, until the future terminates. | ||
/// | ||
/// The poll function accepts a context, which is usually provided by the executor. | ||
pub struct Context<'a>(Option<ContextInner<'a>>); |
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-op contexts seems to be supported by providing an empty LocalMap
plus a no-op NotifyHandle
(or collecting, for testing).
The use for Context(None)
seems to be to implement the panicking context. Could this be accomplished with an empty LocalMap
and panicking NotifyHandle
instead to avoid the Option
?
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.
Probably, though I don't think it's important to get it perfect in the first PR, since it's an implementation detail that can be changed locally at any time.
futures-channel/tests/mpsc.rs
Outdated
@@ -34,29 +34,29 @@ fn send_recv_no_buffer() { | |||
|
|||
// Run on a task context | |||
let f = lazy(move || { | |||
assert!(tx.flush().unwrap().is_ready()); | |||
assert!(tx.poll_ready().unwrap().is_ready()); | |||
assert!(tx.flush(&mut TaskContext).unwrap().is_ready()); |
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.
TaskContext
isn't the name of a variable in this scope-- is this just a WIP artifact?
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 is using the prelude (I don't really like preludes, but I figured I'd use it... maybe it would confuse someone and convince them preludes were bad? j/k).
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.
Thanks for the PR @ahmedcharles! Everything looks mostly good to me but my main worry is about how we'd exactly like to implement the context argument here. I think after reading it over and thinking I'm leaning towards the "have a lifetime argument" solution but I'm not sure it's the best strategy, so I'm curious what @cramertj you think about this?
/// | ||
/// In general, futures are composed into large units of work, which are then | ||
/// spawned as tasks onto an *executor*. The executor is responsible for polling | ||
/// the future as notifications arrive, until the future terminates. | ||
/// | ||
/// This is obtained by the `task::current` function. | ||
/// The poll function accepts a context, which is usually provided by the executor. | ||
pub struct Context<'a>(Option<ContextInner<'a>>); |
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 is certainly quite interesting! I think that for efficiency we will probably want the 'a
lifetime parameter on Context
, but I could also see how that's not always desired (and it doesn't follow 100% for me in terms of what I might naively expect).
@cramertj do you have thoughts on this? Do you think we should avoid this entirely? Or in general, do you think that the concrete implementation of Context
has an "obvious" implementation we should otherwise be doing?
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 had tossed around a few ideas here: I think the actual function signature we want is mut ctx: Context<'a>
. Context
does need to take the locally provided impls of Spawn
etc. by reference, as well as the "super" Context
, so we do need a lifetime parameter.
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.
Agreed!
set(&new_task, || f(obj)) | ||
}) | ||
} | ||
|
||
fn enter<F, R>(&mut self, unpark: BorrowedUnpark, f: F) -> R |
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 whole Spawn
type I think may not make 100% sense in the world where contexts are explicitly passed around. I'd sort of naively expect that we'd have something like:
struct Spawn<F> {
inner: F,
context: Context,
}
but that unfortunately doesn't work well with the lifetime argument on Context
right now. In theory though the task-local data should be stored on the Context
itself for now rather than in Spawn
I think?
I wonder if we could do something like:
struct Context {
data: TaskLocalDataMap, // I forget the actual name
waker: NotifyHandle, // how to do a wakeup
}
impl Context {
fn new<T: Into<NotifyHandle>>(inner: T) -> Context;
// variuos accessors for `data`
fn set_waker(&mut self, ...); // maybe?
}
One problem I think is that right now we make heavy usage of the lifetime parameters and stack closures and whatnot. In that sense we sort of want there to be a Context
type (with no lifetime parameters maybe?) as well as a ContextRef<'a>
type which all futures take. Or at least that matches that current scheme... I'm sort of losing myself here anyway
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 did try an iteration of this code with context
as a member of Spawn
. It makes panic safety harder when trying to change the notify handle temporarily for the task because you have to have a way to unset it. But you need mut access to the future and the context at the same time (which rust doesn't like).
@@ -297,7 +297,8 @@ impl Future for SpawnedFuture { | |||
type Item = bool; | |||
type Error = bool; | |||
|
|||
fn poll(&mut self) -> Poll<bool, bool> { | |||
self.inner.with_task_data(|f| f.poll()) | |||
fn poll(&mut self, ctx: &mut task::Context) -> Poll<bool, bool> { |
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.
At least stylistically for this repo I'd personally prefer cx
over ctx
, but I may also be too used to rustc
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.
For what it is worth, ctx
commonly stands for "context" in programming contexts (can be verified by google search "ctx programming" vs. "cx programming").
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.
+1 for ctx on the bikeshed, if for no other reason than that it will be obvious to folks coming from golang.
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 probably not worth much, but I'm for ctx
since I picked it in the first place and changing it would mean I'd have to change it. :)
@@ -238,6 +238,8 @@ pub mod prelude { | |||
Poll, | |||
}; | |||
|
|||
pub use futures_core::task::Context as TaskContext; |
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.
@cramertj I think you followed the RFC more closely than I, but is this what the RFC concluded?
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.
When the RFC concluded, I said we'd leave it as task::Context
, and that further prelude-import-renamings could be addressed via followup discussions/proposals. I'd be inclined to leave this out for now, and instead import task
.
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.
That's fine. It's an easy change.
Ok so I think this could be a great opportunity to jettison impl Context<'a> {
pub fn new<F>(f: F, data: &'a TaskData) -> Context<'a>
where F: FnMut() -> NotifyHandle + 'a;
} and I think via that we could eliminate |
@alexcrichton I think this is good. As you pointed out, |
Hm so with some more thinking, I think we've got two choices of signatures for trait Future {
// option 1
fn poll(&mut self, cx: Context<'_>) -> Poll<T, E>;
// option 2
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<T, E>;
} If we were to go with option 1 I think it also requires a function like: impl Context<'a> {
fn reborrow(&'b mut self) -> Context<'b>;
} to enable use cases like: impl Future for Select {
fn poll(&mut self, cx: Context<'_>) -> Poll<T, E> {
let res1 = self.a.poll(cx);
let res2 = self.b.poll(cx); //~ ERROR: use of moved value `cx`
// ...
}
} I would personally lean towards |
I lean very heavily towards |
This doesn't change the functionality and the Context is `()`. This allows having this commit be purely mechanical. Also add some missing `#[must_use]`'s to futures and streams.
Ok thanks again for starting this out @ahmedcharles! With #758 extracted from this and merged I think I've finished up most of the rest at #762 so I'm going to close this for now |
Only
futures-core
has been changed so far. Feedback welcome.#129 rust-lang-nursery/futures-rfcs#2