diff options
| author | Dario Nieuwenhuis <[email protected]> | 2021-03-17 01:55:01 +0100 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2021-03-17 02:53:41 +0100 |
| commit | bb68f5d0e83eec609ef0016baa4710b30ce49d62 (patch) | |
| tree | 3da1b55c82e76e53c7953f0457cfc618af76f3e2 | |
| parent | ab01e0be3b2faa320ffc169a6aa629fe44a2080c (diff) | |
Add optimized single-word WakerRegistration, add AtomicWakerRegistration.
| -rw-r--r-- | embassy/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy/src/util/mod.rs | 2 | ||||
| -rw-r--r-- | embassy/src/util/waker.rs | 75 | ||||
| -rw-r--r-- | embassy/src/util/waker_agnostic.rs | 87 |
4 files changed, 142 insertions, 24 deletions
diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml index c58fcbf28..39ad64a0d 100644 --- a/embassy/Cargo.toml +++ b/embassy/Cargo.toml | |||
| @@ -13,6 +13,8 @@ defmt-info = [] | |||
| 13 | defmt-warn = [] | 13 | defmt-warn = [] |
| 14 | defmt-error = [] | 14 | defmt-error = [] |
| 15 | 15 | ||
| 16 | executor-agnostic = [] | ||
| 17 | |||
| 16 | [dependencies] | 18 | [dependencies] |
| 17 | defmt = { version = "0.2.0", optional = true } | 19 | defmt = { version = "0.2.0", optional = true } |
| 18 | log = { version = "0.4.11", optional = true } | 20 | log = { version = "0.4.11", optional = true } |
diff --git a/embassy/src/util/mod.rs b/embassy/src/util/mod.rs index ae434a8b0..e64e7f1f1 100644 --- a/embassy/src/util/mod.rs +++ b/embassy/src/util/mod.rs | |||
| @@ -3,6 +3,8 @@ mod forever; | |||
| 3 | mod mutex; | 3 | mod mutex; |
| 4 | mod portal; | 4 | mod portal; |
| 5 | mod signal; | 5 | mod signal; |
| 6 | |||
| 7 | #[cfg_attr(feature = "executor-agnostic", path = "waker_agnostic.rs")] | ||
| 6 | mod waker; | 8 | mod waker; |
| 7 | 9 | ||
| 8 | pub use drop_bomb::*; | 10 | pub use drop_bomb::*; |
diff --git a/embassy/src/util/waker.rs b/embassy/src/util/waker.rs index 68e45cf1a..1f2d3a770 100644 --- a/embassy/src/util/waker.rs +++ b/embassy/src/util/waker.rs | |||
| @@ -1,11 +1,14 @@ | |||
| 1 | use core::mem; | 1 | use core::ptr::{self, NonNull}; |
| 2 | use core::task::Context; | ||
| 3 | use core::task::Waker; | 2 | use core::task::Waker; |
| 4 | 3 | ||
| 4 | use atomic_polyfill::{AtomicPtr, Ordering}; | ||
| 5 | |||
| 6 | use crate::executor::raw::{task_from_waker, wake_task, Task}; | ||
| 7 | |||
| 5 | /// Utility struct to register and wake a waker. | 8 | /// Utility struct to register and wake a waker. |
| 6 | #[derive(Debug)] | 9 | #[derive(Debug)] |
| 7 | pub struct WakerRegistration { | 10 | pub struct WakerRegistration { |
| 8 | waker: Option<Waker>, | 11 | waker: Option<NonNull<Task>>, |
| 9 | } | 12 | } |
| 10 | 13 | ||
| 11 | impl WakerRegistration { | 14 | impl WakerRegistration { |
| @@ -15,37 +18,61 @@ impl WakerRegistration { | |||
| 15 | 18 | ||
| 16 | /// Register a waker. Overwrites the previous waker, if any. | 19 | /// Register a waker. Overwrites the previous waker, if any. |
| 17 | pub fn register(&mut self, w: &Waker) { | 20 | pub fn register(&mut self, w: &Waker) { |
| 21 | let w = unsafe { task_from_waker(w) }; | ||
| 18 | match self.waker { | 22 | match self.waker { |
| 19 | // Optimization: If both the old and new Wakers wake the same task, we can simply | 23 | // Optimization: If both the old and new Wakers wake the same task, do nothing. |
| 20 | // keep the old waker, skipping the clone. (In most executor implementations, | 24 | Some(w2) if w == w2 => {} |
| 21 | // cloning a waker is somewhat expensive, comparable to cloning an Arc). | 25 | Some(w2) => { |
| 22 | Some(ref w2) if (w2.will_wake(w)) => {} | 26 | // We had a waker registered for another task. Wake it, so the other task can |
| 23 | _ => { | 27 | // reregister itself if it's still interested. |
| 24 | // clone the new waker and store it | 28 | // |
| 25 | if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) { | 29 | // If two tasks are waiting on the same thing concurrently, this will cause them |
| 26 | // We had a waker registered for another task. Wake it, so the other task can | 30 | // to wake each other in a loop fighting over this WakerRegistration. This wastes |
| 27 | // reregister itself if it's still interested. | 31 | // CPU but things will still work. |
| 28 | // | 32 | // |
| 29 | // If two tasks are waiting on the same thing concurrently, this will cause them | 33 | // If the user wants to have two tasks waiting on the same thing they should use |
| 30 | // to wake each other in a loop fighting over this WakerRegistration. This wastes | 34 | // a more appropriate primitive that can store multiple wakers. |
| 31 | // CPU but things will still work. | 35 | |
| 32 | // | 36 | unsafe { wake_task(w2) } |
| 33 | // If the user wants to have two tasks waiting on the same thing they should use | 37 | self.waker = Some(w); |
| 34 | // a more appropriate primitive that can store multiple wakers. | ||
| 35 | old_waker.wake() | ||
| 36 | } | ||
| 37 | } | 38 | } |
| 39 | None => self.waker = Some(w), | ||
| 38 | } | 40 | } |
| 39 | } | 41 | } |
| 40 | 42 | ||
| 41 | /// Wake the registered waker, if any. | 43 | /// Wake the registered waker, if any. |
| 42 | pub fn wake(&mut self) { | 44 | pub fn wake(&mut self) { |
| 43 | if let Some(w) = self.waker.take() { | 45 | if let Some(w) = self.waker.take() { |
| 44 | w.wake() | 46 | unsafe { wake_task(w) } |
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | pub struct AtomicWakerRegistration { | ||
| 52 | waker: AtomicPtr<Task>, | ||
| 53 | } | ||
| 54 | |||
| 55 | impl AtomicWakerRegistration { | ||
| 56 | pub const fn new() -> Self { | ||
| 57 | Self { | ||
| 58 | waker: AtomicPtr::new(ptr::null_mut()), | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Register a waker. Overwrites the previous waker, if any. | ||
| 63 | pub fn register(&self, w: &Waker) { | ||
| 64 | let w = unsafe { task_from_waker(w) }; | ||
| 65 | let w2 = self.waker.swap(w.as_ptr(), Ordering::Relaxed); | ||
| 66 | if !w2.is_null() && w2 != w.as_ptr() { | ||
| 67 | unsafe { wake_task(NonNull::new_unchecked(w2)) }; | ||
| 45 | } | 68 | } |
| 46 | } | 69 | } |
| 47 | 70 | ||
| 48 | pub fn context(&self) -> Option<Context<'_>> { | 71 | /// Wake the registered waker, if any. |
| 49 | self.waker.as_ref().map(|w| Context::from_waker(w)) | 72 | pub fn wake(&self) { |
| 73 | let w2 = self.waker.swap(ptr::null_mut(), Ordering::Relaxed); | ||
| 74 | if !w2.is_null() { | ||
| 75 | unsafe { wake_task(NonNull::new_unchecked(w2)) }; | ||
| 76 | } | ||
| 50 | } | 77 | } |
| 51 | } | 78 | } |
diff --git a/embassy/src/util/waker_agnostic.rs b/embassy/src/util/waker_agnostic.rs new file mode 100644 index 000000000..b4234c0f5 --- /dev/null +++ b/embassy/src/util/waker_agnostic.rs | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | use core::cell::Cell; | ||
| 2 | use core::mem; | ||
| 3 | use core::task::Waker; | ||
| 4 | |||
| 5 | use cortex_m::interrupt::Mutex; | ||
| 6 | |||
| 7 | /// Utility struct to register and wake a waker. | ||
| 8 | #[derive(Debug)] | ||
| 9 | pub struct WakerRegistration { | ||
| 10 | waker: Option<Waker>, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl WakerRegistration { | ||
| 14 | pub const fn new() -> Self { | ||
| 15 | Self { waker: None } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Register a waker. Overwrites the previous waker, if any. | ||
| 19 | pub fn register(&mut self, w: &Waker) { | ||
| 20 | match self.waker { | ||
| 21 | // Optimization: If both the old and new Wakers wake the same task, we can simply | ||
| 22 | // keep the old waker, skipping the clone. (In most executor implementations, | ||
| 23 | // cloning a waker is somewhat expensive, comparable to cloning an Arc). | ||
| 24 | Some(ref w2) if (w2.will_wake(w)) => {} | ||
| 25 | _ => { | ||
| 26 | // clone the new waker and store it | ||
| 27 | if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) { | ||
| 28 | // We had a waker registered for another task. Wake it, so the other task can | ||
| 29 | // reregister itself if it's still interested. | ||
| 30 | // | ||
| 31 | // If two tasks are waiting on the same thing concurrently, this will cause them | ||
| 32 | // to wake each other in a loop fighting over this WakerRegistration. This wastes | ||
| 33 | // CPU but things will still work. | ||
| 34 | // | ||
| 35 | // If the user wants to have two tasks waiting on the same thing they should use | ||
| 36 | // a more appropriate primitive that can store multiple wakers. | ||
| 37 | old_waker.wake() | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Wake the registered waker, if any. | ||
| 44 | pub fn wake(&mut self) { | ||
| 45 | if let Some(w) = self.waker.take() { | ||
| 46 | w.wake() | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Utility struct to register and wake a waker. | ||
| 52 | pub struct AtomicWakerRegistration { | ||
| 53 | waker: Mutex<Cell<Option<Waker>>>, | ||
| 54 | } | ||
| 55 | |||
| 56 | impl AtomicWakerRegistration { | ||
| 57 | pub const fn new() -> Self { | ||
| 58 | Self { | ||
| 59 | waker: Mutex::new(Cell::new(None)), | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Register a waker. Overwrites the previous waker, if any. | ||
| 64 | pub fn register(&mut self, w: &Waker) { | ||
| 65 | cortex_m::interrupt::free(|cs| { | ||
| 66 | let cell = self.waker.borrow(cs); | ||
| 67 | cell.set(match cell.replace(None) { | ||
| 68 | Some(w2) if (w2.will_wake(w)) => Some(w2), | ||
| 69 | Some(w2) => { | ||
| 70 | w2.wake(); | ||
| 71 | Some(w.clone()) | ||
| 72 | } | ||
| 73 | None => Some(w.clone()), | ||
| 74 | }) | ||
| 75 | }) | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Wake the registered waker, if any. | ||
| 79 | pub fn wake(&mut self) { | ||
| 80 | cortex_m::interrupt::free(|cs| { | ||
| 81 | let cell = self.waker.borrow(cs); | ||
| 82 | if let Some(w) = cell.replace(None) { | ||
| 83 | w.wake() | ||
| 84 | } | ||
| 85 | }) | ||
| 86 | } | ||
| 87 | } | ||
