-
Notifications
You must be signed in to change notification settings - Fork 634
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
implement the executor RFC #763
Conversation
The PR needs a couple more things to be complete:
|
What is the way that you are referencing? |
I'm also not following why |
https://gist.github.com/alexcrichton/4408c4f317fca5122d37b718c2625169
It's not that |
|
||
fn run_executor<T, F: FnMut(&Waker) -> Async<T>>(mut f: F) -> T { | ||
let _enter = enter() | ||
.expect("cannot execute `LocalPool` executor from within \ |
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 still necessary?
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.
Yes -- I think we still want enter
to ensure that executors aren't accidentally nested.
futures-core/src/executor.rs
Outdated
/// TODO: dox | ||
pub trait Executor { | ||
/// TODO: dox | ||
fn spawn(&self, f: Box<Future<Item = (), Error = ()> + Send>) -> Result<(), SpawnError>; |
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 had discussed making this &mut self
on the RFC-- I think this would allow you to get rid of the RefCell
in LocalPool
.
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.
Then you can't implement Executor
on an Arc<E: Executor>
.
Also, we can't get rid of the RefCell
, because the handles are behind an Rc
.
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 just did a big complicated dance to satisfy myself that I couldn't make it work-- the piece I was missing is that you want to be able to spawn_local
from inside Future::poll
, while also holding onto a task::Context
.
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.
@aturon fwiw, the doc did switch to &mut self
.
Is being able to impl Executor
on Arc<E: Executor>
a requirement? What is the use case? The executor can always provide a cloneable handle.
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.
Either way, &mut self
vs. &self
isn't a critical distinction.
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 the Executor
for Arc<E: Executor>
is the critical piece: the important part is that this needs to work somehow:
let local_pool = LocalPool::new();
let local_exec = local_pool.executor();
local_pool.run_until(|f| future
.and_then(|_| local_exec.spawn_local(some_other_task))
.and_then(...));
local_exec
needs to be accessible from inside the run_until
method of local_pool
.
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 Why does that depend on &mut self
vs. &self
on the executor trait?
It looks like executor()
returns a handle by value backed by an Rc
, so it should be able to be moved into the closure.
You can also impl Executor
on &LocalExecutor
to be able to spawn with an immut ref to the local executor.
futures-core/src/task/mod.rs
Outdated
map: &'a mut LocalMap, | ||
executor: &'a Executor, |
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 above, I think &'a mut self
here would remove the need for a RefCell
.
@aturon I will look at the gist in more detail tomorrow when I am more rested. At a glance, it looks like this strategy only works if the executor is bounded and I agree that it is ideal for the main Besides just preventing being able to impl a growable executor backed by a slab, it also makes it much more complicated to impl executors like this. Specifically, it is quite nice to be able to be called with a pointer to the executor AND a pointer to the task. If the |
incoming: Weak<Incoming>, | ||
} | ||
|
||
type Incoming = RefCell<Vec<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.
Why are new tasks not placed directly on the FuturesUnordered
?
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.
Because of re-entrancy: you may be in the middle of executing poll
on the FuturesUnordered
when new tasks are spawned.
@carllerche If we do need to retain this API, I would indeed much prefer if we could move it to the unsafe trait, though I'm not sure whether that's possible. |
@aturon The way I see it, the extra
For example, in the slab case, if the capacity is capped, you can use the rest of the Besides that, it also makes some executors easier to implement (as linked above). Specifically, if each task object needs to have a pointer back to the executor, that introduces cycles which are definitely trickier to reason about. It should be possible to move the |
I was thinking about this, and using an atomic linked list as a backing store (similar to |
@cramertj it's all about tradeoffs. IMO it should be possible to implement an executor backed by a growable (but not shrinkable) slab. It's a balance between pre-allocating the full capacity and getting deterministic runtime characteristics. There are also the other benefits I mentioned above. I don't think that removing these capabilities is a critical issue, but if this can be entirely supported at the |
IMO the major argument here would be to reduce the size of |
Reducing the size is valid. It isn't a critical blow either way. I'm just trying to get the full set of trade offs figured out. |
I'm also happy to go either way on this. |
TBH, there probably is still a way to growable "slab" like thing, it just would require pointers for everything instead of indices as well as some funky unsafe code. You'd lose the ability to use the rest of the Smaller wakeup handles is appealing. |
OK, I've now moved in and revamped There's one more thing I want to do. In making So, I want to add free functions, That said, given that I'm going to be tied up in meetings for the rest of the day, it probably makes sense to finish the review and merge this PR, and add the join handle stuff in a follow-up PR. |
I also think we should not block on the |
} | ||
} | ||
|
||
impl ThreadPoolBuilder { |
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 might be good to offer a block_on
function here that works the same as the top-level block_on
function, but uses the ThreadPool
as the Executor
, e.g. let res = ThreadPool::new_num_cpus().block_on(main_future);
.
(feel free to ignore this for now-- I'll open a PR later. I just wanted to make a note about it so I didn't forget)
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 understand what block_on
would mean in this context.
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.
@aturon Spawn the future on the thread pool, then block the current thread until the result of the future is available.
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 unclear on why that would be desirable, though.
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.
@aturon Isn't that what you'd want by default when running an application with a single async_main
on a thread pool?
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 figured you'd instead do:
LocalPool::new()
.run_until(async_main, &ThreadPool::new_num_cpus())
LGTM! |
I opened #767 to follow up. |
CI is still red but I think that's just benchmarks/no_std builds, mind touching those up though? |
Other than that though looks good! |
Oh, re this:
I actually still plan on having executor local reactors and am will probably re-raise the question of a default global once the dust settles on futures & before Tokio 0.2 Specifically, if |
@carllerche Yeah that's what I meant. So yes, let's revisit this once it's more clear what Tokio's needs will be. |
I think this is good to go once CI is happy. I'll do a follow-up PR changing to |
This patch series implements the executor RFC, and along the way:
id
parameters forNotify
, as we now have a way to reap their benefits without the convoluted API.NotifyHandle
andWaker
.task::Context
, as per IRC discussion.enter
functionality, which I believe will be obviated if Tokio ends up baking in default reactors tied to executors, as @carllerche wants to do.The
LocalPool
API is slightly different than in the RFC:LocalExecutor
type, necessary to ensure the lack ofRc
cycles.all_done
future (which has issues with re-entrancy), it provides arun
function which runs all spawned tasks to completion.