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}