diff --git a/text/0000-futures.md b/text/0000-futures.md index cdf694e1b99..55a9e41b1e7 100644 --- a/text/0000-futures.md +++ b/text/0000-futures.md @@ -105,13 +105,23 @@ In addition to `async fn` definitions, futures can be built using adapters, much like with `Iterator`s. Initially these adapters will be provided entirely "out of tree", but eventually they will make their way into the standard library. -Ultimately asynchronous computations are executed by *tasks*, which are -lightweight threads. In particular, an *executor* is able to "spawn" a -`()`-producing `Future` as an independent task; these tasks are then -cooperatively scheduled onto one or more operating system threads. The -`Executor` trait defines this interface, and the `task` module provides a host -of related definitions needed when manually implementing `Future`s or -executors. +Ultimately asynchronous computations are executed in the form of *tasks*, +which are comparable to lightweight threads. *executor*s provide the ability to +create tasks from `()`-producing `Future`s. The executor will pin the `Future` +and `poll` it until completion inside the task that it creates for it. + +The implementation of an executor schedules the tasks it owns in a cooperative +fashion. It is up to the implementation of an executor whether one or more +operation system threads are used for this, as well as how many tasks can be +spawned on it in parallel. Some executor implementations may only be able to +drive a single `Future` to completion, while others can provide the ability to +dynamically accept new `Future`s that are driven to completion inside tasks. + +This RFC does not include any definition of an executor. It merely defines the +interaction between executors, tasks and `Future`s, in the form of APIs +that allow tasks to request getting scheduled again. +The `task` module provides these APIs, which are required when manually implementing +`Future`s or executors. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -124,10 +134,10 @@ onto a single operating system thread. To perform this cooperative scheduling we use a technique sometimes referred to as a "trampoline". When a task would otherwise need to block waiting for some -event, instead it schedules itself for later wakeup and *returns* to the -executor running it, which can then run another task. Subsequent wakeups place -the task back on the executors queue of ready tasks, much like a thread -scheduler in an operating system. +event, instead it saves an object that allows it to get scheduled again later +and *returns* to the executor running it, which can then run another task. +Subsequent wakeups place the task back on the executors queue of ready tasks, +much like a thread scheduler in an operating system. Attempting to complete a task (or async value within it) is called *polling*, and always yields a `Poll` value back: @@ -154,31 +164,323 @@ can be dropped. ### Waking up -Each task executor provides its own scheduling facilities, and hence needs to -customize the way task wakeups are handled. Most of the time, you should use the -`std::task::Wake` trait defining wakeup behavior: +If a future cannot be directly fulfilled during execution and returns `Pending`, +it needs a way to later on inform the executor that it needs to get polled again +to make progress. + +This functionality is provided through a set of `Waker` types. + +`Waker`s are objects which are passed as a parameter to the `Future::poll` call, +and which can be stored by the implementation of those `Futures`s. Whenever a +`Future` has the need to get polled again, it can use the `wake` method of the +waker in order to inform the executor that the task which owns the `Future` +should get scheduled and executed again. + +The RFC defines two concrete `Waker` types, with which implementors of `Futures` +and asynchronous functions will interact: `Waker` and `LocalWaker`. Both of +these types define a `wake(&self)` function which is used to schedule the task +that is associated to the `Waker` again. + +The difference between those types is that `Waker` implements the `Send` and `Sync` +marker traits, while `LocalWaker` doesn't. This means a `Waker` can be sent to +another thread and stored there in order to wake up the associated task later on, +while a `LocalWaker` cannot be sent. Depending on the capabilities of the underlying +executor a `LocalWaker` can be converted into a `Waker`. Most executors in the +ecosystem will implement this functionality. The exception will be highly +specialized executors, which e.g. want to avoid the cost of all synchronization. + +Calling the `wake()` method on a `Waker` will in general be more expensive than +on a `LocalWaker` instance, due to additional synchronization. + +Executors will always pass a `LocalWaker` instance to the tasks they poll. + +The mechanism through which tasks get scheduled again depends on the executor +which is driving the task. +Possible ways of waking up a an executor include: +- If the executor is blocked on a condition variable, the condition variable + needs to get notified. +- If the executor is blocked on a system call like `select`, it might need + to get woken up by a syscall like `write` to a pipe. +- If the executor's thread is parked, the wakeup call needs to unpark it. + +To accommodate this behavior, the `Waker` and `LocalWaker` types are +defined through a `RawWaker` type, which is an struct that defines a dynamic +dispatch mechanism, which consists of an raw object pointer and a virtual function +pointer table (vtable). This mechanism allows implementers of an executor to +customize the behavior of `Waker` and `LocalWaker` objects by providing an +executor-specific `RawWaker`. + +This mechanism is chosen in favor of trait objects since it allows for more +flexible memory management schemes. `RawWaker` can be implemented purely in +terms of global functions and state, on top of reference counted objects, or +in other ways. + +The relation between those `Waker` types is outlined in the following definitions: + +```rust +/// A `RawWaker` allows the implementor of a task executor to customize the +/// behavior of `LocalWaker`s and `Waker`s. +/// It consists of a data pointer and a virtual function pointer table (vtable) that +/// customizes the behavior of the `RawWaker`. +#[derive(PartialEq)] +pub struct RawWaker { + /// A data pointer, which can be used to store arbitrary data as required + /// by the executor. This could be e.g. a type-erased pointer to an `Arc` + /// that is associated with the task. + /// The value of this field gets passed to all functions that are part of + /// the vtable as first parameter. + pub data: *const (), + /// Virtual function pointer table that customizes the behavior of this waker. + pub vtable: &'static RawWakerVTable, +} + +/// A virtual function pointer table (vtable) that allows to customize the +/// behavior of a `RawWaker`. +/// The pointers which get passed to all functions inside the vtable are the +/// values of the `data` field. +#[derive(PartialEq, Copy, Clone)] +pub struct RawWakerVTable { + /// This function will be called when the `RawWaker` gets cloned, e.g. when + /// the `Waker` or `LocalWaker` in which the `RawWaker` is stored gets cloned. + /// + /// The implementation of this function must retain all resources that are + /// required for this additional instance of a `RawWaker` and associated + /// task. The implementation must return a valid `RawWaker` that behaves + /// equivalent to the `RawWaker` that got cloned. E.g. cloning a `RawWaker` + /// that implemented a thread-safe wakeup for use in `Waker` must return + /// a `RawWaker` that implements the same wakeup behavior. + pub clone: unsafe fn(*const ()) -> RawWaker, + /// This function will be called when a `LocalWaker` should be converted into + /// a thread-safe `Waker`. The implementation of this function must return + /// a new `RawWaker` which can fulfill the requirements of `Waker`. It can + /// exchange the vtable for that purpose. E.g. it might replace the `wake` + /// function with a varient that supports cross-thread wakeups. + /// + /// The old `LocalWaker` which contained the data pointer should be consumed + /// while performing the operation. After the operation the `LocalWaker` + /// won't exist anymore, only the new `Waker`. + /// This means that if both instances would utilize the same reference-counted + /// data, changing the reference count would not be necessary. + /// + /// If a conversion is not supported, the implementation should return + /// `None`. In this case the implementation must still make + /// sure that the data pointer which is passed to the function is correctly + /// released, e.g. by calling the associated `drop_fn` on it. + pub into_waker: unsafe fn(*const ()) -> Option, + /// This function will be called when `wake` is called on the `RawWaker`. + pub wake: unsafe fn(*const ()), + /// This function gets called when a `RawWaker` gets dropped. + /// + /// The implementation of this function must make sure to release any + /// resources that are associated with this instance of a `RawWaker` and + /// associated task. + pub drop_fn: unsafe fn(*const ()), +} + +/// A `Waker` is a handle for waking up a task by notifying its executor that it +/// is ready to be run. +/// +/// This handle encapsulates a `RawWaker` instance, which defines the +/// executor-specific wakeup behavior. +/// +/// Implements `Clone`, `Send`, and `Sync`. +pub struct Waker { + waker: RawWaker, +} + +impl Waker { + /// Wake up the task associated with this `Waker`. + pub fn wake(&self) { + // The actual wakeup call is delegated through a virtual function call + // to the implementation which is defined by the executor. + unsafe { (self.waker.vtable.wake)(self.waker.data) } + } + + /// Returns whether or not this `Waker` and other `Waker` have awaken the same task. + /// + /// This function works on a best-effort basis, and may return false even + /// when the `Waker`s would awaken the same task. However, if this function + /// returns `true`, it is guaranteed that the `Waker`s will awaken the same task. + /// + /// This function is primarily used for optimization purposes. + pub fn will_wake(&self, other: &Waker) -> bool { + self.waker == other.waker + } + + /// Creates a new `Waker` from `RawWaker`. + /// + /// The method cannot check whether `RawWaker` fulfills the required API + /// contract to make it usable for `Waker` and is therefore unsafe. + pub unsafe fn new_unchecked(waker: RawWaker) -> Waker { + Waker { + waker: waker, + } + } +} + +impl Clone for Waker { + fn clone(&self) -> Self { + Waker { + waker: RawWaker { + data: unsafe { (self.waker.vtable.clone)(self.waker.data) }, + vtable: self.waker.vtable, + } + } + } +} + +impl Drop for Waker { + fn drop(&mut self) { + unsafe { (self.waker.vtable.drop_fn)(self.waker.data) } + } +} + +/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. +/// +/// This is similar to the `Waker` type, but cannot be sent across threads. +/// Task executors can use this type to implement more optimized singlethreaded wakeup behavior. +/// +/// This handle encapsulates a `RawWaker` instance, which defines the +/// executor-specific wakeup behavior. +/// +/// Implements `Clone`, but neither `Send` nor `Sync` +pub struct LocalWaker { + waker: RawWaker, +} + +impl LocalWaker { + /// Wake up the task associated with this `LocalWaker`. + pub fn wake(&self) { + // The actual wakeup call is delegated through a virtual function call + // to the implementation which is defined by the executor + unsafe { (self.waker.vtable.wake)(self.waker.data) } + } + + /// Converts the `LocalWaker` into `Waker`, which can be sent across + /// thread boundaries. + /// + /// This operation consumes the `LocalWaker`. + /// + /// This function can panic if the associated executor does not support + /// getting woken up from a different thread. + pub fn into_waker(self) -> Waker { + match self.try_into_waker() { + Some(waker) => waker, + None => panic!("Conversion from LocalWaker into Waker is not supported"), + } + } + + /// Tries to convert the `LocalWaker` into a `Waker`, which can be sent + /// across thread boundaries. + /// + /// This operation consumes the `LocalWaker`. + /// + /// Returns None if the if the associated executor does not support + /// getting woken up from a different thread. + pub fn try_into_waker(self) -> Option { + unsafe { + let maybe_raw_waker = (self.waker.vtable.into_waker)(self.waker.data); + // Avoid that the drop runs on self, which would e.g. decrease + // the refcount on it. The object has been consumed or converted + // by the `into_waker` function. + mem::forget(self); + match maybe_raw_waker { + Some(rw) => Some(Waker::new_unchecked(rw)), + None => None, + } + } + } + + /// Returns whether or not this LocalWaker and other LocalWaker awaken the same task. + /// + /// This function works on a best-effort basis, and may return false even + /// when the LocalWakers would awaken the same task. However, if this function + /// returns true, it is guaranteed that the LocalWakers will awaken the same task. + /// + /// This function is primarily used for optimization purposes. + pub fn will_wake(&self, other: &LocalWaker) -> bool { + self.waker == other.waker + } + + /// Creates a new `LocalWaker` from `RawWaker`. + /// + /// The method cannot check whether `RawWaker` fulfills the required API + /// contract to make it usable for `LocalWaker` and is therefore unsafe. + pub unsafe fn new_unchecked(waker: RawWaker) -> LocalWaker { + LocalWaker { + waker: waker, + } + } +} + +// The implementations of `Clone` and `Drop` follow what is shown in for `Waker`. +``` + +`Waker`s must fulfill the following requirements: +- They must be cloneable. +- If all instances of a `Waker` have been dropped and their associated task had + been driven to completion, all resources which had been allocated for the task + must have been released. +- It must be safe to call `wake()` on a `Waker` even if the associated task has + already been driven to completion. +- `Waker::wake()` must wake up an executor even if it is called from an arbitrary + thread. + +An executor that instantiates a `RawWaker` must therefore make sure that all +these requirements are fulfilled. + +Since many of the ownership semantics that are required here can easily be met +through a reference-counted `Waker` implementation, a convienence method for +defining `Waker`s is provided, which does not require implementing a `RawWaker` +and the associated vtable manually. + +This convience method is based around the `ArcWake` trait. An implementor of +an executor can define a type which implements the `ArcWake` trait as defined +below. The `ArcWake` trait defines the associated method `into_local_waker`, +which affords retrieving a `LocalWaker` instance from an `Arc` of this type. +The returned instance will guarantee that the `wake()` and `wake_local` methods +of the type which implements `ArcWake` are called, whenever `wake()` is called +on a `Waker` or `LocalWaker`. ```rust /// A way of waking up a specific task. /// -/// Any task executor must provide a way of signaling that a task it owns -/// is ready to be `poll`ed again. Executors do so by providing a wakeup handle -/// type that implements this trait. -pub trait Wake: Send + Sync { +/// By implementing this trait, types that are expected to be wrapped in an `Arc` +/// can be converted into `LocalWaker` and `Waker` objects. +/// Those Wakers can be used to signal executors that a task it owns +/// is ready to be `poll`ed again. +pub trait ArcWake: Send + Sync { /// Indicates that the associated task is ready to make progress and should /// be `poll`ed. /// + /// This function can be called from an arbitrary thread, including threads which + /// did not create the `ArcWake` based `Waker`. + /// /// Executors generally maintain a queue of "ready" tasks; `wake` should place /// the associated task onto this queue. - fn wake(self: &Arc); + fn wake(arc_self: &Arc); /// Indicates that the associated task is ready to make progress and should be polled. - /// This function is like wake, but can only be called from the thread on which this - /// `Wake` was created. + /// This function is like `wake`, but will only be called from the thread on which this + /// `ArcWake` was created. + /// + /// This function is marked unsafe because callers must guarantee that + /// they call it only on the task on which the `ArcWake` had been created. /// /// Executors generally maintain a queue of "ready" tasks; `wake_local` should place /// the associated task onto this queue. - unsafe fn wake_local(self: &Arc) + unsafe fn wake_local(arc_self: &Arc) { + Self::wake(arc_self); + } + + /// Creates a `LocalWaker` from an Arc, if T implements ArcWake. + /// + /// The returned `LocalWaker` will call `wake.wake_local()` when awoken. + /// + /// The returned `LocalWaker` can be converted into a `Waker` through + /// it's `into_waker()` method. If `wake()` is called on this `Waker`, + /// the `wake()` function that is defined inside this trait will get called. + fn into_local_waker(wake: Arc) -> LocalWaker where Self: Sized; } ``` @@ -195,64 +497,21 @@ struct Task { executor: Arc, } -impl Wake for Task { +impl ArcWake for Task { fn wake(self: &Arc) { self.executor.sync_ready_queue.push(self.clone()); } - unsafe fn wake_local(self: &Arc) { + + fn wake_local(self: &Arc) { (&mut *self.executor.optimized_queue.get()).push(self.clone()) } } ``` The use of `&Arc` rather than just `&self` makes it possible to work directly with -the trait object for `Wake`, including cloning it. With `UnsafeWake` below, we'll see -an API with greater flexibility for the cases where `Arc` is problematic. - -In general async values are not coupled to any particular executor, so we use trait -objects to handle waking. These come in two forms: `Waker` for the general case, and -`LocalWaker` to provide more effenciency when the wakeup is guaranteed to occur on the -executor thread: - -```rust -/// A `Waker` is a handle for waking up a task by notifying its executor that it -/// is ready to be run. -/// -/// This handle contains a trait object pointing to an instance of the `UnsafeWake` -/// trait, allowing notifications to get routed through it. -/// -/// Implements `Clone`, `Send`, and `Sync`. -pub struct Waker { ... } - -impl Waker { - /// Wake up the task associated with this `Waker`. - pub fn wake(&self); -} - -/// A `LocalWaker` is a handle for waking up a task by notifying its executor that it is ready to be run. -/// -/// This is similar to the `Waker` type, but cannot be sent across threads. Task executors can use this type to implement more optimized singlethreaded wakeup behavior. -impl LocalWaker { - /// Wake up the task associated with this `LocalWaker`. - pub fn wake(&self); -} - -/// You can upgrade to a sendable `Waker` at zero cost, but waking through a `Waker` is more expensive -/// due to synchronization. -impl From for Waker { .. } -``` - -Task execution always happens in the context of a `LocalWaker` that can be used to -wake the task up locally, or converted into a `Waker` that can be sent to other threads. - -It's possible to construct a `Waker` using `From>`. - -### `UnsafeWake` and `no_std` compatibility +the `Arc` that contains the object which implements `ArcWake`. This enables +use-cases like cloning it. -The [`UnsafeWake` trait](https://doc.rust-lang.org/nightly/std/task/trait.UnsafeWake.html) -in `core::task` is designed to support task wakeup in `no_std` environments, where -we cannot use `Arc`. It is *not* proposed for stabilization at this time, because -its APIs are awaiting revision based on object safety for `*mut self` methods. ## `core::future` module @@ -313,9 +572,10 @@ pub trait Future { /// # [`LocalWaker`], [`Waker`] and thread-safety /// /// The `poll` function takes a [`LocalWaker`], an object which knows how to - /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so in - /// order to make thread-safe futures the [`LocalWaker::into_waker`] method - /// should be used to convert the [`LocalWaker`] into a thread-safe version. + /// awaken the current task. [`LocalWaker`] is not `Send` nor `Sync`, so + /// to make thread-safe futures the [`LocalWaker::into_waker`] and + /// [`LocalWaker::try_into_waker`] methods should be used to convert + /// the [`LocalWaker`] into a thread-safe version. /// [`LocalWaker::wake`] implementations have the ability to be more /// efficient, however, so when thread safety is not necessary, /// [`LocalWaker`] should be preferred.