-
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
Sleep trait #665
Sleep trait #665
Conversation
This is the first step to supporting customizing executor sleeping strategies. Currently, it is not possible to run a Tokio reactor and a `CurrentThread` executor on the same thread. The executor is hard coded to use condvars for sleeping and the Tokio reactor requires calling `epoll_wait` (or equivalent) for blocking the current thread. The `Sleep` trait is added to abstract over this sleeping strategy. Beyond just supporting the Tokio reactor, adding a `Sleep` trait is useful for integrating any logic that requires hooking into the sleep strategy (e.g. timers).
This will be used in the future to add multiple different node lists.
Currently, `Scheduler` has to move every future it schedules out of the allocated node onto the stack, then move it back when it is done polling. This can be costly with large structs. The comment claims: > The future was extracted above (taken ownership). That way if it > panics we're guaranteed that the future is dropped on this thread > and doesn't accidentally get dropped on a different thread (bad). However, in the event of a panic. It is not possible for another thread to be able to drop the future. This is true even if the future remains in the node. A node is stored in an `Arc`, which means that it is not dropped until the ref count goes to zero. The `Bomb` structure holds a handle to the `Arc`, ensuring that the ref count does not go to zero. The other way a future is dropped is by calling `release_node`. This function is only called in `Bomb::drop`, which happens in the event of a panic, or in `Scheduler::drop`, which is permitted to drop the future as it is the type that logically owns the future value.
`Inner` holds an Arc reference for each node and each node holds an Arc reference to `Inner`. This creates an `Arc` cycle, which before this patch was broken by having `Node` use a weak ref back to `Inner`. However, using weak references requires atomic writes when notifying the future. This patch removes the weak ref and exchanges it for a strong ref. The rational is that the ref cycle is already broken by the `Scheduler::drop` implementation. The `Scheduler` type is outside of the ref cycle and the drop implementation iterates through each node that `Inner` holds and drops the ref. This breaks the cycle. `Node` now has an `Option<Arc<Inner<_>>` field. It is `Option` to support stub nodes not having to reference `Inner` as well as will support future improvements.
This allows queuing up futures to be submitted to a scheduler using the same internal data structures that the scheduler uses. This is added so that futures can be efficiently spawned without knowing the actual type of the scheduler (the `W` generic).
The traits are identical. `scheduler::Wakeup` was private and introduced earlier to allow the scheduler to support both `CurrentThread` and `FuturesUnordered`.
Adds a new function to `executor::CurrentThread` that allows specifying a custom sleep strategy. This will allow integrating `CurrentThread` with reactors, like Tokio, that require being able to block the current thread.
The function was originally named `block_with_init` as it was intended to be used commonly as a strategy to block the current thread waiting on a future to complete. This functionality is now provided with `future::blocking` and `CurrentThread` is intended to be exclusively used as a full executor. Given this change, the emphasis on `block_` is less important and a function name that conveys running the executor is picked instead.
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.
Ok, I completed the review, and raised a few significant concerns. Thanks for putting this PR together!
src/executor/sleep.rs
Outdated
} | ||
|
||
/// Wake up a sleeping thread. | ||
pub trait Wakeup: Send + Sync { |
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.
Does this also want to be 'static
?
src/scheduler.rs
Outdated
@@ -11,7 +11,7 @@ use std::mem; | |||
use std::ptr; | |||
use std::sync::atomic::Ordering::{Relaxed, SeqCst, Acquire, Release, AcqRel}; | |||
use std::sync::atomic::{AtomicPtr, AtomicBool}; | |||
use std::sync::{Arc, Weak}; |
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 agree with the reasoning laid out in the commit message. In particular, suppose you have a node which is in the ready queue at the point when the scheduler is dropped. While release_node
is called on that node, the fact that it's marked as queued means that it's Arc
is not decremented at that point, but instead assumed to be picked up by a later call to tick
. That later call never comes, and the Inner
is never dropped because of the cycle.
More to the point, other threads may have outstanding Node
references (via stashed NotifyHandle
s), which keep the node alive, which keeps the Inner
alive, which points back to the Node
...
src/executor/current_thread.rs
Outdated
let ret = current.enter(|| { | ||
let ret = f(&mut ctx); | ||
|
||
println!("~~~ DONE ENTER ~~~"); |
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.
Leftovers.
src/executor/current_thread.rs
Outdated
/// mut reference is set to null. This is done even if the closure panics. | ||
/// | ||
/// This reduces the odds of introducing pointer aliasing. | ||
fn set_scheduler<F, R>(&self, scheduler: &mut Scheduler, 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.
There are various comments in the file still referring to this function/it's implications.
src/executor/current_thread.rs
Outdated
// b) `cancel_all_executing` is called, forcing the executor to | ||
// return. | ||
runner.run(thread_notify, current); | ||
println!("~~~ DONE ENTER ~~~"); |
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.
Needs to be cleaned out.
src/scheduler.rs
Outdated
/// | ||
/// This is safe for the following reasons | ||
/// | ||
/// 1) The only usage of `W` in `Node` is `Option<Arc<Inner<_, W>>>`. When used |
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 feel pretty uneasy about this approach; it's "probably fine" but we don't have enough representation guarantees to be sure.
I'd be a lot more comfortable using a SmallVec
or something similar to queue up tasks, creating nodes afterward; assuming that most polling will result in at most a couple of new tasks, this is likely to be extremely small overhead and can be done in safe code.
I will revert the removal of I am worried that the weak ref will cause contention on NUMA systems, but we can punt until this is demonstrated. I will add additional comments and hopefully I won't forget the context again in the future. Re: the |
This patch addresses the feedback provided in the PR. Specifically, it does a few things: * Revert the removal of the Weak arc reference. * Switch to trait objects to hide the `W` generic. * Add 'static bound to `Wakeup`. The assumptions made when removing the `Weak` arc reference were incorrect. The incorrect assumptions are described in the commit being reverted (85a6351) and the error is explained [here]. [here]: #665 (comment) The implementation details of `current_thread` are reverted back what they were before this PR. The only remaining differences are the naming of the `run` function and the thread-local variable used to spawn futures into the current thread executor now uses a trait object to hide the `W` generic. While this adds a dynamic dispatch, this cost is only incurred on spawn. Finally, a 'static bound is added to the `executor::Wakeup` trait.
@aturon the latest commit should address your feedback. |
Thanks, the changes look great! Merge when ready. |
Ok, I fixed CI + updated impl comments. |
Introduce a
Sleep
trait that executors can use to customize the strategy used to put the current thread to sleep. This will be used by Tokio to enable running an instance ofCurrentThread
and an instance of the Tokio reactor on a single thread.This essentially makes the
CurrentThread
implementation generic overW: Wakeup
. This introduces some trickiness as the thread-local state that allows spawning new futures into the scheduler cannot be generic over theW
.Handling this required some restructuring of
scheduler
. This work was done incrementally in each commit and each commit includes a commit message that describes the step./cc @aturon