maitake_sync/
rwlock.rs

1//! An asynchronous [readers-writer lock].
2//!
3//! See the documentation for the [`RwLock`] type for details.
4//!
5//! [readers-writer lock]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
6use super::semaphore::{self, Semaphore};
7use crate::{
8    blocking::RawMutex,
9    loom::cell::{self, UnsafeCell},
10    spin::Spinlock,
11    util::fmt,
12};
13use core::ops::{Deref, DerefMut};
14
15#[cfg(test)]
16mod tests;
17
18/// An asynchronous [readers-writer lock].
19///
20/// This type of lock protects shared data by allowing either multiple
21/// concurrent readers (shared access), or a single writer (exclusive access) at
22/// a given point in time. If the shared data must be modified, write access
23/// must be acquired, preventing other threads from accessing the data while it
24/// is being written, but multiple threads can read the shared data when it is
25/// not being mutated.
26///
27/// This is in contrast to a [`Mutex`], which only ever allows a single
28/// core/thread to access the shared data at any point in time. In some cases,
29/// such as when a large number of readers need to access the shared data
30/// without modifying it, using a `RwLock` can be more efficient than a
31/// [`Mutex`].
32///
33/// # Usage
34///
35/// The type parameter `T` represents the data that this lock protects. It is
36/// required that `T` satisfies [`Send`] to be shared across threads and
37/// [`Sync`] to allow concurrent access through readers. The RAII guards
38/// returned from the locking methods implement [`Deref`] (and [`DerefMut`]
39/// for the `write` methods) to allow access to the content of the lock.
40///
41/// The [`read`] method acquires read access to the lock, returning a
42/// [`RwLockReadGuard`]. If the lock is currently locked for write access, the
43/// [`read`] method will wait until the write access completes before allowing
44/// read access to the locked data.
45///
46/// The [`write`] method acquires write access to the lock, returning a
47/// [`RwLockWriteGuard`], which implements [`DerefMut`]. If the lock is
48/// currently locked for reading *or* writing, the [`write`] method will wait
49/// until all current reads or the current write completes before allowing write
50/// access to the locked data.
51///
52/// # Priority Policy
53///
54/// The priority policy of this lock is _fair_ (or [_write-preferring_]), in
55/// order to ensure that readers cannot starve  writers. Fairness is ensured
56/// using a first-in, first-out queue for the tasks awaiting the lock; if a task
57/// that wishes to acquire the write lock is at the head of the queue, read
58/// locks will not be given out until the write lock has  been released. This is
59/// in contrast to the Rust standard library's [`std::sync::RwLock`], where the
60/// priority policy is dependent on the operating system's implementation.
61///
62/// # Overriding the blocking mutex
63///
64/// This type uses a [blocking `Mutex`](crate::blocking::Mutex) internally to
65/// synchronize access to its wait list. By default, this is a [`Spinlock`]. To
66/// use an alternative [`RawMutex`] implementation, use the
67/// [`new_with_raw_mutex`](Self::new_with_raw_mutex) constructor. See [the documentation
68/// on overriding mutex
69/// implementations](crate::blocking#overriding-mutex-implementations) for more
70/// details.
71///
72/// Note that this type currently requires that the raw mutex implement
73/// [`RawMutex`] rather than [`mutex_traits::ScopedRawMutex`]!
74///
75/// # Examples
76///
77/// ```
78/// use maitake_sync::RwLock;
79///
80/// # async fn example() {
81/// let lock = RwLock::new(5);
82///
83/// // many reader locks can be held at once
84/// {
85///     let r1 = lock.read().await;
86///     let r2 = lock.read().await;
87///     assert_eq!(*r1, 5);
88///     assert_eq!(*r2, 5);
89/// } // read locks are dropped at this point
90///
91/// // only one write lock may be held, however
92/// {
93///     let mut w = lock.write().await;
94///     *w += 1;
95///     assert_eq!(*w, 6);
96/// } // write lock is dropped here
97/// # }
98/// # futures::executor::block_on(example());
99/// ```
100///
101/// [`Mutex`]: crate::Mutex
102/// [`read`]: Self::read
103/// [`write`]: Self::write
104/// [readers-writer lock]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
105/// [_write-preferring_]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies
106/// [`std::sync::RwLock`]:
107///     https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html
108pub struct RwLock<T: ?Sized, Lock: RawMutex = Spinlock> {
109    /// The semaphore used to control access to `data`.
110    ///
111    /// To read `data`, a single permit must be acquired. To write to `data`,
112    /// all the permits in the semaphore must be acquired.
113    sem: Semaphore<Lock>,
114
115    /// The data protected by the lock.
116    data: UnsafeCell<T>,
117}
118
119/// [RAII] structure used to release the shared read access of a [`RwLock`] when
120/// dropped.
121///
122/// The data protected by the [`RwLock`] can be accessed through this guard via
123/// its [`Deref`](#impl-Deref) implementation.
124///
125/// This guard can be held across any `.await` point, as it implements
126/// [`Send`].
127///
128/// This structure is created by the [`read`] and [`try_read`] methods on
129/// [`RwLock`].
130///
131/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html
132/// [`read`]: RwLock::read
133/// [`try_read`]: RwLock::try_read
134#[must_use = "if unused, the `RwLock` will immediately unlock"]
135pub struct RwLockReadGuard<'lock, T: ?Sized, Lock: RawMutex = Spinlock> {
136    /// /!\ WARNING: semi-load-bearing drop order /!\
137    ///
138    /// This struct's field ordering is important for Loom tests; the `ConstPtr`
139    /// must be dropped before the permit, as dropping the permit may wake
140    /// another task that wants to access the cell, and Loom will still consider the data to
141    /// be "accessed" until the `ConstPtr` is dropped.
142    data: cell::ConstPtr<T>,
143    _permit: semaphore::Permit<'lock, Lock>,
144}
145
146/// [RAII] structure used to release the exclusive write access of a [`RwLock`] when
147/// dropped.
148///
149/// The data protected by the [`RwLock`] can be accessed through this guard via
150/// its [`Deref`](#impl-Deref) and [`DerefMut`](#impl-Deref) implementations.
151///
152/// This guard can be held across any `.await` point, as it implements
153/// [`Send`].
154///
155/// This structure is created by the [`write`] and [`try_write`] methods on
156/// [`RwLock`].
157///
158/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html
159/// [`write`]: RwLock::write
160/// [`try_write`]: RwLock::try_write
161#[must_use = "if unused, the `RwLock` will immediately unlock"]
162pub struct RwLockWriteGuard<'lock, T: ?Sized, Lock: RawMutex = Spinlock> {
163    /// /!\ WARNING: semi-load-bearing drop order /!\
164    ///
165    /// This struct's field ordering is important for Loom tests; the `MutPtr`
166    /// must be dropped before the permit, as dropping the permit may wake
167    /// another task that wants to access the cell, and Loom will still consider
168    /// the data to be "accessed mutably" until the `MutPtr` is dropped.
169    data: cell::MutPtr<T>,
170    _permit: semaphore::Permit<'lock, Lock>,
171}
172
173feature! {
174    #![feature = "alloc"]
175    mod owned;
176    pub use self::owned::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard};
177}
178
179// === impl RwLock ===
180
181impl<T> RwLock<T> {
182    loom_const_fn! {
183        /// Returns a new `RwLock` protecting the provided `data`, in an
184        /// unlocked state.
185        ///
186        /// This constructor returns a `RwLock` that uses a [`Spinlock`] as the
187        /// underlying blocking mutex implementation. To use an alternative
188        /// [`RawMutex`] implementation, use the [`RwLock::new_with_raw_mutex`]
189        /// constructor instead. See [the documentation on overriding mutex
190        /// implementations](crate::blocking#overriding-mutex-implementations)
191        /// for more details.
192        ///
193        /// # Examples
194        ///
195        /// ```
196        /// use maitake_sync::RwLock;
197        ///
198        /// let lock = RwLock::new(5);
199        /// # drop(lock)
200        /// ```
201        ///
202        /// Because this is a `const fn`, it may be used in `static`
203        /// initializers:
204        ///
205        /// ```
206        /// use maitake_sync::RwLock;
207        ///
208        /// static LOCK: RwLock<usize> = RwLock::new(5);
209        /// ```
210        #[must_use]
211        pub fn new(data: T) -> Self {
212            Self::new_with_raw_mutex(data, Spinlock::new())
213        }
214    }
215}
216
217impl<T, Lock: RawMutex> RwLock<T, Lock> {
218    loom_const_fn! {
219        /// Returns a new `RwLock` protecting the provided `data`, in an
220        /// unlocked state, using the provided [`RawMutex`] implementation.
221        ///
222        /// This constructor allows a [`RwLock`] to be constructed with any type that
223        /// implements [`RawMutex`] as the underlying raw blocking mutex
224        /// implementation. See [the documentation on overriding mutex
225        /// implementations](crate::blocking#overriding-mutex-implementations)
226        /// for more details.
227        #[must_use]
228        pub fn new_with_raw_mutex(data: T, lock: Lock) -> Self {
229            Self {
230                sem: Semaphore::new_with_raw_mutex(Self::MAX_READERS, lock),
231                data: UnsafeCell::new(data),
232            }
233        }
234    }
235
236    /// Consumes this `RwLock`, returning the guarded data.
237    #[inline]
238    #[must_use]
239    pub fn into_inner(self) -> T {
240        self.data.into_inner()
241    }
242}
243
244impl<T: ?Sized, Lock: RawMutex> RwLock<T, Lock> {
245    const MAX_READERS: usize = semaphore::MAX_PERMITS;
246
247    /// Locks this `RwLock` with shared read access, causing the current task
248    /// to yield until the lock has been acquired.
249    ///
250    /// If the lock is locked for write access, the calling task will yield and
251    /// wait until there are no writers which  hold the lock. There may be other
252    /// readers inside the lock when the task resumes.
253    ///
254    /// Note that under the [priority policy] of [`RwLock`], read locks are not
255    /// granted until prior write locks, to prevent starvation. Therefore
256    /// deadlock may occur if a read lock is held by the current task, a write
257    /// lock attempt is made, and then a subsequent read lock attempt is made
258    /// by the current task.
259    ///
260    /// Returns [an RAII guard] which will release this read access of the
261    /// `RwLock`  when dropped.
262    ///
263    /// # Cancellation
264    ///
265    /// This method [uses a queue to fairly distribute locks][priority policy]
266    /// in the order they  were requested. Cancelling a call to `read` results
267    /// in the calling task losing its place in the queue.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// # #[tokio::main(flavor="current_thread")]
273    /// # async fn test() {
274    /// # // since we are targeting no-std, it makes more sense to use `alloc`
275    /// # // in these examples, rather than `std`...but i don't want to make
276    /// # // the tests actually `#![no_std]`...
277    /// # use std as alloc;
278    /// # use tokio::task;
279    /// use maitake_sync::RwLock;
280    /// use alloc::sync::Arc;
281    ///
282    /// let lock = Arc::new(RwLock::new(1));
283    ///
284    /// // hold the lock for reading in `main`.
285    /// let n = lock
286    ///     .try_read()
287    ///     .expect("read lock must be acquired, as the lock is unlocked");
288    /// assert_eq!(*n, 1);
289    ///
290    /// # let task2 =
291    /// task::spawn({
292    ///     let lock = lock.clone();
293    ///     async move {
294    ///         // While main has an active read lock, this task can acquire
295    ///         // one too.
296    ///         let n = lock.read().await;
297    ///         assert_eq!(*n, 1);
298    ///     }
299    /// });
300    ///
301    /// # task2.await.unwrap();
302    /// # }
303    /// # test();
304    /// ```
305    ///
306    /// [priority policy]: Self#priority-policy
307    /// [an RAII guard]:
308    pub async fn read(&self) -> RwLockReadGuard<'_, T, Lock> {
309        let _permit = self
310            .sem
311            .acquire(1)
312            .await
313            .expect("RwLock semaphore should never be closed");
314        RwLockReadGuard {
315            data: self.data.get(),
316            _permit,
317        }
318    }
319
320    /// Locks this `RwLock` with exclusive write access, causing the current
321    /// task to yield until the lock has been acquired.
322    ///
323    /// If other tasks are holding a read or write lock, the calling task will
324    /// wait until the write lock or all read locks are released.
325    ///
326    /// Returns [an RAII guard] which will release the write access of this
327    /// `RwLock` when dropped.
328    ///
329    /// # Cancellation
330    ///
331    /// This method [uses a queue to fairly distribute
332    /// locks](Self#priority-policy) in the order they were requested.
333    /// Cancelling a call to `write` results in the calling task losing its place
334    /// in the queue.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// # #[tokio::main(flavor="current_thread")]
340    /// # async fn test() {
341    /// # // since we are targeting no-std, it makes more sense to use `alloc`
342    /// # // in these examples, rather than `std`...but i don't want to make
343    /// # // the tests actually `#![no_std]`...
344    /// # use std as alloc;
345    /// # use tokio::task;
346    /// use maitake_sync::RwLock;
347    /// use alloc::sync::Arc;
348    ///
349    /// let lock = Arc::new(RwLock::new(1));
350    ///
351    /// # let task =
352    /// task::spawn(async move {
353    ///     let mut guard = lock.write().await;
354    ///     *guard += 1;
355    /// });
356    /// # task.await.unwrap()
357    /// # }
358    /// # test();
359    /// ```
360    pub async fn write(&self) -> RwLockWriteGuard<'_, T, Lock> {
361        let _permit = self
362            .sem
363            .acquire(Self::MAX_READERS)
364            .await
365            .expect("RwLock semaphore should never be closed");
366        RwLockWriteGuard {
367            data: self.data.get_mut(),
368            _permit,
369        }
370    }
371
372    /// Attempts to acquire this `RwLock` for shared read access, without
373    /// waiting.
374    ///
375    /// If the access couldn't be acquired immediately, this method returns
376    /// [`None`] rather than waiting.
377    ///
378    /// Otherwise, [an RAII guard] is returned, which allows read access to the
379    /// protected data and will release that access when dropped.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use maitake_sync::RwLock;
385    ///
386    /// let lock = RwLock::new(1);
387    ///
388    /// let mut write_guard = lock
389    ///     .try_write()
390    ///     .expect("lock is unlocked, so write access should be acquired");
391    /// *write_guard += 1;
392    ///
393    /// // because a write guard is held, we cannot acquire the read lock, so
394    /// // this will return `None`.
395    /// assert!(lock.try_read().is_none());
396    /// ```
397    ///
398    /// [an RAII guard]: RwLockReadGuard
399    pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T, Lock>> {
400        match self.sem.try_acquire(1) {
401            Ok(_permit) => Some(RwLockReadGuard {
402                data: self.data.get(),
403                _permit,
404            }),
405            Err(semaphore::TryAcquireError::InsufficientPermits) => None,
406            Err(semaphore::TryAcquireError::Closed) => {
407                unreachable!("RwLock semaphore should never be closed")
408            }
409        }
410    }
411
412    /// Attempts to acquire this `RwLock` for exclusive write access, without
413    /// waiting.
414    ///
415    /// If the access couldn't be acquired immediately, this method returns
416    /// [`None`] rather than waiting.
417    ///
418    /// Otherwise, [an RAII guard] is returned, which allows write access to the
419    /// protected data and will release that access when dropped.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use maitake_sync::RwLock;
425    ///
426    /// let lock = RwLock::new(1);
427    ///
428    /// let read_guard = lock
429    ///     .try_read()
430    ///     .expect("lock is unlocked, so read access should be acquired");
431    /// assert_eq!(*read_guard, 1);
432    ///
433    /// // because a read guard is held, we cannot acquire the write lock, so
434    /// // this will return `None`.
435    /// assert!(lock.try_write().is_none());
436    /// ```
437    ///
438    /// [an RAII guard]: RwLockWriteGuard
439    pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T, Lock>> {
440        match self.sem.try_acquire(Self::MAX_READERS) {
441            Ok(_permit) => Some(RwLockWriteGuard {
442                data: self.data.get_mut(),
443                _permit,
444            }),
445            Err(semaphore::TryAcquireError::InsufficientPermits) => None,
446            Err(semaphore::TryAcquireError::Closed) => {
447                unreachable!("RwLock semaphore should never be closed")
448            }
449        }
450    }
451
452    /// Returns a mutable reference to the underlying data.
453    ///
454    /// Since this call borrows the `RwLock` mutably, no actual locking needs to
455    /// take place -- the mutable borrow statically guarantees no locks exist.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// let mut lock = maitake_sync::RwLock::new(0);
461    /// *lock.get_mut() = 10;
462    /// assert_eq!(*lock.try_read().unwrap(), 10);
463    /// ```
464    pub fn get_mut(&mut self) -> &mut T {
465        unsafe {
466            // Safety: since this call borrows the `RwLock` mutably, no actual
467            // locking needs to take place -- the mutable borrow statically
468            // guarantees no locks exist.
469            self.data.with_mut(|data| &mut *data)
470        }
471    }
472}
473
474impl<T: Default> Default for RwLock<T> {
475    fn default() -> Self {
476        Self::new(T::default())
477    }
478}
479
480impl<T, Lock> fmt::Debug for RwLock<T, Lock>
481where
482    T: ?Sized + fmt::Debug,
483    Lock: RawMutex + fmt::Debug,
484{
485    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486        let Self { sem, data: _ } = self;
487        f.debug_struct("RwLock")
488            .field("sem", sem)
489            .field("data", &fmt::opt(&self.try_read()).or_else("<locked>"))
490            .finish()
491    }
492}
493
494// Safety: if `T` is `Send + Sync`, an `RwLock<T>` can safely be sent or shared
495// between threads. If `T` wasn't `Send`, this would be unsafe, since the
496// `RwLock` exposes access to the `T`.
497unsafe impl<T, Lock> Send for RwLock<T, Lock>
498where
499    T: ?Sized + Send,
500    Lock: RawMutex + Send,
501{
502}
503unsafe impl<T, Lock> Sync for RwLock<T, Lock>
504where
505    T: ?Sized + Send + Sync,
506    Lock: RawMutex + Sync,
507{
508}
509
510// === impl RwLockReadGuard ===
511
512impl<T: ?Sized, Lock: RawMutex> Deref for RwLockReadGuard<'_, T, Lock> {
513    type Target = T;
514
515    #[inline]
516    fn deref(&self) -> &Self::Target {
517        unsafe {
518            // safety: we are holding the semaphore permit that ensures the lock
519            // cannot be accessed mutably.
520            self.data.deref()
521        }
522    }
523}
524
525impl<T: ?Sized + fmt::Debug, Lock: RawMutex> fmt::Debug for RwLockReadGuard<'_, T, Lock> {
526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527        self.deref().fmt(f)
528    }
529}
530
531// Safety: A read guard can be shared or sent between threads as long as `T` is
532// `Sync`. It can implement `Send` even if `T` does not implement `Send`, as
533// long as `T` is `Sync`, because the read guard only permits borrowing the `T`.
534unsafe impl<T, Lock> Send for RwLockReadGuard<'_, T, Lock>
535where
536    T: ?Sized + Sync,
537    Lock: RawMutex + Sync,
538{
539}
540unsafe impl<T, Lock> Sync for RwLockReadGuard<'_, T, Lock>
541where
542    T: ?Sized + Send + Sync,
543    Lock: RawMutex + Sync,
544{
545}
546
547// === impl RwLockWriteGuard ===
548
549impl<T: ?Sized, Lock: RawMutex> Deref for RwLockWriteGuard<'_, T, Lock> {
550    type Target = T;
551
552    #[inline]
553    fn deref(&self) -> &Self::Target {
554        unsafe {
555            // safety: we are holding all the semaphore permits, so the data
556            // inside the lock cannot be accessed by another thread.
557            self.data.deref()
558        }
559    }
560}
561
562impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
563    #[inline]
564    fn deref_mut(&mut self) -> &mut Self::Target {
565        unsafe {
566            // safety: we are holding all the semaphore permits, so the data
567            // inside the lock cannot be accessed by another thread.
568            self.data.deref()
569        }
570    }
571}
572
573impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockWriteGuard<'_, T> {
574    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575        self.deref().fmt(f)
576    }
577}
578
579// Safety: Unlike the read guard, `T` must be both `Send` and `Sync` for the
580// write guard to be `Send`, because the mutable access provided by the write
581// guard can be used to `mem::replace` or `mem::take` the value, transferring
582// ownership of it across threads.
583unsafe impl<T> Send for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {}
584unsafe impl<T> Sync for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {}