Add optimized single-word WakerRegistration, add AtomicWakerRegistration.

This commit is contained in:
Dario Nieuwenhuis 2021-03-17 01:55:01 +01:00
parent ab01e0be3b
commit bb68f5d0e8
4 changed files with 142 additions and 24 deletions

View File

@ -13,6 +13,8 @@ defmt-info = []
defmt-warn = [] defmt-warn = []
defmt-error = [] defmt-error = []
executor-agnostic = []
[dependencies] [dependencies]
defmt = { version = "0.2.0", optional = true } defmt = { version = "0.2.0", optional = true }
log = { version = "0.4.11", optional = true } log = { version = "0.4.11", optional = true }

View File

@ -3,6 +3,8 @@ mod forever;
mod mutex; mod mutex;
mod portal; mod portal;
mod signal; mod signal;
#[cfg_attr(feature = "executor-agnostic", path = "waker_agnostic.rs")]
mod waker; mod waker;
pub use drop_bomb::*; pub use drop_bomb::*;

View File

@ -1,11 +1,14 @@
use core::mem; use core::ptr::{self, NonNull};
use core::task::Context;
use core::task::Waker; use core::task::Waker;
use atomic_polyfill::{AtomicPtr, Ordering};
use crate::executor::raw::{task_from_waker, wake_task, Task};
/// Utility struct to register and wake a waker. /// Utility struct to register and wake a waker.
#[derive(Debug)] #[derive(Debug)]
pub struct WakerRegistration { pub struct WakerRegistration {
waker: Option<Waker>, waker: Option<NonNull<Task>>,
} }
impl WakerRegistration { impl WakerRegistration {
@ -15,37 +18,61 @@ impl WakerRegistration {
/// Register a waker. Overwrites the previous waker, if any. /// Register a waker. Overwrites the previous waker, if any.
pub fn register(&mut self, w: &Waker) { pub fn register(&mut self, w: &Waker) {
let w = unsafe { task_from_waker(w) };
match self.waker { match self.waker {
// Optimization: If both the old and new Wakers wake the same task, we can simply // Optimization: If both the old and new Wakers wake the same task, do nothing.
// keep the old waker, skipping the clone. (In most executor implementations, Some(w2) if w == w2 => {}
// cloning a waker is somewhat expensive, comparable to cloning an Arc). Some(w2) => {
Some(ref w2) if (w2.will_wake(w)) => {} // We had a waker registered for another task. Wake it, so the other task can
_ => { // reregister itself if it's still interested.
// clone the new waker and store it //
if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) { // If two tasks are waiting on the same thing concurrently, this will cause them
// We had a waker registered for another task. Wake it, so the other task can // to wake each other in a loop fighting over this WakerRegistration. This wastes
// reregister itself if it's still interested. // CPU but things will still work.
// //
// If two tasks are waiting on the same thing concurrently, this will cause them // If the user wants to have two tasks waiting on the same thing they should use
// to wake each other in a loop fighting over this WakerRegistration. This wastes // a more appropriate primitive that can store multiple wakers.
// CPU but things will still work.
// unsafe { wake_task(w2) }
// If the user wants to have two tasks waiting on the same thing they should use self.waker = Some(w);
// a more appropriate primitive that can store multiple wakers.
old_waker.wake()
}
} }
None => self.waker = Some(w),
} }
} }
/// Wake the registered waker, if any. /// Wake the registered waker, if any.
pub fn wake(&mut self) { pub fn wake(&mut self) {
if let Some(w) = self.waker.take() { if let Some(w) = self.waker.take() {
w.wake() unsafe { wake_task(w) }
}
}
}
pub struct AtomicWakerRegistration {
waker: AtomicPtr<Task>,
}
impl AtomicWakerRegistration {
pub const fn new() -> Self {
Self {
waker: AtomicPtr::new(ptr::null_mut()),
} }
} }
pub fn context(&self) -> Option<Context<'_>> { /// Register a waker. Overwrites the previous waker, if any.
self.waker.as_ref().map(|w| Context::from_waker(w)) pub fn register(&self, w: &Waker) {
let w = unsafe { task_from_waker(w) };
let w2 = self.waker.swap(w.as_ptr(), Ordering::Relaxed);
if !w2.is_null() && w2 != w.as_ptr() {
unsafe { wake_task(NonNull::new_unchecked(w2)) };
}
}
/// Wake the registered waker, if any.
pub fn wake(&self) {
let w2 = self.waker.swap(ptr::null_mut(), Ordering::Relaxed);
if !w2.is_null() {
unsafe { wake_task(NonNull::new_unchecked(w2)) };
}
} }
} }

View File

@ -0,0 +1,87 @@
use core::cell::Cell;
use core::mem;
use core::task::Waker;
use cortex_m::interrupt::Mutex;
/// Utility struct to register and wake a waker.
#[derive(Debug)]
pub struct WakerRegistration {
waker: Option<Waker>,
}
impl WakerRegistration {
pub const fn new() -> Self {
Self { waker: None }
}
/// Register a waker. Overwrites the previous waker, if any.
pub fn register(&mut self, w: &Waker) {
match self.waker {
// Optimization: If both the old and new Wakers wake the same task, we can simply
// keep the old waker, skipping the clone. (In most executor implementations,
// cloning a waker is somewhat expensive, comparable to cloning an Arc).
Some(ref w2) if (w2.will_wake(w)) => {}
_ => {
// clone the new waker and store it
if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) {
// We had a waker registered for another task. Wake it, so the other task can
// reregister itself if it's still interested.
//
// If two tasks are waiting on the same thing concurrently, this will cause them
// to wake each other in a loop fighting over this WakerRegistration. This wastes
// CPU but things will still work.
//
// If the user wants to have two tasks waiting on the same thing they should use
// a more appropriate primitive that can store multiple wakers.
old_waker.wake()
}
}
}
}
/// Wake the registered waker, if any.
pub fn wake(&mut self) {
if let Some(w) = self.waker.take() {
w.wake()
}
}
}
/// Utility struct to register and wake a waker.
pub struct AtomicWakerRegistration {
waker: Mutex<Cell<Option<Waker>>>,
}
impl AtomicWakerRegistration {
pub const fn new() -> Self {
Self {
waker: Mutex::new(Cell::new(None)),
}
}
/// Register a waker. Overwrites the previous waker, if any.
pub fn register(&mut self, w: &Waker) {
cortex_m::interrupt::free(|cs| {
let cell = self.waker.borrow(cs);
cell.set(match cell.replace(None) {
Some(w2) if (w2.will_wake(w)) => Some(w2),
Some(w2) => {
w2.wake();
Some(w.clone())
}
None => Some(w.clone()),
})
})
}
/// Wake the registered waker, if any.
pub fn wake(&mut self) {
cortex_m::interrupt::free(|cs| {
let cell = self.waker.borrow(cs);
if let Some(w) = cell.replace(None) {
w.wake()
}
})
}
}