ruspiro_lock/sync/rwlock.rs
1/***********************************************************************************************************************
2 * Copyright (c) 2020 by the authors
3 *
4 * Author: André Borrmann <pspwizard@gmx.de>
5 * License: Apache License 2.0 / MIT
6 **********************************************************************************************************************/
7
8//! # RWLock
9//!
10
11use core::arch::asm;
12use core::cell::UnsafeCell;
13use core::fmt;
14use core::ops::{Deref, DerefMut};
15use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
16
17/// An exclusive access lock around the given data
18#[repr(C, align(16))]
19pub struct RWLock<T: ?Sized> {
20 /// indicates whether a mutual exclusive write lock exists
21 write_lock: AtomicBool,
22 /// counts existing read-locks, this could be used in future to mark the data as "dirty" if a write lock is aquired
23 /// whiled read access is also handed out. Should a write access request fail with existing read access ?
24 read_locks: AtomicU32,
25 data: UnsafeCell<T>,
26}
27
28/// Result of trying to access the data using ``try_lock`` or ``lock`` on the data lock. If the
29/// result goes out of scope the write lock is released.
30pub struct WriteLockGuard<'a, T: ?Sized + 'a> {
31 _data: &'a RWLock<T>,
32}
33
34/// Result of aquiring read access to the data using ``read`` on the data lock. If the
35/// result goes out of scope the read lock is released.
36pub struct ReadLockGuard<'a, T: ?Sized + 'a> {
37 _data: &'a RWLock<T>,
38}
39
40impl<T> RWLock<T> {
41 /// Create a new data access guarding lock.
42 pub const fn new(value: T) -> Self {
43 RWLock {
44 write_lock: AtomicBool::new(false),
45 read_locks: AtomicU32::new(0),
46 data: UnsafeCell::new(value),
47 }
48 }
49}
50
51impl<T: ?Sized> RWLock<T> {
52 /// Try to provide a Writelock for mutual exclusive access. Returns ``None`` if the lock fails
53 /// or ``Some(WriteLockGuard)``. The actual data, the [WriteLockGuard] wraps could be conviniently accessed by
54 /// dereferencing it.
55 pub fn try_write(&self) -> Option<WriteLockGuard<T>> {
56 if self.read_locks.load(Ordering::Relaxed) > 0 {
57 // write lock can only be given if there is no concurrent ReadLock already
58 // existing
59 return None;
60 }
61 // do the atomic operation to set the lock
62 if !self.write_lock.swap(true, Ordering::Acquire) {
63 // has been false previously means we now have the lock
64
65 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
66 unsafe {
67 // dmb required before allow access to the protected resource, see:
68 // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
69 asm!("dmb sy");
70 }
71
72 Some(WriteLockGuard { _data: self })
73 } else {
74 // we couldn't set the lock
75 None
76 }
77 }
78
79 /// Provide a WriteLock for mutual exclusive access. This blocks until the data could be
80 /// successfully locked. This also implies that there is no concurrent [ReadLockGuard] existing.
81 /// The locked data will be returned as [WriteLockGuard]. Simply derefrencing
82 /// this allows access to the contained data value.
83 ///
84 pub fn write(&self) -> WriteLockGuard<T> {
85 loop {
86 if let Some(write_guard) = self.try_write() {
87 //println!("write lock aquired {:?}", core::any::type_name::<T>());
88 return write_guard;
89 }
90 // to save energy and cpu consumption we can wait for an event beeing raised that indicates that the
91 // semaphore value has likely beeing changed
92 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
93 unsafe {
94 asm!("wfe");
95 }
96 }
97 }
98
99 /// Provide a ReadLock to the wrapped data. This call blocks until the recource is available.
100 /// There can be as many concurrent [ReadLockGuard]s being handed out if there is no [WriteLockGuard] to the
101 /// same resource already existing.
102 pub fn try_read(&self) -> Option<ReadLockGuard<T>> {
103 // read locks can only handed out if no write lock is existing already
104 if self.write_lock.load(Ordering::Relaxed) {
105 None
106 } else {
107 self.read_locks.fetch_add(1, Ordering::Acquire);
108 //println!("read lock aquired {:?}", core::any::type_name::<T>());
109 Some(ReadLockGuard { _data: self })
110 }
111 }
112
113 /// Provide a ReadLock to the wrapped data. This call blocks until the recource is available.
114 /// There can be as many concurrent [ReadLockGuard]s being handed out if there is no [WriteLockGuard] to the
115 /// same resource already existing.
116 pub fn read(&self) -> ReadLockGuard<T> {
117 // read locks can only handed out if no write lock is existing already
118 loop {
119 if let Some(read_guard) = self.try_read() {
120 //println!("write lock aquired {:?}", core::any::type_name::<T>());
121 return read_guard;
122 }
123
124 // to save energy and cpu consumption we can wait for an event beeing raised that indicates that the
125 // lock value has likely beeing changed
126 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
127 unsafe {
128 asm!("wfe");
129 }
130 }
131 }
132
133 /// Provide an immutable borrow to the data secured by the RWLock.
134 ///
135 /// # Safety
136 /// This is only safe if it is guarantied that there is exactly only one call to this function or any other
137 /// accessor of the RWLock until the returned borrow goes out of scope.
138 pub unsafe fn as_ref_unchecked(&self) -> &T {
139 &*self.data.get()
140 }
141
142 /// Consume the Mutex and return the inner value
143 pub fn into_inner(self) -> T
144 where
145 T: Sized,
146 {
147 self.data.into_inner()
148 }
149}
150
151impl<T: ?Sized + fmt::Debug> fmt::Debug for RWLock<T>
152where
153 T: fmt::Debug,
154{
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 let mut dbg = f.debug_struct("DataLock");
157 match self.try_read() {
158 Some(guard) => {
159 dbg.field("Value", &&*guard);
160 }
161 _ => {
162 dbg.field("Value", &"unable to r-lock");
163 }
164 }
165 dbg.field("ReadLocks", &self.read_locks);
166 dbg.finish_non_exhaustive()
167 }
168}
169
170// when the WriteLockGuard is dropped release the owning lock
171impl<T: ?Sized> Drop for WriteLockGuard<'_, T> {
172 fn drop(&mut self) {
173 self._data.write_lock.store(false, Ordering::Release);
174 //println!("write lock released {:?}", core::any::type_name::<T>());
175
176 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
177 unsafe {
178 // dmb required before allow access to the protected resource, see:
179 // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
180 asm!("dmb sy");
181 // also raise a signal to indicate the semaphore has been changed (this trigger all WFE's to continue
182 // processing) but do data syncronisation barrier upfront to ensure any data updates has been finished
183 asm!(
184 "dsb sy
185 sev"
186 );
187 }
188 }
189}
190
191// when the ReadLockGuard is dropped release the owning lock
192impl<T: ?Sized> Drop for ReadLockGuard<'_, T> {
193 fn drop(&mut self) {
194 self._data.read_locks.fetch_sub(1, Ordering::Release);
195 //println!("read lock released {:?}", core::any::type_name::<T>());
196
197 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
198 unsafe {
199 // dmb required after atomic operations, see:
200 // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
201 asm!("dmb sy");
202 }
203 }
204}
205
206// dereferencing the value contained in the DataWriteLock
207// this is ok as the DataWriteLock does only exist if the exclusive access to the data could
208// be ensured. Therefore also only one ``WriteLockGuard`` could ever exist for one specific ``RWLock``, which makes
209// it safe to return immutable and mutable references.
210impl<T: ?Sized> Deref for WriteLockGuard<'_, T> {
211 type Target = T;
212
213 fn deref(&self) -> &T {
214 unsafe { &*self._data.data.get() }
215 }
216}
217
218impl<T: ?Sized> DerefMut for WriteLockGuard<'_, T> {
219 fn deref_mut(&mut self) -> &mut T {
220 unsafe { &mut *self._data.data.get() }
221 }
222}
223
224// the ``ReadLockGuard`` can only be immutable dereferenced
225impl<T: ?Sized> Deref for ReadLockGuard<'_, T> {
226 type Target = T;
227
228 fn deref(&self) -> &T {
229 unsafe { &*self._data.data.get() }
230 }
231}
232
233/// implement debug trait to forward to the type wrapped within the guard
234impl<T: ?Sized + fmt::Debug> fmt::Debug for WriteLockGuard<'_, T> {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 fmt::Debug::fmt(&**self, f)
237 }
238}
239
240/// implement debug trait to forward to the type wrapped within the guard
241impl<T: ?Sized + fmt::Debug> fmt::Debug for ReadLockGuard<'_, T> {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 fmt::Debug::fmt(&**self, f)
244 }
245}
246
247/// The RWLock is always `Sync`, to make it `Send` as well it need to be wrapped into an `Arc`.
248unsafe impl<T: ?Sized + Send> Sync for RWLock<T> {}
249
250#[cfg(testing)]
251mod tests {
252 extern crate alloc;
253 use super::*;
254 use alloc::sync::Arc;
255
256 #[test]
257 fn only_one_write_lock() {
258 let rwlock = Arc::new(RWLock::new(0u32));
259 let rwlock_clone = Arc::clone(&rwlock);
260 // try_lock and lock will provide a WriteLock
261 let mut data = rwlock.write();
262 *data = 20;
263 // if a write lock exists no read lock's could be aquired
264 assert!(rwlock_clone.try_write().is_none());
265 }
266
267 #[test]
268 fn only_one_write_no_readlock() {
269 let rwlock = Arc::new(RWLock::new(0u32));
270 let rwlock_clone = Arc::clone(&rwlock);
271 // try_lock and lock will provide a WriteLock
272 let mut data = rwlock.write();
273 *data = 20;
274 // if a write lock exists no read lock's could be aquired
275 assert!(rwlock_clone.try_read().is_none());
276 }
277
278 #[test]
279 fn only_multiple_readlocks() {
280 let rwlock = Arc::new(RWLock::new(0u32));
281 let rwlock_clone = Arc::clone(&rwlock);
282 // try_lock and lock will provide a WriteLock
283 let data = rwlock.read();
284 // if a write lock exists no read lock's could be aquired
285 assert!(rwlock_clone.try_read().is_some());
286 println!("{}", *data);
287 }
288
289 #[test]
290 fn only_read_no_write_lock() {
291 let rwlock = Arc::new(RWLock::new(0u32));
292 let rwlock_clone = Arc::clone(&rwlock);
293 // try_lock and lock will provide a WriteLock
294 let data = rwlock.read();
295 // if a write lock exists no read lock's could be aquired
296 assert!(rwlock_clone.try_write().is_none());
297 println!("{}", *data);
298 }
299}