aboutsummaryrefslogtreecommitdiff
path: root/embassy-sync/src
diff options
context:
space:
mode:
authorAlix ANNERAUD <[email protected]>2025-02-28 16:32:12 +0100
committerAlix ANNERAUD <[email protected]>2025-02-28 16:32:12 +0100
commit33cf27adf646bacd758b7289ebbf460b24c26fa5 (patch)
treee9a82c6e171c42b067e13dee47a80cbed59ec368 /embassy-sync/src
parenta7ecf14259591ff5b324ec1c7d7c521fabebe7d3 (diff)
Refactor blocking read-write lock module structure and improve assertions in ThreadModeRawRwLock
Diffstat (limited to 'embassy-sync/src')
-rw-r--r--embassy-sync/src/blocking_rwlock/mod.rs6
-rw-r--r--embassy-sync/src/blocking_rwlock/raw.rs12
-rw-r--r--embassy-sync/src/rwlock.rs376
3 files changed, 265 insertions, 129 deletions
diff --git a/embassy-sync/src/blocking_rwlock/mod.rs b/embassy-sync/src/blocking_rwlock/mod.rs
index a304b77d6..a8fb7d6bc 100644
--- a/embassy-sync/src/blocking_rwlock/mod.rs
+++ b/embassy-sync/src/blocking_rwlock/mod.rs
@@ -1,11 +1,11 @@
1//! Blocking read-write lock. 1//! Blocking read-write lock.
2//! 2//!
3//! This module provides a blocking read-write lock that can be used to synchronize data. 3//! This module provides a blocking read-write lock that can be used to synchronize data.
4pub mod raw_rwlock; 4pub mod raw;
5 5
6use core::cell::UnsafeCell; 6use core::cell::UnsafeCell;
7 7
8use self::raw_rwlock::RawRwLock; 8use self::raw::RawRwLock;
9 9
10/// Blocking read-write lock (not async) 10/// Blocking read-write lock (not async)
11/// 11///
@@ -218,4 +218,4 @@ mod thread_mode_rwlock {
218 // Drop of the inner `T` happens after this. 218 // Drop of the inner `T` happens after this.
219 } 219 }
220 } 220 }
221} \ No newline at end of file 221}
diff --git a/embassy-sync/src/blocking_rwlock/raw.rs b/embassy-sync/src/blocking_rwlock/raw.rs
index 85e8374b5..7725edfa5 100644
--- a/embassy-sync/src/blocking_rwlock/raw.rs
+++ b/embassy-sync/src/blocking_rwlock/raw.rs
@@ -126,13 +126,19 @@ mod thread_mode {
126 unsafe impl RawRwLock for ThreadModeRawRwLock { 126 unsafe impl RawRwLock for ThreadModeRawRwLock {
127 const INIT: Self = Self::new(); 127 const INIT: Self = Self::new();
128 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R { 128 fn read_lock<R>(&self, f: impl FnOnce() -> R) -> R {
129 assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); 129 assert!(
130 in_thread_mode(),
131 "ThreadModeRwLock can only be locked from thread mode."
132 );
130 133
131 f() 134 f()
132 } 135 }
133 136
134 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R { 137 fn write_lock<R>(&self, f: impl FnOnce() -> R) -> R {
135 assert!(in_thread_mode(), "ThreadModeRwLock can only be locked from thread mode."); 138 assert!(
139 in_thread_mode(),
140 "ThreadModeRwLock can only be locked from thread mode."
141 );
136 142
137 f() 143 f()
138 } 144 }
@@ -156,4 +162,4 @@ mod thread_mode {
156 } 162 }
157} 163}
158#[cfg(any(cortex_m, feature = "std"))] 164#[cfg(any(cortex_m, feature = "std"))]
159pub use thread_mode::*; \ No newline at end of file 165pub use thread_mode::*;
diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs
index 30e1e74ad..365f6fda5 100644
--- a/embassy-sync/src/rwlock.rs
+++ b/embassy-sync/src/rwlock.rs
@@ -1,134 +1,160 @@
1use core::cell::UnsafeCell; 1//! Async read-write lock.
2use core::future::poll_fn; 2//!
3//! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks.
4use core::cell::{RefCell, UnsafeCell};
5use core::future::{poll_fn, Future};
3use core::ops::{Deref, DerefMut}; 6use core::ops::{Deref, DerefMut};
4use core::task::Poll; 7use core::task::Poll;
8use core::{fmt, mem};
5 9
6use crate::blocking_mutex::Mutex as BlockingMutex; 10use crate::blocking_mutex::raw::RawRwLock;
11use crate::blocking_mutex::RwLock as BlockingRwLock;
7use crate::waitqueue::WakerRegistration; 12use crate::waitqueue::WakerRegistration;
8use crate::raw_rwlock::RawRwLock;
9 13
10pub struct RwLock<M, T> 14/// Error returned by [`RwLock::try_read_lock`] and [`RwLock::try_write_lock`]
15#[derive(PartialEq, Eq, Clone, Copy, Debug)]
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17pub struct TryLockError;
18
19struct State {
20 readers: usize,
21 writer: bool,
22 waker: WakerRegistration,
23}
24
25/// Async read-write lock.
26///
27/// The read-write lock is generic over a blocking [`RawRwLock`](crate::blocking_mutex::raw_rwlock::RawRwLock).
28/// The raw read-write lock is used to guard access to the internal state. It
29/// is held for very short periods only, while locking and unlocking. It is *not* held
30/// for the entire time the async RwLock is locked.
31///
32/// Which implementation you select depends on the context in which you're using the read-write lock.
33///
34/// Use [`CriticalSectionRawRwLock`](crate::blocking_mutex::raw_rwlock::CriticalSectionRawRwLock) when data can be shared between threads and interrupts.
35///
36/// Use [`NoopRawRwLock`](crate::blocking_mutex::raw_rwlock::NoopRawRwLock) when data is only shared between tasks running on the same executor.
37///
38/// Use [`ThreadModeRawRwLock`](crate::blocking_mutex::raw_rwlock::ThreadModeRawRwLock) when data is shared between tasks running on the same executor but you want a singleton.
39///
40pub struct RwLock<R, T>
11where 41where
12 M: RawRwLock, 42 R: RawRwLock,
13 T: ?Sized, 43 T: ?Sized,
14{ 44{
15 state: BlockingMutex<M, RwLockState>, 45 state: BlockingRwLock<R, RefCell<State>>,
16 inner: UnsafeCell<T>, 46 inner: UnsafeCell<T>,
17} 47}
18 48
19unsafe impl<M: RawRwLock + Send, T: ?Sized + Send> Send for RwLock<M, T> {} 49unsafe impl<R: RawRwLock + Send, T: ?Sized + Send> Send for RwLock<R, T> {}
20unsafe impl<M: RawRwLock + Sync, T: ?Sized + Send> Sync for RwLock<M, T> {} 50unsafe impl<R: RawRwLock + Sync, T: ?Sized + Send> Sync for RwLock<R, T> {}
21 51
22impl<M, T> RwLock<M, T> 52/// Async read-write lock.
53impl<R, T> RwLock<R, T>
23where 54where
24 M: RawRwLock, 55 R: RawRwLock,
25{ 56{
57 /// Create a new read-write lock with the given value.
26 pub const fn new(value: T) -> Self { 58 pub const fn new(value: T) -> Self {
27 Self { 59 Self {
28 inner: UnsafeCell::new(value), 60 inner: UnsafeCell::new(value),
29 state: BlockingMutex::new(RwLockState { 61 state: BlockingRwLock::new(RefCell::new(State {
30 locked: LockedState::Unlocked, 62 readers: 0,
31 writer_pending: 0, 63 writer: false,
32 readers_pending: 0,
33 waker: WakerRegistration::new(), 64 waker: WakerRegistration::new(),
34 }), 65 })),
35 } 66 }
36 } 67 }
37} 68}
38 69
39impl<M, T> RwLock<M, T> 70impl<R, T> RwLock<R, T>
40where 71where
41 M: RawRwLock, 72 R: RawRwLock,
42 T: ?Sized, 73 T: ?Sized,
43{ 74{
44 pub fn read(&self) -> impl Future<Output = RwLockReadGuard<'_, M, T>> { 75 /// Lock the read-write lock for reading.
76 ///
77 /// This will wait for the lock to be available if it's already locked for writing.
78 pub fn read_lock(&self) -> impl Future<Output = RwLockReadGuard<'_, R, T>> {
45 poll_fn(|cx| { 79 poll_fn(|cx| {
46 let ready = self.state.lock(|s| { 80 let ready = self.state.lock(|s| {
47 let mut s = s.borrow_mut(); 81 let mut s = s.borrow_mut();
48 match s.locked { 82 if s.writer {
49 LockedState::Unlocked => { 83 s.waker.register(cx.waker());
50 s.locked = LockedState::ReadLocked(1); 84 false
51 true 85 } else {
52 } 86 s.readers += 1;
53 LockedState::ReadLocked(ref mut count) => { 87 true
54 *count += 1;
55 true
56 }
57 LockedState::WriteLocked => {
58 s.readers_pending += 1;
59 s.waker.register(cx.waker());
60 false
61 }
62 } 88 }
63 }); 89 });
64 90
65 if ready { 91 if ready {
66 Poll::Ready(RwLockReadGuard { lock: self }) 92 Poll::Ready(RwLockReadGuard { rwlock: self })
67 } else { 93 } else {
68 Poll::Pending 94 Poll::Pending
69 } 95 }
70 }) 96 })
71 } 97 }
72 98
73 pub fn write(&self) -> impl Future<Output = RwLockWriteGuard<'_, M, T>> { 99 /// Lock the read-write lock for writing.
100 ///
101 /// This will wait for the lock to be available if it's already locked for reading or writing.
102 pub fn write_lock(&self) -> impl Future<Output = RwLockWriteGuard<'_, R, T>> {
74 poll_fn(|cx| { 103 poll_fn(|cx| {
75 let ready = self.state.lock(|s| { 104 let ready = self.state.lock(|s| {
76 let mut s = s.borrow_mut(); 105 let mut s = s.borrow_mut();
77 match s.locked { 106 if s.readers > 0 || s.writer {
78 LockedState::Unlocked => { 107 s.waker.register(cx.waker());
79 s.locked = LockedState::WriteLocked; 108 false
80 true 109 } else {
81 } 110 s.writer = true;
82 _ => { 111 true
83 s.writer_pending += 1;
84 s.waker.register(cx.waker());
85 false
86 }
87 } 112 }
88 }); 113 });
89 114
90 if ready { 115 if ready {
91 Poll::Ready(RwLockWriteGuard { lock: self }) 116 Poll::Ready(RwLockWriteGuard { rwlock: self })
92 } else { 117 } else {
93 Poll::Pending 118 Poll::Pending
94 } 119 }
95 }) 120 })
96 } 121 }
97 122
98 pub fn try_read(&self) -> Result<RwLockReadGuard<'_, M, T>, TryLockError> { 123 /// Attempt to immediately lock the read-write lock for reading.
124 ///
125 /// If the lock is already locked for writing, this will return an error instead of waiting.
126 pub fn try_read_lock(&self) -> Result<RwLockReadGuard<'_, R, T>, TryLockError> {
99 self.state.lock(|s| { 127 self.state.lock(|s| {
100 let mut s = s.borrow_mut(); 128 let mut s = s.borrow_mut();
101 match s.locked { 129 if s.writer {
102 LockedState::Unlocked => { 130 Err(TryLockError)
103 s.locked = LockedState::ReadLocked(1); 131 } else {
104 Ok(()) 132 s.readers += 1;
105 } 133 Ok(())
106 LockedState::ReadLocked(ref mut count) => {
107 *count += 1;
108 Ok(())
109 }
110 LockedState::WriteLocked => Err(TryLockError),
111 } 134 }
112 })?; 135 })?;
113 136
114 Ok(RwLockReadGuard { lock: self }) 137 Ok(RwLockReadGuard { rwlock: self })
115 } 138 }
116 139
117 pub fn try_write(&self) -> Result<RwLockWriteGuard<'_, M, T>, TryLockError> { 140 /// Attempt to immediately lock the read-write lock for writing.
141 ///
142 /// If the lock is already locked for reading or writing, this will return an error instead of waiting.
143 pub fn try_write_lock(&self) -> Result<RwLockWriteGuard<'_, R, T>, TryLockError> {
118 self.state.lock(|s| { 144 self.state.lock(|s| {
119 let mut s = s.borrow_mut(); 145 let mut s = s.borrow_mut();
120 match s.locked { 146 if s.readers > 0 || s.writer {
121 LockedState::Unlocked => { 147 Err(TryLockError)
122 s.locked = LockedState::WriteLocked; 148 } else {
123 Ok(()) 149 s.writer = true;
124 } 150 Ok(())
125 _ => Err(TryLockError),
126 } 151 }
127 })?; 152 })?;
128 153
129 Ok(RwLockWriteGuard { lock: self }) 154 Ok(RwLockWriteGuard { rwlock: self })
130 } 155 }
131 156
157 /// Consumes this read-write lock, returning the underlying data.
132 pub fn into_inner(self) -> T 158 pub fn into_inner(self) -> T
133 where 159 where
134 T: Sized, 160 T: Sized,
@@ -136,20 +162,24 @@ where
136 self.inner.into_inner() 162 self.inner.into_inner()
137 } 163 }
138 164
165 /// Returns a mutable reference to the underlying data.
166 ///
167 /// Since this call borrows the RwLock mutably, no actual locking needs to
168 /// take place -- the mutable borrow statically guarantees no locks exist.
139 pub fn get_mut(&mut self) -> &mut T { 169 pub fn get_mut(&mut self) -> &mut T {
140 self.inner.get_mut() 170 self.inner.get_mut()
141 } 171 }
142} 172}
143 173
144impl<M: RawRwLock, T> From<T> for RwLock<M, T> { 174impl<R: RawRwLock, T> From<T> for RwLock<R, T> {
145 fn from(from: T) -> Self { 175 fn from(from: T) -> Self {
146 Self::new(from) 176 Self::new(from)
147 } 177 }
148} 178}
149 179
150impl<M, T> Default for RwLock<M, T> 180impl<R, T> Default for RwLock<R, T>
151where 181where
152 M: RawRwLock, 182 R: RawRwLock,
153 T: Default, 183 T: Default,
154{ 184{
155 fn default() -> Self { 185 fn default() -> Self {
@@ -157,103 +187,203 @@ where
157 } 187 }
158} 188}
159 189
160pub struct RwLockReadGuard<'a, M, T> 190impl<R, T> fmt::Debug for RwLock<R, T>
191where
192 R: RawRwLock,
193 T: ?Sized + fmt::Debug,
194{
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 let mut d = f.debug_struct("RwLock");
197 match self.try_write_lock() {
198 Ok(value) => {
199 d.field("inner", &&*value);
200 }
201 Err(TryLockError) => {
202 d.field("inner", &format_args!("<locked>"));
203 }
204 }
205
206 d.finish_non_exhaustive()
207 }
208}
209
210/// Async read lock guard.
211///
212/// Owning an instance of this type indicates having
213/// successfully locked the read-write lock for reading, and grants access to the contents.
214///
215/// Dropping it unlocks the read-write lock.
216#[clippy::has_significant_drop]
217#[must_use = "if unused the RwLock will immediately unlock"]
218pub struct RwLockReadGuard<'a, R, T>
161where 219where
162 M: RawRwLock, 220 R: RawRwLock,
163 T: ?Sized, 221 T: ?Sized,
164{ 222{
165 lock: &'a RwLock<M, T>, 223 rwlock: &'a RwLock<R, T>,
166} 224}
167 225
168impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> 226impl<'a, R, T> Drop for RwLockReadGuard<'a, R, T>
169where 227where
170 M: RawRwLock, 228 R: RawRwLock,
171 T: ?Sized, 229 T: ?Sized,
172{ 230{
173 type Target = T; 231 fn drop(&mut self) {
232 self.rwlock.state.lock(|s| {
233 let mut s = unwrap!(s.try_borrow_mut());
234 s.readers -= 1;
235 if s.readers == 0 {
236 s.waker.wake();
237 }
238 })
239 }
240}
174 241
242impl<'a, R, T> Deref for RwLockReadGuard<'a, R, T>
243where
244 R: RawRwLock,
245 T: ?Sized,
246{
247 type Target = T;
175 fn deref(&self) -> &Self::Target { 248 fn deref(&self) -> &Self::Target {
176 unsafe { &*self.lock.inner.get() } 249 // Safety: the RwLockReadGuard represents shared access to the contents
250 // of the read-write lock, so it's OK to get it.
251 unsafe { &*(self.rwlock.inner.get() as *const T) }
177 } 252 }
178} 253}
179 254
180impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> 255impl<'a, R, T> fmt::Debug for RwLockReadGuard<'a, R, T>
181where 256where
182 M: RawRwLock, 257 R: RawRwLock,
183 T: ?Sized, 258 T: ?Sized + fmt::Debug,
184{ 259{
185 fn drop(&mut self) { 260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 self.lock.state.lock(|s| { 261 fmt::Debug::fmt(&**self, f)
187 let mut s = s.borrow_mut();
188 match s.locked {
189 LockedState::ReadLocked(ref mut count) => {
190 *count -= 1;
191 if *count == 0 {
192 s.locked = LockedState::Unlocked;
193 s.waker.wake();
194 }
195 }
196 _ => unreachable!(),
197 }
198 });
199 } 262 }
200} 263}
201 264
202pub struct RwLockWriteGuard<'a, M, T> 265impl<'a, R, T> fmt::Display for RwLockReadGuard<'a, R, T>
203where 266where
204 M: RawRwLock, 267 R: RawRwLock,
268 T: ?Sized + fmt::Display,
269{
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 fmt::Display::fmt(&**self, f)
272 }
273}
274
275/// Async write lock guard.
276///
277/// Owning an instance of this type indicates having
278/// successfully locked the read-write lock for writing, and grants access to the contents.
279///
280/// Dropping it unlocks the read-write lock.
281#[clippy::has_significant_drop]
282#[must_use = "if unused the RwLock will immediately unlock"]
283pub struct RwLockWriteGuard<'a, R, T>
284where
285 R: RawRwLock,
205 T: ?Sized, 286 T: ?Sized,
206{ 287{
207 lock: &'a RwLock<M, T>, 288 rwlock: &'a RwLock<R, T>,
208} 289}
209 290
210impl<'a, M, T> Deref for RwLockWriteGuard<'a, M, T> 291impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T>
211where 292where
212 M: RawRwLock, 293 R: RawRwLock,
213 T: ?Sized, 294 T: ?Sized,
214{ 295{
215 type Target = T; 296 fn drop(&mut self) {
297 self.rwlock.state.lock(|s| {
298 let mut s = unwrap!(s.try_borrow_mut());
299 s.writer = false;
300 s.waker.wake();
301 })
302 }
303}
216 304
305impl<'a, R, T> Deref for RwLockWriteGuard<'a, R, T>
306where
307 R: RawRwLock,
308 T: ?Sized,
309{
310 type Target = T;
217 fn deref(&self) -> &Self::Target { 311 fn deref(&self) -> &Self::Target {
218 unsafe { &*self.lock.inner.get() } 312 // Safety: the RwLockWriteGuard represents exclusive access to the contents
313 // of the read-write lock, so it's OK to get it.
314 unsafe { &*(self.rwlock.inner.get() as *mut T) }
219 } 315 }
220} 316}
221 317
222impl<'a, M, T> DerefMut for RwLockWriteGuard<'a, M, T> 318impl<'a, R, T> DerefMut for RwLockWriteGuard<'a, R, T>
223where 319where
224 M: RawRwLock, 320 R: RawRwLock,
225 T: ?Sized, 321 T: ?Sized,
226{ 322{
227 fn deref_mut(&mut self) -> &mut Self::Target { 323 fn deref_mut(&mut self) -> &mut Self::Target {
228 unsafe { &mut *self.lock.inner.get() } 324 // Safety: the RwLockWriteGuard represents exclusive access to the contents
325 // of the read-write lock, so it's OK to get it.
326 unsafe { &mut *(self.rwlock.inner.get()) }
229 } 327 }
230} 328}
231 329
232impl<'a, M, T> Drop for RwLockWriteGuard<'a, M, T> 330impl<'a, R, T> fmt::Debug for RwLockWriteGuard<'a, R, T>
233where 331where
234 M: RawRwLock, 332 R: RawRwLock,
235 T: ?Sized, 333 T: ?Sized + fmt::Debug,
236{ 334{
237 fn drop(&mut self) { 335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 self.lock.state.lock(|s| { 336 fmt::Debug::fmt(&**self, f)
239 let mut s = s.borrow_mut();
240 s.locked = LockedState::Unlocked;
241 s.waker.wake();
242 });
243 } 337 }
244} 338}
245 339
246struct RwLockState { 340impl<'a, R, T> fmt::Display for RwLockWriteGuard<'a, R, T>
247 locked: LockedState, 341where
248 writer_pending: usize, 342 R: RawRwLock,
249 readers_pending: usize, 343 T: ?Sized + fmt::Display,
250 waker: WakerRegistration, 344{
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 fmt::Display::fmt(&**self, f)
347 }
251} 348}
252 349
253enum LockedState { 350#[cfg(test)]
254 Unlocked, 351mod tests {
255 ReadLocked(usize), 352 use crate::blocking_mutex::raw_rwlock::NoopRawRwLock;
256 WriteLocked, 353 use crate::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
257}
258 354
259pub struct TryLockError; 355 #[futures_test::test]
356 async fn read_guard_releases_lock_when_dropped() {
357 let rwlock: RwLock<NoopRawRwLock, [i32; 2]> = RwLock::new([0, 1]);
358
359 {
360 let guard = rwlock.read_lock().await;
361 assert_eq!(*guard, [0, 1]);
362 }
363
364 {
365 let guard = rwlock.read_lock().await;
366 assert_eq!(*guard, [0, 1]);
367 }
368
369 assert_eq!(*rwlock.read_lock().await, [0, 1]);
370 }
371
372 #[futures_test::test]
373 async fn write_guard_releases_lock_when_dropped() {
374 let rwlock: RwLock<NoopRawRwLock, [i32; 2]> = RwLock::new([0, 1]);
375
376 {
377 let mut guard = rwlock.write_lock().await;
378 assert_eq!(*guard, [0, 1]);
379 guard[1] = 2;
380 }
381
382 {
383 let guard = rwlock.read_lock().await;
384 assert_eq!(*guard, [0, 2]);
385 }
386
387 assert_eq!(*rwlock.read_lock().await, [0, 2]);
388 }
389}