maitake_sync/rwlock/
owned.rs

1use super::*;
2use crate::semaphore;
3use alloc::sync::Arc;
4
5/// Owned [RAII] structure used to release the shared read access of a
6/// [`RwLock`] when dropped.
7///
8/// This type is similar to the [`RwLockReadGuard`] type, but it is only
9/// returned by an [`RwLock`] that is wrapped in an an [`Arc`]. Instead
10/// of borrowing the [`RwLock`], this guard holds an [`Arc`] clone of
11/// the [`RwLock`], incrementing its reference count. Therefore, this
12/// type can outlive the [`RwLock`] that created it, and it is valid for
13/// the `'static` lifetime. Beyond this, it is identical to the
14/// [`RwLockReadGuard`] type.
15///
16/// The data protected by the [`RwLock`] can be accessed through this
17/// guard via its [`Deref`](#impl-Deref) implementation.
18///
19/// This guard can be held across any `.await` point, as it implements
20/// [`Send`].
21///
22/// This structure is created by the [`read_owned`] and
23/// [`try_read_owned`] methods on [`Arc`]`<`[`RwLock`]`>`.
24///
25/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html
26/// [`read_owned`]: RwLock::read_owned
27/// [`try_read_owned`]: RwLock::try_read_owned
28#[must_use = "if unused, the `RwLock` will immediately unlock"]
29pub struct OwnedRwLockReadGuard<T: ?Sized> {
30    /// /!\ WARNING: semi-load-bearing drop order /!\
31    ///
32    /// This struct's field ordering is important for Loom tests; the `ConstPtr`
33    /// must be dropped before the semaphore permit is released back to the
34    /// semaphore, as this may wake another task that wants to mutably access
35    /// the cell. However, Loom will still consider the data to be "immutably
36    /// accessed" until the ConstPtr` is dropped, so we must drop the `ConstPtr`
37    /// first.
38    ///
39    /// This isn't actually a bug in "real life", because we're not going to
40    /// actually *read* the data through the `ConstPtr` in the guard's `Drop`
41    /// impl, but Loom considers us to be "accessing" it as long as the
42    /// `ConstPtr` exists.
43    data: cell::ConstPtr<T>,
44    _lock: AddPermits<1, T>,
45}
46
47/// Owned [RAII] structure used to release the exclusive write access of a
48/// [`RwLock`] when dropped.
49///
50/// This type is similar to the [`RwLockWriteGuard`] type, but it is
51/// only returned by an [`RwLock`] that is wrapped in an an [`Arc`].
52/// Instead of borrowing the [`RwLock`], this guard holds an [`Arc`]
53/// clone of the [`RwLock`], incrementing its reference count.
54/// Therefore, this type can outlive the [`RwLock`] that created it, and
55/// it is valid for the `'static` lifetime. Beyond this, is identical to
56/// the [`RwLockWriteGuard`] type.
57///
58/// The data protected by the [`RwLock`] can be accessed through this
59/// guard via its [`Deref`](#impl-Deref) and [`DerefMut`](#impl-Deref)
60/// implementations.
61///
62/// This guard can be held across any `.await` point, as it implements
63/// [`Send`].
64///
65/// This structure is created by the [`read_owned`] and
66/// [`try_read_owned`] methods on [`Arc`]`<`[`RwLock`]`>`.
67///
68/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html
69/// [`read_owned`]: RwLock::read_owned
70/// [`try_read_owned`]: RwLock::try_read_owned
71#[must_use = "if unused, the `RwLock` will immediately unlock"]
72pub struct OwnedRwLockWriteGuard<T: ?Sized> {
73    /// /!\ WARNING: semi-load-bearing drop order /!\
74    ///
75    /// This struct's field ordering is important for Loom tests; the `MutPtr`
76    /// must be dropped before the semaphore permits are released back to the
77    /// semaphore, as this may wake another task that wants to access the cell.
78    /// However, Loom will still consider the data to be "mutably accessed"
79    /// until the `MutPtr` is dropped, so we must drop the `MutPtr` first.
80    ///
81    /// This isn't actually a bug in "real life", because we're not going to
82    /// actually read or write the data through the `MutPtr` in the guard's
83    /// `Drop` impl, but Loom considers us to be "accessing" it as long as the
84    /// `MutPtr` exists.
85    data: cell::MutPtr<T>,
86    _lock: AddPermits<{ semaphore::MAX_PERMITS }, T>,
87}
88
89/// A wrapper around an `RwLock` `Arc` clone that releases a fixed number of
90/// permits when it's dropped.
91///
92/// This is factored out to a separate type to ensure that it's dropped *after*
93/// the `MutPtr`/`ConstPtr`s are dropped, to placate `loom`.
94struct AddPermits<const PERMITS: usize, T: ?Sized>(Arc<RwLock<T>>);
95
96// === impl RwLock ===
97
98impl<T: ?Sized> RwLock<T> {
99    /// Locks this `RwLock` with shared read access, returning an [owned RAII
100    /// guard][guard].
101    ///
102    /// This method is identical to [`RwLock::read`], execept that it requires
103    /// the `RwLock` to be wrapped in an [`Arc`], and returns an
104    /// [`OwnedRwLockReadGuard`] that clones the [`Arc`] rather than
105    /// borrowing the lock. Therefore, the returned guard is valid for the
106    /// `'static` lifetime.
107    ///
108    /// If the lock is locked for write access, the calling task will yield and
109    /// wait until there are no writers which  hold the lock. There may be other
110    /// readers inside the lock when the task resumes.
111    ///
112    /// Note that under the [priority policy] of [`RwLock`], read locks are not
113    /// granted until prior write locks, to prevent starvation. Therefore
114    /// deadlock may occur if a read lock is held by the current task, a write
115    /// lock attempt is made, and then a subsequent read lock attempt is made
116    /// by the current task.
117    ///
118    /// Returns [an RAII guard][guard] which will release this read access of the
119    /// `RwLock` when dropped.
120    ///
121    /// # Cancellation
122    ///
123    /// This method [uses a queue to fairly distribute locks][priority policy]
124    /// in the order they  were requested. Cancelling a call to `read` results
125    /// in the calling task losing its place in the queue.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// # use tokio::task;
131    /// # #[tokio::main(flavor = "current_thread")]
132    /// # async fn test() {
133    /// # // since we are targeting no-std, it makes more sense to use `alloc`
134    /// # // in these examples, rather than `std`...but i don't want to make
135    /// # // the tests actually `#![no_std]`...
136    /// # use std as alloc;
137    /// use maitake_sync::RwLock;
138    /// use alloc::sync::Arc;
139    ///
140    /// let lock = Arc::new(RwLock::new(1));
141    /// // hold the lock for reading in `main`.
142    /// let n = lock
143    ///     .try_read()
144    ///     .expect("read lock must be acquired, as the lock is unlocked");
145    /// assert_eq!(*n, 1);
146    ///
147    /// # let task =
148    /// task::spawn({
149    ///     let lock = lock.clone();
150    ///     async move {
151    ///         // While main has an active read lock, this task can acquire
152    ///         // one too.
153    ///         let n = lock.read_owned().await;
154    ///         assert_eq!(*n, 1);
155    ///     }
156    /// });
157    /// # task.await.unwrap();
158    /// # }
159    /// # test();
160    /// ```
161    ///
162    /// [priority policy]: Self#priority-policy
163    /// [guard]: OwnedRwLockReadGuard
164    pub async fn read_owned(self: &Arc<Self>) -> OwnedRwLockReadGuard<T> {
165        let guard = self.read().await;
166        OwnedRwLockReadGuard::from_borrowed(self.clone(), guard)
167    }
168
169    /// Locks this `RwLock` with exclusive write access,returning an [owned RAII
170    /// guard][guard].
171    ///
172    /// This method is identical to [`RwLock::write`], execept that it requires
173    /// the `RwLock` to be wrapped in an [`Arc`], and returns an
174    /// [`OwnedRwLockWriteGuard`] that clones the [`Arc`] rather than
175    /// borrowing the lock. Therefore, the returned guard is valid for the
176    /// `'static` lifetime.
177    ///
178    /// # Returns
179    ///
180    /// If other tasks are holding a read or write lock, the calling task will
181    /// wait until the write lock or all read locks are released.
182    ///
183    /// Returns [an RAII guard][guard] which will release the write access of this
184    /// `RwLock` when dropped.
185    ///
186    /// # Cancellation
187    ///
188    /// This method [uses a queue to fairly distribute
189    /// locks](Self#priority-policy) in the order they were requested.
190    /// Cancelling a call to `write` results in the calling task losing its place
191    /// in the queue.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// # use tokio::task;
197    /// # #[tokio::main(flavor = "current_thread")]
198    /// # async fn test() {
199    /// # // since we are targeting no-std, it makes more sense to use `alloc`
200    /// # // in these examples, rather than `std`...but i don't want to make
201    /// # // the tests actually `#![no_std]`...
202    /// # use std as alloc;
203    /// use maitake_sync::RwLock;
204    /// use alloc::sync::Arc;
205    ///
206    /// let lock = Arc::new(RwLock::new(1));
207    ///
208    /// # let task =
209    /// task::spawn(async move {
210    ///     let mut guard = lock.write_owned().await;
211    ///     *guard += 1;
212    /// });
213    /// # task.await.unwrap();
214    /// # }
215    /// # test();
216    /// ```
217    ///
218    /// [guard]: OwnedRwLockWriteGuard
219    pub async fn write_owned(self: &Arc<Self>) -> OwnedRwLockWriteGuard<T> {
220        let guard = self.write().await;
221        OwnedRwLockWriteGuard::from_borrowed(self.clone(), guard)
222    }
223
224    /// Attempts to acquire this `RwLock` for shared read access, without
225    /// waiting, and returning an [owned RAII guard][guard].
226    ///
227    /// This method is identical to [`RwLock::try_read`], execept that it requires
228    /// the `RwLock` to be wrapped in an [`Arc`], and returns an
229    /// [`OwnedRwLockReadGuard`] that clones the [`Arc`] rather than
230    /// borrowing the lock. Therefore, the returned guard is valid for the
231    /// `'static` lifetime.
232    ///
233    /// # Returns
234    ///
235    /// If the access couldn't be acquired immediately, this method returns
236    /// [`None`] rather than waiting.
237    ///
238    /// Otherwise, [an RAII guard][guard] is returned, which allows read access to the
239    /// protected data and will release that access when dropped.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// # fn main() {
245    /// # // since we are targeting no-std, it makes more sense to use `alloc`
246    /// # // in these examples, rather than `std`...but i don't want to make
247    /// # // the tests actually `#![no_std]`...
248    /// # use std as alloc;
249    /// use maitake_sync::RwLock;
250    /// use alloc::sync::Arc;
251    ///
252    /// let lock = Arc::new(RwLock::new(1));
253    ///
254    /// let mut write_guard = lock
255    ///     .try_write()
256    ///     .expect("lock is unlocked, so write access should be acquired");
257    /// *write_guard += 1;
258    ///
259    /// // because a write guard is held, we cannot acquire the read lock, so
260    /// // this will return `None`.
261    /// assert!(lock.try_read_owned().is_none());
262    /// # }
263    /// ```
264    ///
265    /// [guard]: OwnedRwLockReadGuard
266    pub fn try_read_owned(self: &Arc<Self>) -> Option<OwnedRwLockReadGuard<T>> {
267        self.try_read()
268            .map(|guard| OwnedRwLockReadGuard::from_borrowed(self.clone(), guard))
269    }
270
271    /// Attempts to acquire this `RwLock` for exclusive write access, without
272    /// waiting, and returning an [owned RAII guard][guard].
273    ///
274    /// This method is identical to [`RwLock::try_write`], execept that it requires
275    /// the `RwLock` to be wrapped in an [`Arc`], and returns an
276    /// [`OwnedRwLockWriteGuard`] that clones the [`Arc`] rather than
277    /// borrowing the lock. Therefore, the returned guard is valid for the
278    /// `'static` lifetime.
279    ///
280    /// # Returns
281    ///
282    /// If the access couldn't be acquired immediately, this method returns
283    /// [`None`] rather than waiting.
284    ///
285    /// Otherwise, [an RAII guard][guard] is returned, which allows write access to the
286    /// protected data and will release that access when dropped.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// # fn main() {
292    /// # // since we are targeting no-std, it makes more sense to use `alloc`
293    /// # // in these examples, rather than `std`...but i don't want to make
294    /// # // the tests actually `#![no_std]`...
295    /// # use std as alloc;
296    /// use maitake_sync::RwLock;
297    /// use alloc::sync::Arc;
298    ///
299    /// let lock = Arc::new(RwLock::new(1));
300    ///
301    /// let read_guard = lock
302    ///     .try_read()
303    ///     .expect("lock is unlocked, so read access should be acquired");
304    /// assert_eq!(*read_guard, 1);
305    ///
306    /// // because a read guard is held, we cannot acquire the write lock, so
307    /// // this will return `None`.
308    /// assert!(lock.try_write_owned().is_none());
309    /// # }
310    /// ```
311    ///
312    /// [guard]: OwnedRwLockWriteGuard
313    pub fn try_write_owned(self: &Arc<Self>) -> Option<OwnedRwLockWriteGuard<T>> {
314        self.try_write()
315            .map(|guard| OwnedRwLockWriteGuard::from_borrowed(self.clone(), guard))
316    }
317}
318
319// === impl OwnedRwLockReadGuard ===
320
321impl<T: ?Sized> OwnedRwLockReadGuard<T> {
322    fn from_borrowed(
323        lock: Arc<RwLock<T>>,
324        RwLockReadGuard { data, _permit }: RwLockReadGuard<'_, T>,
325    ) -> Self {
326        // forget the semaphore permit, as it has a lifetime tied to the
327        // borrowed semaphore. we'll manually release the permit in
328        // `OwnedRwLockReadGuard`'s `Drop` impl.
329        _permit.forget();
330        Self {
331            _lock: AddPermits(lock),
332            data,
333        }
334    }
335}
336
337impl<T: ?Sized> Deref for OwnedRwLockReadGuard<T> {
338    type Target = T;
339
340    #[inline]
341    fn deref(&self) -> &Self::Target {
342        unsafe {
343            // safety: we are holding the semaphore permit that ensures the lock
344            // cannot be accessed mutably.
345            self.data.deref()
346        }
347    }
348}
349
350impl<T: ?Sized + fmt::Debug> fmt::Debug for OwnedRwLockReadGuard<T> {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        self.deref().fmt(f)
353    }
354}
355
356// Safety: A read guard can be shared or sent between threads as long as `T` is
357// `Sync`. It can implement `Send` even if `T` does not implement `Send`, as
358// long as `T` is `Sync`, because the read guard only permits borrowing the `T`.
359unsafe impl<T> Send for OwnedRwLockReadGuard<T> where T: ?Sized + Sync {}
360unsafe impl<T> Sync for OwnedRwLockReadGuard<T> where T: ?Sized + Send + Sync {}
361
362// === impl OwnedRwLockWriteGuard ===
363
364impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
365    fn from_borrowed(
366        lock: Arc<RwLock<T>>,
367        RwLockWriteGuard { data, _permit }: RwLockWriteGuard<'_, T>,
368    ) -> Self {
369        // forget the semaphore permit, as it has a lifetime tied to the
370        // borrowed semaphore. we'll manually release the permit in
371        // `OwnedRwLockWriteGuard`'s `Drop` impl.
372        _permit.forget();
373        Self {
374            _lock: AddPermits(lock),
375            data,
376        }
377    }
378}
379
380impl<T: ?Sized> Deref for OwnedRwLockWriteGuard<T> {
381    type Target = T;
382
383    #[inline]
384    fn deref(&self) -> &Self::Target {
385        unsafe {
386            // safety: we are holding all the semaphore permits, so the data
387            // inside the lock cannot be accessed by another thread.
388            self.data.deref()
389        }
390    }
391}
392
393impl<T: ?Sized> DerefMut for OwnedRwLockWriteGuard<T> {
394    #[inline]
395    fn deref_mut(&mut self) -> &mut Self::Target {
396        unsafe {
397            // safety: we are holding all the semaphore permits, so the data
398            // inside the lock cannot be accessed by another thread.
399            self.data.deref()
400        }
401    }
402}
403
404impl<T: ?Sized + fmt::Debug> fmt::Debug for OwnedRwLockWriteGuard<T> {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        self.deref().fmt(f)
407    }
408}
409
410// Safety: Unlike the read guard, `T` must be both `Send` and `Sync` for the
411// write guard to be `Send`, because the mutable access provided by the write
412// guard can be used to `mem::replace` or `mem::take` the value, transferring
413// ownership of it across threads.
414unsafe impl<T> Send for OwnedRwLockWriteGuard<T> where T: ?Sized + Send + Sync {}
415unsafe impl<T> Sync for OwnedRwLockWriteGuard<T> where T: ?Sized + Send + Sync {}
416
417// === impl AddPermits ===
418
419impl<const PERMITS: usize, T: ?Sized> Drop for AddPermits<PERMITS, T> {
420    fn drop(&mut self) {
421        self.0.sem.add_permits(PERMITS);
422    }
423}