aboutsummaryrefslogtreecommitdiff
path: root/embassy-sync
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-sync')
-rw-r--r--embassy-sync/src/lib.rs1
-rw-r--r--embassy-sync/src/rwlock.rs256
-rw-r--r--embassy-sync/tests/rwlock.rs81
3 files changed, 338 insertions, 0 deletions
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs
index df0f5e815..5d91b4d9c 100644
--- a/embassy-sync/src/lib.rs
+++ b/embassy-sync/src/lib.rs
@@ -18,6 +18,7 @@ pub mod once_lock;
18pub mod pipe; 18pub mod pipe;
19pub mod priority_channel; 19pub mod priority_channel;
20pub mod pubsub; 20pub mod pubsub;
21pub mod rwlock;
21pub mod semaphore; 22pub mod semaphore;
22pub mod signal; 23pub mod signal;
23pub mod waitqueue; 24pub mod waitqueue;
diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs
new file mode 100644
index 000000000..15ea8468e
--- /dev/null
+++ b/embassy-sync/src/rwlock.rs
@@ -0,0 +1,256 @@
1use core::cell::RefCell;
2use core::future::{poll_fn, Future};
3use core::ops::{Deref, DerefMut};
4use core::task::Poll;
5
6use crate::blocking_mutex::raw::RawMutex;
7use crate::blocking_mutex::Mutex as BlockingMutex;
8use crate::waitqueue::MultiWakerRegistration;
9
10/// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`]
11#[derive(PartialEq, Eq, Clone, Copy, Debug)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct TryLockError;
14
15/// Async read-write lock.
16///
17/// The lock is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex).
18/// The raw mutex is used to guard access to the internal state. It
19/// is held for very short periods only, while locking and unlocking. It is *not* held
20/// for the entire time the async RwLock is locked.
21///
22/// Which implementation you select depends on the context in which you're using the lock.
23///
24/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts.
25///
26/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) when data is only shared between tasks running on the same executor.
27///
28/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton.
29///
30pub struct RwLock<M, T>
31where
32 M: RawMutex,
33 T: ?Sized,
34{
35 state: BlockingMutex<M, RefCell<State>>,
36 inner: RefCell<T>,
37}
38
39struct State {
40 readers: usize,
41 writer: bool,
42 writer_waker: MultiWakerRegistration<1>,
43 reader_wakers: MultiWakerRegistration<8>,
44}
45
46impl State {
47 fn new() -> Self {
48 Self {
49 readers: 0,
50 writer: false,
51 writer_waker: MultiWakerRegistration::new(),
52 reader_wakers: MultiWakerRegistration::new(),
53 }
54 }
55}
56
57impl<M, T> RwLock<M, T>
58where
59 M: RawMutex,
60{
61 /// Create a new read-write lock with the given value.
62 pub const fn new(value: T) -> Self {
63 Self {
64 inner: RefCell::new(value),
65 state: BlockingMutex::new(RefCell::new(State::new())),
66 }
67 }
68}
69
70impl<M, T> RwLock<M, T>
71where
72 M: RawMutex,
73 T: ?Sized,
74{
75 /// Acquire a read lock.
76 ///
77 /// This will wait for the lock to be available if it's already locked for writing.
78 pub fn read(&self) -> impl Future<Output = RwLockReadGuard<'_, M, T>> {
79 poll_fn(|cx| {
80 let mut state = self.state.lock(|s| s.borrow_mut());
81 if state.writer {
82 state.reader_wakers.register(cx.waker());
83 Poll::Pending
84 } else {
85 state.readers += 1;
86 Poll::Ready(RwLockReadGuard { lock: self })
87 }
88 })
89 }
90
91 /// Acquire a write lock.
92 ///
93 /// This will wait for the lock to be available if it's already locked for reading or writing.
94 pub fn write(&self) -> impl Future<Output = RwLockWriteGuard<'_, M, T>> {
95 poll_fn(|cx| {
96 let mut state = self.state.lock(|s| s.borrow_mut());
97 if state.writer || state.readers > 0 {
98 state.writer_waker.register(cx.waker());
99 Poll::Pending
100 } else {
101 state.writer = true;
102 Poll::Ready(RwLockWriteGuard { lock: self })
103 }
104 })
105 }
106
107 /// Attempt to immediately acquire a read lock.
108 ///
109 /// If the lock is already locked for writing, this will return an error instead of waiting.
110 pub fn try_read(&self) -> Result<RwLockReadGuard<'_, M, T>, TryLockError> {
111 let mut state = self.state.lock(|s| s.borrow_mut());
112 if state.writer {
113 Err(TryLockError)
114 } else {
115 state.readers += 1;
116 Ok(RwLockReadGuard { lock: self })
117 }
118 }
119
120 /// Attempt to immediately acquire a write lock.
121 ///
122 /// If the lock is already locked for reading or writing, this will return an error instead of waiting.
123 pub fn try_write(&self) -> Result<RwLockWriteGuard<'_, M, T>, TryLockError> {
124 let mut state = self.state.lock(|s| s.borrow_mut());
125 if state.writer || state.readers > 0 {
126 Err(TryLockError)
127 } else {
128 state.writer = true;
129 Ok(RwLockWriteGuard { lock: self })
130 }
131 }
132
133 /// Consumes this lock, returning the underlying data.
134 pub fn into_inner(self) -> T
135 where
136 T: Sized,
137 {
138 self.inner.into_inner()
139 }
140
141 /// Returns a mutable reference to the underlying data.
142 ///
143 /// Since this call borrows the RwLock mutably, no actual locking needs to
144 /// take place -- the mutable borrow statically guarantees no locks exist.
145 pub fn get_mut(&mut self) -> &mut T {
146 self.inner.get_mut()
147 }
148}
149
150impl<M, T> From<T> for RwLock<M, T>
151where
152 M: RawMutex,
153{
154 fn from(from: T) -> Self {
155 Self::new(from)
156 }
157}
158
159impl<M, T> Default for RwLock<M, T>
160where
161 M: RawMutex,
162 T: Default,
163{
164 fn default() -> Self {
165 Self::new(Default::default())
166 }
167}
168
169/// Async read lock guard.
170///
171/// Owning an instance of this type indicates having
172/// successfully locked the RwLock for reading, and grants access to the contents.
173///
174/// Dropping it unlocks the RwLock.
175#[must_use = "if unused the RwLock will immediately unlock"]
176pub struct RwLockReadGuard<'a, M, T>
177where
178 M: RawMutex,
179 T: ?Sized,
180{
181 lock: &'a RwLock<M, T>,
182}
183
184impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T>
185where
186 M: RawMutex,
187 T: ?Sized,
188{
189 fn drop(&mut self) {
190 let mut state = self.lock.state.lock(|s| s.borrow_mut());
191 state.readers -= 1;
192 if state.readers == 0 {
193 state.writer_waker.wake();
194 }
195 }
196}
197
198impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T>
199where
200 M: RawMutex,
201 T: ?Sized,
202{
203 type Target = T;
204 fn deref(&self) -> &Self::Target {
205 self.lock.inner.borrow()
206 }
207}
208
209/// Async write lock guard.
210///
211/// Owning an instance of this type indicates having
212/// successfully locked the RwLock for writing, and grants access to the contents.
213///
214/// Dropping it unlocks the RwLock.
215#[must_use = "if unused the RwLock will immediately unlock"]
216pub struct RwLockWriteGuard<'a, M, T>
217where
218 M: RawMutex,
219 T: ?Sized,
220{
221 lock: &'a RwLock<M, T>,
222}
223
224impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T>
225where
226 M: RawMutex,
227 T: ?Sized,
228{
229 fn drop(&mut self) {
230 let mut state = self.lock.state.lock(|s| s.borrow_mut());
231 state.writer = false;
232 state.reader_wakers.wake();
233 state.writer_waker.wake();
234 }
235}
236
237impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T>
238where
239 M: RawMutex,
240 T: ?Sized,
241{
242 type Target = T;
243 fn deref(&self) -> &Self::Target {
244 self.lock.inner.borrow()
245 }
246}
247
248impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T>
249where
250 M: RawMutex,
251 T: ?Sized,
252{
253 fn deref_mut(&mut self) -> &mut Self::Target {
254 self.lock.inner.borrow_mut()
255 }
256}
diff --git a/embassy-sync/tests/rwlock.rs b/embassy-sync/tests/rwlock.rs
new file mode 100644
index 000000000..301cb0492
--- /dev/null
+++ b/embassy-sync/tests/rwlock.rs
@@ -0,0 +1,81 @@
1use embassy_sync::blocking_mutex::raw::NoopRawMutex;
2use embassy_sync::rwlock::RwLock;
3use futures_executor::block_on;
4
5#[futures_test::test]
6async fn test_rwlock_read() {
7 let lock = RwLock::<NoopRawMutex, _>::new(5);
8
9 {
10 let read_guard = lock.read().await;
11 assert_eq!(*read_guard, 5);
12 }
13
14 {
15 let read_guard = lock.read().await;
16 assert_eq!(*read_guard, 5);
17 }
18}
19
20#[futures_test::test]
21async fn test_rwlock_write() {
22 let lock = RwLock::<NoopRawMutex, _>::new(5);
23
24 {
25 let mut write_guard = lock.write().await;
26 *write_guard = 10;
27 }
28
29 {
30 let read_guard = lock.read().await;
31 assert_eq!(*read_guard, 10);
32 }
33}
34
35#[futures_test::test]
36async fn test_rwlock_try_read() {
37 let lock = RwLock::<NoopRawMutex, _>::new(5);
38
39 {
40 let read_guard = lock.try_read().unwrap();
41 assert_eq!(*read_guard, 5);
42 }
43
44 {
45 let read_guard = lock.try_read().unwrap();
46 assert_eq!(*read_guard, 5);
47 }
48}
49
50#[futures_test::test]
51async fn test_rwlock_try_write() {
52 let lock = RwLock::<NoopRawMutex, _>::new(5);
53
54 {
55 let mut write_guard = lock.try_write().unwrap();
56 *write_guard = 10;
57 }
58
59 {
60 let read_guard = lock.try_read().unwrap();
61 assert_eq!(*read_guard, 10);
62 }
63}
64
65#[futures_test::test]
66async fn test_rwlock_fairness() {
67 let lock = RwLock::<NoopRawMutex, _>::new(5);
68
69 let read1 = lock.read().await;
70 let read2 = lock.read().await;
71
72 let write_fut = lock.write();
73 futures_util::pin_mut!(write_fut);
74
75 assert!(futures_util::poll!(write_fut.as_mut()).is_pending());
76
77 drop(read1);
78 drop(read2);
79
80 assert!(futures_util::poll!(write_fut.as_mut()).is_ready());
81}