aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2021-03-17 01:55:01 +0100
committerDario Nieuwenhuis <[email protected]>2021-03-17 02:53:41 +0100
commitbb68f5d0e83eec609ef0016baa4710b30ce49d62 (patch)
tree3da1b55c82e76e53c7953f0457cfc618af76f3e2
parentab01e0be3b2faa320ffc169a6aa629fe44a2080c (diff)
Add optimized single-word WakerRegistration, add AtomicWakerRegistration.
-rw-r--r--embassy/Cargo.toml2
-rw-r--r--embassy/src/util/mod.rs2
-rw-r--r--embassy/src/util/waker.rs75
-rw-r--r--embassy/src/util/waker_agnostic.rs87
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 = []
13defmt-warn = [] 13defmt-warn = []
14defmt-error = [] 14defmt-error = []
15 15
16executor-agnostic = []
17
16[dependencies] 18[dependencies]
17defmt = { version = "0.2.0", optional = true } 19defmt = { version = "0.2.0", optional = true }
18log = { version = "0.4.11", optional = true } 20log = { 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;
3mod mutex; 3mod mutex;
4mod portal; 4mod portal;
5mod signal; 5mod signal;
6
7#[cfg_attr(feature = "executor-agnostic", path = "waker_agnostic.rs")]
6mod waker; 8mod waker;
7 9
8pub use drop_bomb::*; 10pub 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 @@
1use core::mem; 1use core::ptr::{self, NonNull};
2use core::task::Context;
3use core::task::Waker; 2use core::task::Waker;
4 3
4use atomic_polyfill::{AtomicPtr, Ordering};
5
6use 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)]
7pub struct WakerRegistration { 10pub struct WakerRegistration {
8 waker: Option<Waker>, 11 waker: Option<NonNull<Task>>,
9} 12}
10 13
11impl WakerRegistration { 14impl 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
51pub struct AtomicWakerRegistration {
52 waker: AtomicPtr<Task>,
53}
54
55impl 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 @@
1use core::cell::Cell;
2use core::mem;
3use core::task::Waker;
4
5use cortex_m::interrupt::Mutex;
6
7/// Utility struct to register and wake a waker.
8#[derive(Debug)]
9pub struct WakerRegistration {
10 waker: Option<Waker>,
11}
12
13impl 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.
52pub struct AtomicWakerRegistration {
53 waker: Mutex<Cell<Option<Waker>>>,
54}
55
56impl 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}