Skip to main content

dtact_util/sync/
rwlock.rs

1//! Async reader-writer lock.
2
3use super::wait_queue::WaitQueue;
4use std::cell::UnsafeCell;
5use std::ops::{Deref, DerefMut};
6use std::sync::atomic::{AtomicIsize, Ordering};
7use std::task::{Context, Poll};
8
9/// `state == 0`: unlocked. `state > 0`: that many readers hold the lock.
10/// `state == WRITE_LOCKED`: one writer holds the lock. No writer-starves-
11/// readers fairness — a steady stream of readers can, in principle, keep
12/// a waiting writer pending indefinitely, the same simplification
13/// `std::sync::RwLock` itself makes on most platforms.
14const WRITE_LOCKED: isize = -1;
15
16/// An async reader-writer lock: `.read().await`/`.write().await` yield
17/// the calling task rather than blocking the OS thread while contended.
18#[repr(align(64))]
19pub struct RwLock<T: ?Sized> {
20    state: AtomicIsize,
21    wait: WaitQueue,
22    data: UnsafeCell<T>,
23}
24
25// SAFETY: same reasoning as `Mutex`'s — `state`'s CAS/fetch-based
26// transitions are the sole gate on `data` access, so `Sync` needs `T:
27// Send + Sync` (shared reads must actually be safe to share, unlike a
28// plain mutex which never exposes concurrent `&T`).
29unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
30unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
31
32impl<T> RwLock<T> {
33    /// Create a new lock guarding `data`, initially unlocked.
34    #[must_use]
35    pub const fn new(data: T) -> Self {
36        Self {
37            state: AtomicIsize::new(0),
38            wait: WaitQueue::new(),
39            data: UnsafeCell::new(data),
40        }
41    }
42
43    /// Consume the lock, returning the guarded value.
44    #[inline(always)]
45    pub fn into_inner(self) -> T {
46        self.data.into_inner()
47    }
48}
49
50impl<T: ?Sized + Send + Sync> RwLock<T> {
51    /// Acquire a shared read lock, waiting while a writer holds it.
52    /// Multiple readers may hold the lock concurrently.
53    #[inline(always)]
54    pub async fn read(&self) -> RwLockReadGuard<'_, T> {
55        std::future::poll_fn(|cx| self.poll_read(cx)).await
56    }
57
58    /// Acquire the exclusive write lock, waiting while any reader or
59    /// writer holds it.
60    #[inline(always)]
61    pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
62        std::future::poll_fn(|cx| self.poll_write(cx)).await
63    }
64}
65
66impl<T: ?Sized> RwLock<T> {
67    /// Acquire a read lock if immediately available, without waiting.
68    #[must_use]
69    #[inline(always)]
70    pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
71        // See `Mutex::try_lock`'s comment on why this must be `.then(||
72        // ...)`, not `.then_some(...)` — the guard's `Drop` releases a
73        // read-lock slot, which `then_some`'s eager evaluation would do
74        // even when acquisition failed.
75        self.try_acquire_read()
76            .then(|| RwLockReadGuard { lock: self })
77    }
78
79    /// Acquire the write lock if immediately available, without waiting.
80    #[must_use]
81    #[inline(always)]
82    pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
83        self.state
84            .compare_exchange(0, WRITE_LOCKED, Ordering::Acquire, Ordering::Relaxed)
85            .is_ok()
86            .then(|| RwLockWriteGuard { lock: self })
87    }
88
89    #[inline(always)]
90    fn try_acquire_read(&self) -> bool {
91        let mut current = self.state.load(Ordering::Relaxed);
92        loop {
93            if current < 0 {
94                return false;
95            }
96            match self.state.compare_exchange_weak(
97                current,
98                current + 1,
99                Ordering::Acquire,
100                Ordering::Relaxed,
101            ) {
102                Ok(_) => return true,
103                Err(observed) => current = observed,
104            }
105        }
106    }
107
108    #[inline(always)]
109    fn poll_read(&self, cx: &Context<'_>) -> Poll<RwLockReadGuard<'_, T>> {
110        if self.try_acquire_read() {
111            return Poll::Ready(RwLockReadGuard { lock: self });
112        }
113        // See `Mutex::poll_lock` for why registration comes before the
114        // re-check, not after.
115        let token = self.wait.register(cx.waker());
116        if self.try_acquire_read() {
117            self.wait.cancel(token);
118            return Poll::Ready(RwLockReadGuard { lock: self });
119        }
120        Poll::Pending
121    }
122
123    #[inline(always)]
124    fn poll_write(&self, cx: &Context<'_>) -> Poll<RwLockWriteGuard<'_, T>> {
125        // Skip the fast-path CAS if anyone is already waiting — a fresh
126        // writer bypassing an already-waiting one every time the lock
127        // frees up is the same starvation risk `Mutex::poll_lock` guards
128        // against; see `WaitQueue::has_waiters`'s doc. (This module's own
129        // doc already accepts reader-vs-waiting-writer unfairness as
130        // intentional, matching `std::sync::RwLock` — this is a
131        // different case: writer-vs-writer.)
132        if !self.wait.has_waiters()
133            && let Some(guard) = self.try_write()
134        {
135            return Poll::Ready(guard);
136        }
137        let token = self.wait.register(cx.waker());
138        if let Some(guard) = self.try_write() {
139            self.wait.cancel(token);
140            return Poll::Ready(guard);
141        }
142        Poll::Pending
143    }
144
145    /// Get mutable access to the guarded value without locking — sound
146    /// because `&mut self` statically proves no other reference exists.
147    pub const fn get_mut(&mut self) -> &mut T {
148        self.data.get_mut()
149    }
150}
151
152impl<T: Default> Default for RwLock<T> {
153    fn default() -> Self {
154        Self {
155            state: AtomicIsize::new(0),
156            wait: WaitQueue::new(),
157            data: UnsafeCell::new(T::default()),
158        }
159    }
160}
161
162/// RAII guard for a shared read lock on a [`RwLock`].
163#[repr(align(64))]
164pub struct RwLockReadGuard<'a, T: ?Sized> {
165    lock: &'a RwLock<T>,
166}
167
168unsafe impl<T: ?Sized + Sync> Send for RwLockReadGuard<'_, T> {}
169unsafe impl<T: ?Sized + Sync> Sync for RwLockReadGuard<'_, T> {}
170
171impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
172    type Target = T;
173    #[inline(always)]
174    fn deref(&self) -> &T {
175        // SAFETY: holding a read guard is proof no writer holds the lock.
176        unsafe { &*self.lock.data.get() }
177    }
178}
179
180impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
181    #[inline(always)]
182    fn drop(&mut self) {
183        let prev = self.lock.state.fetch_sub(1, Ordering::Release);
184        // Only the last reader to leave can possibly unblock a waiting
185        // writer — waking on every reader release would just have every
186        // earlier wakeup find the lock still held and re-park.
187        if prev == 1 {
188            self.lock.wait.wake_all();
189        }
190    }
191}
192
193/// RAII guard for the exclusive write lock on a [`RwLock`].
194#[repr(align(64))]
195pub struct RwLockWriteGuard<'a, T: ?Sized> {
196    lock: &'a RwLock<T>,
197}
198
199unsafe impl<T: ?Sized + Send> Send for RwLockWriteGuard<'_, T> {}
200unsafe impl<T: ?Sized + Sync> Sync for RwLockWriteGuard<'_, T> {}
201
202impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
203    type Target = T;
204    #[inline(always)]
205    fn deref(&self) -> &T {
206        // SAFETY: holding a write guard is exclusive proof of the lock.
207        unsafe { &*self.lock.data.get() }
208    }
209}
210
211impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
212    #[inline(always)]
213    fn deref_mut(&mut self) -> &mut T {
214        // SAFETY: same as `Deref` above.
215        unsafe { &mut *self.lock.data.get() }
216    }
217}
218
219impl<T: ?Sized> Drop for RwLockWriteGuard<'_, T> {
220    #[inline(always)]
221    fn drop(&mut self) {
222        self.lock.state.store(0, Ordering::Release);
223        self.lock.wait.wake_all();
224    }
225}