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 {}