Skip to content
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

Add Wake trait for safe construction of Wakers. #68700

Merged
merged 8 commits into from
Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/liballoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub mod str;
pub mod string;
#[cfg(target_has_atomic = "ptr")]
pub mod sync;
#[cfg(target_has_atomic = "ptr")]
pub mod task;
#[cfg(test)]
mod tests;
pub mod vec;
Expand Down
87 changes: 87 additions & 0 deletions src/liballoc/task.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![unstable(feature = "wake_trait", issue = "69912")]
//! Types and Traits for working with asynchronous tasks.
use core::mem::{self, ManuallyDrop};
use core::task::{RawWaker, RawWakerVTable, Waker};

use crate::sync::Arc;

/// The implementation of waking a task on an executor.
///
/// This trait can be used to create a [`Waker`]. An executor can define an
/// implementation of this trait, and use that to construct a Waker to pass
/// to the tasks that are executed on that executor.
///
/// This trait is a memory-safe and ergonomic alternative to constructing a
/// [`RawWaker`]. It supports the common executor design in which the data
/// used to wake up a task is stored in an [`Arc`]. Some executors (especially
/// those for embedded systems) cannot use this API, which is why [`RawWaker`]
/// exists as an alternative for those systems.
#[unstable(feature = "wake_trait", issue = "69912")]
pub trait Wake {
/// Wake this task.
#[unstable(feature = "wake_trait", issue = "69912")]
fn wake(self: Arc<Self>);

/// Wake this task without consuming the waker.
///
/// If an executor supports a cheaper way to wake without consuming the
/// waker, it should override this method. By default, it clones the
/// [`Arc`] and calls `wake` on the clone.
#[unstable(feature = "wake_trait", issue = "69912")]
fn wake_by_ref(self: &Arc<Self>) {
self.clone().wake();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

futures::task::ArcWake has the opposite defaulted method. Is there a reason to assume that implementations would commonly need to own the Arc instead of being able to wake through a shared reference?

Copy link
Contributor Author

@withoutboats withoutboats Jan 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my experience working on romio/juliex and reading the tokio source, the standard executor model is to re-enqueue the task on some TLS or global queue (which means it must have ownership), and the standard reactor model is to store an Option<Waker> and then call .take().unwrap().wake(). IMO the default in futures is backwards, and would result in naive implementations making an additional unnecessary ref count increment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion about this either way-- I can definitely imagine arguments on either side. The reason I did it the other way in futures-rs was that I imagined implementations which only needed by-reference waking would implement just wake not realizing that that would result in a wake_by_ref method which cloned unnecessarily. In practice, I don't think either case is too big of an issue.

Copy link
Contributor Author

@withoutboats withoutboats Feb 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, either way I think its a chance of an incorrectly implemented executor leading to one extra incr/decr on wakeup - a trivial and easily fixable problem.

But I do think the more likely scenario is that they do need ownership of the arc, rather than that they don't (the only point of it being by arc is to cheaply enqueue on wake up after all), and I thought our choice to name the fns wake and wake_by_ref was tied up in this assumption (that wake would be the default one).

}
}

#[unstable(feature = "wake_trait", issue = "69912")]
impl<W: Wake + Send + Sync + 'static> From<Arc<W>> for Waker {
fn from(waker: Arc<W>) -> Waker {
// SAFETY: This is safe because raw_waker safely constructs
// a RawWaker from Arc<W>.
unsafe { Waker::from_raw(raw_waker(waker)) }
}
}

#[unstable(feature = "wake_trait", issue = "69912")]
impl<W: Wake + Send + Sync + 'static> From<Arc<W>> for RawWaker {
fn from(waker: Arc<W>) -> RawWaker {
raw_waker(waker)
}
}

// NB: This private function for constructing a RawWaker is used, rather than
// inlining this into the `From<Arc<W>> for RawWaker` impl, to ensure that
// the safety of `From<Arc<W>> for Waker` does not depend on the correct
// trait dispatch - instead both impls call this function directly and
// explicitly.
#[inline(always)]
fn raw_waker<W: Wake + Send + Sync + 'static>(waker: Arc<W>) -> RawWaker {
// Increment the reference count of the arc to clone it.
unsafe fn clone_waker<W: Wake + Send + Sync + 'static>(waker: *const ()) -> RawWaker {
let waker: Arc<W> = Arc::from_raw(waker as *const W);
mem::forget(Arc::clone(&waker));
raw_waker(waker)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use ManuallyDrop like in wake_by_ref, can't the entire thing just become

let waker: ManuallyDrop<Arc<W>> = ManuallyDrop::new(Arc::from_raw(waker as *const W));
raw_waker(waker.clone())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I find this less clear than mem::forgetting the clone.

Copy link
Member

@RalfJung RalfJung Mar 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I find it really confusing to create a clone only to forget it immediately. Conceptually speaking, you want to return the clone (that's what the function name says), but without dropping the original.

If you stick to the current code, I think this needs a comment explaining why it makes any sense to create a clone that is immediately forgotten.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me clone + mem forget an Arc is a pretty clear "increment the ref count" signal (TBH just having unsafe methods to fiddle with Arc's ref count would be nicer than any of this). What I would find non-obvious is why you need to put self into a manually drop.

There is a comment on this function indicating that it's purpose is to increment the refcount.

}

// Wake by value, moving the Arc into the Wake::wake function
unsafe fn wake<W: Wake + Send + Sync + 'static>(waker: *const ()) {
let waker: Arc<W> = Arc::from_raw(waker as *const W);
<W as Wake>::wake(waker);
}

// Wake by reference, wrap the waker in ManuallyDrop to avoid dropping it
unsafe fn wake_by_ref<W: Wake + Send + Sync + 'static>(waker: *const ()) {
let waker: ManuallyDrop<Arc<W>> = ManuallyDrop::new(Arc::from_raw(waker as *const W));
<W as Wake>::wake_by_ref(&waker);
}

// Decrement the reference count of the Arc on drop
unsafe fn drop_waker<W: Wake + Send + Sync + 'static>(waker: *const ()) {
mem::drop(Arc::from_raw(waker as *const W));
}

RawWaker::new(
Arc::into_raw(waker) as *const (),
&RawWakerVTable::new(clone_waker::<W>, wake::<W>, wake_by_ref::<W>, drop_waker::<W>),
)
}
6 changes: 6 additions & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@
#![feature(untagged_unions)]
#![feature(unwind_attributes)]
#![feature(vec_into_raw_parts)]
#![feature(wake_trait)]
// NB: the above list is sorted to minimize merge conflicts.
#![default_lib_allocator]

Expand Down Expand Up @@ -463,9 +464,14 @@ pub mod time;
#[stable(feature = "futures_api", since = "1.36.0")]
pub mod task {
//! Types and Traits for working with asynchronous tasks.

#[doc(inline)]
#[stable(feature = "futures_api", since = "1.36.0")]
pub use core::task::*;

#[doc(inline)]
#[unstable(feature = "wake_trait", issue = "69912")]
pub use alloc::task::*;
}

#[stable(feature = "futures_api", since = "1.36.0")]
Expand Down