From 17dab88a7b4bc86cf156a1e0ac1bac19e6f9f5c6 Mon Sep 17 00:00:00 2001 From: Botahamec Date: Sat, 29 Mar 2025 17:34:10 -0400 Subject: Clean up existing documentation --- src/collection.rs | 38 ++++-- src/collection/boxed.rs | 29 ++-- src/key.rs | 2 +- src/lockable.rs | 42 ++++-- src/mutex.rs | 40 ++++-- src/mutex/mutex.rs | 71 ++++++---- src/poisonable.rs | 64 +++++++-- src/poisonable/error.rs | 60 ++++---- src/poisonable/poisonable.rs | 148 ++++++++++---------- src/rwlock.rs | 317 +++++-------------------------------------- src/rwlock/read_lock.rs | 251 ---------------------------------- src/rwlock/rwlock.rs | 74 ++++++---- src/rwlock/write_lock.rs | 229 ------------------------------- 13 files changed, 374 insertions(+), 991 deletions(-) delete mode 100644 src/rwlock/read_lock.rs delete mode 100644 src/rwlock/write_lock.rs diff --git a/src/collection.rs b/src/collection.rs index f8c31d7..9c04fbb 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -16,12 +16,11 @@ pub(crate) mod utils; /// /// The data in this collection is guaranteed to not contain duplicates because /// `L` must always implement [`OwnedLockable`]. The underlying data may not be -/// immutably referenced and locked. Because of this, there is no need for -/// sorting the locks in the collection, or checking for duplicates, because it -/// can be guaranteed that until the underlying collection is mutated (which -/// requires releasing all acquired locks in the collection to do), then the -/// locks will stay in the same order and be locked in that order, preventing -/// cyclic wait. +/// immutably referenced. Because of this, there is no need for sorting the +/// locks in the collection, or checking for duplicates, because it can be +/// guaranteed that until the underlying collection is mutated (which requires +/// releasing all acquired locks in the collection to do), then the locks will +/// stay in the same order and be locked in that order, preventing cyclic wait. /// /// [`Lockable`]: `crate::lockable::Lockable` /// [`OwnedLockable`]: `crate::lockable::OwnedLockable` @@ -30,6 +29,7 @@ pub(crate) mod utils; // collection exist #[derive(Debug)] pub struct OwnedLockCollection { + // TODO: rename to child data: L, } @@ -42,7 +42,7 @@ pub struct OwnedLockCollection { /// Upon construction, it must be confirmed that the collection contains no /// duplicate locks. This can be done by either using [`OwnedLockable`] or by /// checking. Regardless of how this is done, the locks will be sorted by their -/// memory address before locking them. The sorted order of the locks is stored +/// memory address before locking them. The sorted order of the locks is cached /// within this collection. /// /// Unlike [`BoxedLockCollection`], this type does not allocate memory for the @@ -72,7 +72,7 @@ pub struct RefLockCollection<'a, L> { /// Upon construction, it must be confirmed that the collection contains no /// duplicate locks. This can be done by either using [`OwnedLockable`] or by /// checking. Regardless of how this is done, the locks will be sorted by their -/// memory address before locking them. The sorted order of the locks is stored +/// memory address before locking them. The sorted order of the locks is cached /// within this collection. /// /// Unlike [`RefLockCollection`], this is a self-referential type which boxes @@ -95,18 +95,15 @@ pub struct BoxedLockCollection { /// can be safely locked without causing a deadlock. /// /// The data in this collection is guaranteed to not contain duplicates, but it -/// also not be sorted. In some cases the lack of sorting can increase +/// also is not sorted. In some cases the lack of sorting can increase /// performance. However, in most cases, this collection will be slower. Cyclic /// wait is not guaranteed here, so the locking algorithm must release all its /// locks if one of the lock attempts blocks. This results in wasted time and /// potential [livelocking]. /// /// However, one case where this might be faster than [`RefLockCollection`] is -/// when the first lock in the collection is always the first in any -/// collection, and the other locks in the collection are always locked after -/// that first lock is acquired. This means that as soon as it is locked, there -/// will be no need to unlock it later on subsequent lock attempts, because -/// they will always succeed. +/// when cyclic wait is ensured manually. This will prevent the need for +/// subsequent unlocking and re-locking. /// /// [`Lockable`]: `crate::lockable::Lockable` /// [`OwnedLockable`]: `crate::lockable::OwnedLockable` @@ -118,8 +115,19 @@ pub struct RetryingLockCollection { data: L, } -/// A RAII guard for a generic [`Lockable`] type. +/// A RAII guard for a generic [`Lockable`] type. When this structure is +/// dropped (falls out of scope), the locks will be unlocked. /// +/// The data protected by the mutex can be accessed through this guard via its +/// [`Deref`] and [`DerefMut`] implementations. +/// +/// Several lock collections can be used to create this type. Specifically, +/// [`BoxedLockCollection`], [`RefLockCollection`], [`OwnedLockCollection`], and +/// [`RetryingLockCollection`]. It is created using the methods, `lock`, +/// `try_lock`, `read`, and `try_read`. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` /// [`Lockable`]: `crate::lockable::Lockable` pub struct LockGuard { guard: Guard, diff --git a/src/collection/boxed.rs b/src/collection/boxed.rs index 0a30eac..7767b31 100644 --- a/src/collection/boxed.rs +++ b/src/collection/boxed.rs @@ -197,16 +197,11 @@ impl BoxedLockCollection { /// ``` /// use happylock::{Mutex, ThreadKey, LockCollection}; /// - /// let data1 = Mutex::new(42); - /// let data2 = Mutex::new(""); - /// - /// // data1 and data2 refer to distinct mutexes, so this won't panic - /// let data = (&data1, &data2); - /// let lock = LockCollection::try_new(&data).unwrap(); + /// let collection = LockCollection::try_new([Mutex::new(42), Mutex::new(1)]).unwrap(); /// /// let key = ThreadKey::get().unwrap(); - /// let guard = lock.into_child().0.lock(key); - /// assert_eq!(*guard, 42); + /// let mutex = &collection.into_child()[0]; + /// mutex.scoped_lock(key, |guard| assert_eq!(*guard, 42)); /// ``` #[must_use] pub fn into_child(mut self) -> L { @@ -232,16 +227,13 @@ impl BoxedLockCollection { /// ``` /// use happylock::{Mutex, ThreadKey, LockCollection}; /// - /// let data1 = Mutex::new(42); - /// let data2 = Mutex::new(""); + /// let collection = LockCollection::try_new([Mutex::new(42), Mutex::new(1)]).unwrap(); /// - /// // data1 and data2 refer to distinct mutexes, so this won't panic - /// let data = (&data1, &data2); - /// let lock = LockCollection::try_new(&data).unwrap(); - /// - /// let key = ThreadKey::get().unwrap(); - /// let guard = lock.child().0.lock(key); - /// assert_eq!(*guard, 42); + /// let mut key = ThreadKey::get().unwrap(); + /// let mutex1 = &collection.child()[0]; + /// let mutex2 = &collection.child()[1]; + /// mutex1.scoped_lock(&mut key, |guard| assert_eq!(*guard, 42)); + /// mutex2.scoped_lock(&mut key, |guard| assert_eq!(*guard, 1)); /// ``` #[must_use] pub fn child(&self) -> &L { @@ -381,7 +373,8 @@ impl BoxedLockCollection { scoped_try_write(self, key, f) } - /// Locks the collection + /// Locks the collection, blocking the current thread until it can be + /// acquired. /// /// This function returns a guard that can be used to access the underlying /// data. When the guard is dropped, the locks in the collection are also diff --git a/src/key.rs b/src/key.rs index 4cd145d..b29245e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -26,7 +26,7 @@ pub struct ThreadKey { phantom: PhantomData<*const ()>, // implement !Send and !Sync } -/// Allows the type to be used as a key for a lock +/// Allows the type to be used as a key for a scoped lock /// /// # Safety /// diff --git a/src/lockable.rs b/src/lockable.rs index 16e3968..43ff5c3 100644 --- a/src/lockable.rs +++ b/src/lockable.rs @@ -4,9 +4,7 @@ use std::mem::MaybeUninit; /// /// # Safety /// -/// A deadlock must never occur. The `unlock` method must correctly unlock the -/// data. The `get_ptrs` method must be implemented correctly. The `Output` -/// must be unlocked when it is dropped. +/// A deadlock must never occur when using these methods correctly. // // Why not use a RawRwLock? Because that would be semantically incorrect, and I // don't want an INIT or GuardMarker associated item. @@ -24,8 +22,8 @@ pub unsafe trait RawLock { /// # Safety /// /// It is undefined behavior to use this without ownership or mutable - /// access to the [`ThreadKey`], which should last as long as the return - /// value is alive. + /// access to the [`ThreadKey`], which should last as long as the lock is + /// held. /// /// [`ThreadKey`]: `crate::ThreadKey` unsafe fn raw_write(&self); @@ -37,8 +35,8 @@ pub unsafe trait RawLock { /// # Safety /// /// It is undefined behavior to use this without ownership or mutable - /// access to the [`ThreadKey`], which should last as long as the return - /// value is alive. + /// access to the [`ThreadKey`], which should last as long as the lock is + /// held. /// /// [`ThreadKey`]: `crate::ThreadKey` unsafe fn raw_try_write(&self) -> bool; @@ -47,7 +45,8 @@ pub unsafe trait RawLock { /// /// # Safety /// - /// It is undefined behavior to use this if the lock is not acquired + /// It is undefined behavior to use this if the lock is not acquired by the + /// calling thread. unsafe fn raw_unlock_write(&self); /// Blocks until the data the lock protects can be safely read. @@ -59,8 +58,8 @@ pub unsafe trait RawLock { /// # Safety /// /// It is undefined behavior to use this without ownership or mutable - /// access to the [`ThreadKey`], which should last as long as the return - /// value is alive. + /// access to the [`ThreadKey`], which should last as long as the lock is + /// held. /// /// [`ThreadKey`]: `crate::ThreadKey` unsafe fn raw_read(&self); @@ -76,8 +75,8 @@ pub unsafe trait RawLock { /// # Safety /// /// It is undefined behavior to use this without ownership or mutable - /// access to the [`ThreadKey`], which should last as long as the return - /// value is alive. + /// access to the [`ThreadKey`], which should last as long as the lock is + /// held. /// /// [`ThreadKey`]: `crate::ThreadKey` unsafe fn raw_try_read(&self) -> bool; @@ -86,7 +85,8 @@ pub unsafe trait RawLock { /// /// # Safety /// - /// It is undefined behavior to use this if the read lock is not acquired + /// It is undefined behavior to use this if the read lock is not held by the + /// calling thread. unsafe fn raw_unlock_read(&self); } @@ -114,6 +114,7 @@ pub unsafe trait Lockable { where Self: 'g; + /// A reference to the protected data type DataMut<'a> where Self: 'a; @@ -137,6 +138,13 @@ pub unsafe trait Lockable { #[must_use] unsafe fn guard(&self) -> Self::Guard<'_>; + /// Returns a mutable reference to the data protected by this lock. + /// + /// # Safety + /// + /// All locks given by calling [`Lockable::get_ptrs`] must be locked + /// exclusively before calling this function. The locks must not be unlocked + /// until the lifetime of this reference ends. #[must_use] unsafe fn data_mut(&self) -> Self::DataMut<'_>; } @@ -155,6 +163,7 @@ pub unsafe trait Sharable: Lockable { where Self: 'g; + /// An immutable reference to the protected data type DataRef<'a> where Self: 'a; @@ -170,6 +179,13 @@ pub unsafe trait Sharable: Lockable { #[must_use] unsafe fn read_guard(&self) -> Self::ReadGuard<'_>; + /// Creates an immutable reference to the data that is protected by this lock. + /// + /// # Safety + /// + /// All locks given by calling [`Lockable::get_ptrs`] must be locked using + /// [`RawLock::raw_read`] before calling this function. The locks must not be + /// unlocked until the lifetime of this reference ends. #[must_use] unsafe fn data_ref(&self) -> Self::DataRef<'_>; } diff --git a/src/mutex.rs b/src/mutex.rs index 413bd8a..0d6aa73 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -20,11 +20,12 @@ pub type ParkingMutex = Mutex; /// A mutual exclusion primitive useful for protecting shared data, which /// cannot deadlock. /// -/// This mutex will block threads waiting for the lock to become available. -/// Each mutex has a type parameter which represents the data that it is -/// protecting. The data can only be accessed through the [`MutexGuard`]s -/// returned from [`lock`] and [`try_lock`], which guarantees that the data is -/// only ever accessed when the mutex is locked. +/// This mutex will block threads waiting for the lock to become available. The +/// mutex can be created via a `new` constructor. Each mutex has a type +/// parameter which represents the data that it is protecting. The data can +/// only be accessed through the [`MutexGuard`]s returned from [`lock`] and +/// [`try_lock`], which guarantees that the data is only ever accessed when +/// the mutex is locked. /// /// Locking the mutex on a thread that already locked it is impossible, due to /// the requirement of the [`ThreadKey`]. Therefore, this will never deadlock. @@ -133,18 +134,37 @@ pub struct Mutex { data: UnsafeCell, } -/// A reference to a mutex that unlocks it when dropped. +/// An RAII implementation of a “scoped lock” of a mutex. When this structure +/// is dropped (falls out of scope), the lock will be unlocked. /// -/// This is similar to [`MutexGuard`], except it does not hold a [`Keyable`]. +/// The data protected by the mutex can be accessed through this guard via its +/// [`Deref`] and [`DerefMut`] implementations. +/// +/// This is created by calling the [`lock`] and [`try_lock`] methods on [`Mutex`] +/// +/// This is similar to the [`MutexGuard`] type, except it does not hold a +/// [`ThreadKey`]. +/// +/// [`lock`]: `Mutex::lock` +/// [`try_lock`]: `Mutex::try_lock` +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` pub struct MutexRef<'a, T: ?Sized + 'a, R: RawMutex>(&'a Mutex, PhantomData); -/// An RAII implementation of a “scoped lock” of a mutex. +/// An RAII implementation of a “scoped lock” of a mutex. When this structure +/// is dropped (falls out of scope), the lock will be unlocked. /// -/// When this structure is dropped (falls out of scope), the lock will be -/// unlocked. +/// The data protected by the mutex can be accessed through this guard via its +/// [`Deref`] and [`DerefMut`] implementations. /// /// This is created by calling the [`lock`] and [`try_lock`] methods on [`Mutex`] /// +/// This guard holds on to a [`ThreadKey`], which ensures that nothing else is +/// locked until this guard is dropped. The [`ThreadKey`] can be reacquired +/// using [`Mutex::unlock`]. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` /// [`lock`]: `Mutex::lock` /// [`try_lock`]: `Mutex::try_lock` // diff --git a/src/mutex/mutex.rs b/src/mutex/mutex.rs index a2813a1..05b10db 100644 --- a/src/mutex/mutex.rs +++ b/src/mutex/mutex.rs @@ -108,7 +108,7 @@ impl LockableGetMut for Mutex { unsafe impl OwnedLockable for Mutex {} impl Mutex { - /// Create a new unlocked `Mutex`. + /// Creates a `Mutex` in an unlocked state ready for use. /// /// # Examples /// @@ -283,25 +283,30 @@ impl Mutex { } impl Mutex { - /// Block the thread until this mutex can be locked, and lock it. + /// Acquires a mutex, blocking the current thread until it is able to do so. /// - /// Upon returning, the thread is the only thread with a lock on the - /// `Mutex`. A [`MutexGuard`] is returned to allow a scoped unlock of this - /// `Mutex`. When the guard is dropped, this `Mutex` will unlock. + /// This function will block the local thread until it is available to acquire + /// the mutex. Upon returning, the thread is the only thread with the lock + /// held. A [`MutexGuard`] is returned to allow a scoped unlock of this + /// `Mutex`. When the guard goes out of scope, this `Mutex` will unlock. + /// + /// Due to the requirement of a [`ThreadKey`] to call this function, it is not + /// possible for this function to deadlock. /// /// # Examples /// /// ``` - /// use std::{thread, sync::Arc}; + /// use std::thread; /// use happylock::{Mutex, ThreadKey}; /// - /// let mutex = Arc::new(Mutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Mutex::new(0); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// *c_mutex.lock(key) = 10; - /// }).join().expect("thread::spawn failed"); + /// thread::scope(|s| { + /// s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// *mutex.lock(key) = 10; + /// }); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key), 10); @@ -316,35 +321,36 @@ impl Mutex { } } - /// Attempts to lock the `Mutex` without blocking. + /// Attempts to lock this `Mutex` without blocking. /// - /// If the access could not be granted at this time, then `Err` is - /// returned. Otherwise, an RAII guard is returned which will release the - /// lock when it is dropped. + /// If the lock could not be acquired at this time, then `Err` is returned. + /// Otherwise, an RAII guard is returned. The lock will be unlocked when the + /// guard is dropped. /// /// # Errors /// /// If the mutex could not be acquired because it is already locked, then - /// this call will return an error containing the given key. + /// this call will return an error containing the [`ThreadKey`]. /// /// # Examples /// /// ``` - /// use std::{thread, sync::Arc}; + /// use std::thread; /// use happylock::{Mutex, ThreadKey}; /// - /// let mutex = Arc::new(Mutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Mutex::new(0); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let mut lock = c_mutex.try_lock(key); - /// if let Ok(mut lock) = lock { - /// *lock = 10; - /// } else { - /// println!("try_lock failed"); - /// } - /// }).join().expect("thread::spawn failed"); + /// thread::scope(|s| { + /// s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let mut lock = mutex.try_lock(key); + /// if let Ok(mut lock) = lock { + /// *lock = 10; + /// } else { + /// println!("try_lock failed"); + /// } + /// }); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key), 10); @@ -375,6 +381,10 @@ impl Mutex { /// Consumes the [`MutexGuard`], and consequently unlocks its `Mutex`. /// + /// This function is equivalent to calling [`drop`] on the guard, except that + /// it returns the key that was used to create it. Alernatively, the guard + /// will be automatically dropped when it goes out of scope. + /// /// # Examples /// /// ``` @@ -387,6 +397,9 @@ impl Mutex { /// *guard += 20; /// /// let key = Mutex::unlock(guard); + /// + /// let guard = mutex.lock(key); + /// assert_eq!(*guard, 20); /// ``` #[must_use] pub fn unlock(guard: MutexGuard<'_, T, R>) -> ThreadKey { diff --git a/src/poisonable.rs b/src/poisonable.rs index f9b0622..74444db 100644 --- a/src/poisonable.rs +++ b/src/poisonable.rs @@ -8,6 +8,8 @@ mod flag; mod guard; mod poisonable; +// TODO add helper types for poisonable mutex and so on + /// A flag indicating if a lock is poisoned or not. The implementation differs /// depending on whether panics are set to unwind or abort. #[derive(Debug, Default)] @@ -20,19 +22,25 @@ pub(crate) struct PoisonFlag(#[cfg(panic = "unwind")] AtomicBool); /// default, because the data may be tainted (some invariant of the data might /// not be upheld). /// -/// The [`lock`] and [`try_lock`] methods return a [`Result`] which indicates -/// whether the lock has been poisoned or not. The [`PoisonError`] type has an -/// [`into_inner`] method which will return the guard that normally would have -/// been returned for a successful lock. This allows access to the data, -/// despite the lock being poisoned. +/// The [`lock`], [`try_lock`], [`read`], and [`try_read`] methods return a +/// [`Result`] which indicates whether the lock has been poisoned or not. The +/// [`PoisonError`] type has an [`into_inner`] method which will return the +/// guard that normally would have been returned for a successful lock. This +/// allows access to the data, despite the lock being poisoned. The scoped +/// locking methods (such as [`scoped_lock`]) will pass the [`Result`] into the +/// given closure. Poisoning will occur if the closure panics. /// /// Alternatively, there is also a [`clear_poison`] method, which should /// indicate that all invariants of the underlying data are upheld, so that /// subsequent calls may still return [`Ok`]. /// +/// /// [`Lockable`]: `crate::lockable::Lockable` /// [`lock`]: `Poisonable::lock` /// [`try_lock`]: `Poisonable::try_lock` +/// [`read`]: `Poisonable::read` +/// [`try_read`]: `Poisonable::try_read` +/// [`scoped_lock`]: `Poisonable::scoped_lock` /// [`into_inner`]: `PoisonError::into_inner` /// [`clear_poison`]: `Poisonable::clear_poison` #[derive(Debug, Default)] @@ -41,12 +49,23 @@ pub struct Poisonable { poisoned: PoisonFlag, } -/// An RAII guard for a [`Poisonable`]. +/// An RAII guard for a [`Poisonable`]. When this structure is dropped (falls +/// out of scope), the lock will be unlocked. /// /// This is similar to a [`PoisonGuard`], except that it does not hold a -/// [`Keyable`] +/// [`ThreadKey`]. +/// +/// The data protected by the underlying lock can be accessed through this +/// guard via its [`Deref`] and [`DerefMut`] implementations. /// -/// [`Keyable`]: `crate::Keyable` +/// This structure is created when passing a `Poisonable` into another lock +/// wrapper, such as [`LockCollection`], and obtaining a guard through the +/// wrapper type. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` +/// [`ThreadKey`]: `crate::ThreadKey` +/// [`LockCollection`]: `crate::LockCollection` pub struct PoisonRef<'a, G> { guard: G, #[cfg(panic = "unwind")] @@ -54,15 +73,37 @@ pub struct PoisonRef<'a, G> { _phantom: PhantomData<&'a ()>, } -/// An RAII guard for a [`Poisonable`]. +/// An RAII guard for a [`Poisonable`]. When this structure is dropped (falls +/// out of scope), the lock will be unlocked. +/// +/// The data protected by the underlying lock can be accessed through this +/// guard via its [`Deref`] and [`DerefMut`] implementations. /// -/// This is created by calling methods like [`Poisonable::lock`]. +/// This method is created by calling the [`lock`], [`try_lock`], [`read`], and +/// [`try_read`] methods on [`Poisonable`] +/// +/// This guard holds a [`ThreadKey`], so it is not possible to lock anything +/// else until this guard is dropped. The [`ThreadKey`] can be reacquired by +/// calling [`Poisonable::unlock`], or [`Poisonable::unlock_read`]. +/// +/// [`lock`]: `Poisonable::lock` +/// [`try_lock`]: `Poisonable::try_lock` +/// [`read`]: `Poisonable::read` +/// [`try_read`]: `Poisonable::try_read` +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` +/// [`ThreadKey`]: `crate::ThreadKey` +/// [`LockCollection`]: `crate::LockCollection` pub struct PoisonGuard<'a, G> { guard: PoisonRef<'a, G>, key: ThreadKey, } /// A type of error which can be returned when acquiring a [`Poisonable`] lock. +/// +/// A [`Poisonable`] is poisoned whenever a thread fails while the lock is +/// held. For a lock in the poisoned state, unless the state is cleared +/// manually, all future acquisitions will return this error. pub struct PoisonError { guard: Guard, } @@ -78,7 +119,8 @@ pub enum TryLockPoisonableError<'flag, G> { /// A type alias for the result of a lock method which can poisoned. /// /// The [`Ok`] variant of this result indicates that the primitive was not -/// poisoned, and the primitive was poisoned. Note that the [`Err`] variant +/// poisoned, and the operation result is contained within. The [`Err`] variant +/// indicates that the primitive was poisoned. Note that the [`Err`] variant /// *also* carries the associated guard, and it can be acquired through the /// [`into_inner`] method. /// diff --git a/src/poisonable/error.rs b/src/poisonable/error.rs index b69df5d..eed454b 100644 --- a/src/poisonable/error.rs +++ b/src/poisonable/error.rs @@ -38,12 +38,6 @@ impl PoisonError { /// /// This is generally created by methods like [`Poisonable::lock`]. /// - /// ``` - /// use happylock::poisonable::PoisonError; - /// - /// let error = PoisonError::new("oh no"); - /// ``` - /// /// [`Poisonable::lock`]: `crate::poisonable::Poisonable::lock` #[must_use] pub const fn new(guard: Guard) -> Self { @@ -57,21 +51,21 @@ impl PoisonError { /// /// ``` /// use std::collections::HashSet; - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(HashSet::new()))); + /// let mutex = Poisonable::new(Mutex::new(HashSet::new())); /// /// // poison the mutex - /// let c_mutex = Arc::clone(&mutex); - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let mut data = c_mutex.lock(key).unwrap(); - /// data.insert(10); - /// panic!(); - /// }).join(); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let mut data = mutex.lock(key).unwrap(); + /// data.insert(10); + /// panic!(); + /// }).join(); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// let p_err = mutex.lock(key).unwrap_err(); @@ -90,22 +84,22 @@ impl PoisonError { /// /// ``` /// use std::collections::HashSet; - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// use happylock::poisonable::PoisonGuard; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(HashSet::new()))); + /// let mutex = Poisonable::new(Mutex::new(HashSet::new())); /// /// // poison the mutex - /// let c_mutex = Arc::clone(&mutex); - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let mut data = c_mutex.lock(key).unwrap(); - /// data.insert(10); - /// panic!(); - /// }).join(); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let mut data = mutex.lock(key).unwrap(); + /// data.insert(10); + /// panic!(); + /// }).join(); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// let p_err = mutex.lock(key).unwrap_err(); @@ -124,21 +118,21 @@ impl PoisonError { /// /// ``` /// use std::collections::HashSet; - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(HashSet::new()))); + /// let mutex =Poisonable::new(Mutex::new(HashSet::new())); /// /// // poison the mutex - /// let c_mutex = Arc::clone(&mutex); - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let mut data = c_mutex.lock(key).unwrap(); - /// data.insert(10); - /// panic!(); - /// }).join(); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let mut data = mutex.lock(key).unwrap(); + /// data.insert(10); + /// panic!(); + /// }).join(); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// let mut p_err = mutex.lock(key).unwrap_err(); diff --git a/src/poisonable/poisonable.rs b/src/poisonable/poisonable.rs index ff78330..c098041 100644 --- a/src/poisonable/poisonable.rs +++ b/src/poisonable/poisonable.rs @@ -161,28 +161,28 @@ impl Poisonable { } } - /// Determines whether the mutex is poisoned. + /// Determines whether the `Poisonable` is poisoned. /// - /// If another thread is active, the mutex can still become poisoned at any - /// time. You should not trust a `false` value for program correctness + /// If another thread is active, the `Poisonable` can still become poisoned at + /// any time. You should not trust a `false` value for program correctness /// without additional synchronization. /// /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Poisonable::new(Mutex::new(0)); /// - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let _lock = c_mutex.lock(key).unwrap(); - /// panic!(); // the mutex gets poisoned - /// }).join(); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let _lock = mutex.lock(key).unwrap(); + /// panic!(); // the mutex gets poisoned + /// }).join(); + /// }); /// /// assert_eq!(mutex.is_poisoned(), true); /// ``` @@ -202,19 +202,19 @@ impl Poisonable { /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Poisonable::new(Mutex::new(0)); /// - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let _lock = c_mutex.lock(key).unwrap(); - /// panic!(); // the mutex gets poisoned - /// }).join(); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let _lock = mutex.lock(key).unwrap(); + /// panic!(); // the mutex gets poisoned + /// }).join(); + /// }); /// /// assert_eq!(mutex.is_poisoned(), true); /// @@ -234,9 +234,6 @@ impl Poisonable { /// Consumes this `Poisonable`, returning the underlying lock. /// - /// This consumes the `Poisonable` and returns ownership of the lock, which - /// means that the `Poisonable` can still be `RefUnwindSafe`. - /// /// # Errors /// /// If another user of this lock panicked while holding the lock, then this @@ -260,9 +257,6 @@ impl Poisonable { /// Returns a mutable reference to the underlying lock. /// - /// This can be implemented while still being `RefUnwindSafe` because - /// it requires a mutable reference. - /// /// # Errors /// /// If another user of this lock panicked while holding the lock, then @@ -287,7 +281,6 @@ impl Poisonable { } // NOTE: `child_ref` isn't implemented because it would make this not `RefUnwindSafe` - // } impl Poisonable { @@ -366,30 +359,30 @@ impl Poisonable { /// Acquires the lock, blocking the current thread until it is ok to do so. /// /// This function will block the current thread until it is available to - /// acquire the mutex. Upon returning, the thread is the only thread with + /// acquire the lock. Upon returning, the thread is the only thread with /// the lock held. An RAII guard is returned to allow scoped unlock of the /// lock. When the guard goes out of scope, the mutex will be unlocked. /// /// # Errors /// - /// If another use of this mutex panicked while holding the mutex, then + /// If another use of this lock panicked while holding the mutex, then /// this call will return an error once the mutex is acquired. /// /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Poisonable::new(Mutex::new(0)); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// *c_mutex.lock(key).unwrap() = 10; - /// }).join().expect("thread::spawn failed"); + /// thread::scope(|s| { + /// let r = s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// *mutex.lock(key).unwrap() = 10; + /// }).join(); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key).unwrap(), 10); @@ -411,33 +404,33 @@ impl Poisonable { /// /// # Errors /// - /// If another user of this mutex panicked while holding the mutex, then - /// this call will return the [`Poisoned`] error if the mutex would - /// otherwise be acquired. + /// If another user of this lock panicked while holding the lock, then this + /// call will return the [`Poisoned`] error if the lock would otherwise be + /// acquired. /// - /// If the mutex could not be acquired because it is already locked, then + /// If the lock could not be acquired because it is already locked, then /// this call will return the [`WouldBlock`] error. /// /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{Mutex, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// let mutex = Poisonable::new(Mutex::new(0)); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let mut lock = c_mutex.try_lock(key); - /// if let Ok(mut mutex) = lock { - /// *mutex = 10; - /// } else { - /// println!("try_lock failed"); - /// } - /// }).join().expect("thread::spawn failed"); + /// thread::scope(|s| { + /// s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let mut lock = mutex.try_lock(key); + /// if let Ok(mut mutex) = lock { + /// *mutex = 10; + /// } else { + /// println!("try_lock failed"); + /// } + /// }); + /// }); /// /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key).unwrap(), 10); @@ -457,6 +450,10 @@ impl Poisonable { /// Consumes the [`PoisonGuard`], and consequently unlocks its `Poisonable`. /// + /// This function is equivalent to calling [`drop`] on the guard, except that + /// it returns the key that was used to create it. Alternatively, the guard + /// will be automatically dropped when it goes out of scope. + /// /// # Examples /// /// ``` @@ -556,28 +553,31 @@ impl Poisonable { /// /// # Errors /// - /// If another use of this lock panicked while holding the lock, then - /// this call will return an error once the lock is acquired. + /// This function will return an error if the `Poisonable` is poisoned. A + /// `Poisonable` is poisoned whenever a thread panics while holding a lock. + /// The failure will occur immediately after the lock has been acquired. The + /// acquired lock guard will be contained in the returned error. /// /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// /// use happylock::{RwLock, Poisonable, ThreadKey}; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = Arc::new(Poisonable::new(RwLock::new(0))); - /// let c_lock = Arc::clone(&lock); + /// let lock = Poisonable::new(RwLock::new(0)); /// /// let n = lock.read(key).unwrap(); /// assert_eq!(*n, 0); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// assert!(c_lock.read(key).is_ok()); - /// }).join().expect("thread::spawn failed"); + /// thread::scope(|s| { + /// s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let r = lock.read(key); + /// assert!(r.is_ok()); + /// }); + /// }); /// ``` pub fn read(&self, key: ThreadKey) -> PoisonResult>> { unsafe { @@ -586,23 +586,22 @@ impl Poisonable { } } - /// Attempts to acquire the lock with shared read access. + /// Attempts to acquire the lock with shared read access, without blocking the + /// thread. /// /// If the access could not be granted at this time, then `Err` is returned. /// Otherwise, an RAII guard is returned which will release the shared access /// when it is dropped. /// - /// This function does not block. - /// /// This function does not provide any guarantees with respect to the ordering /// of whether contentious readers or writers will acquire the lock first. /// /// # Errors /// /// This function will return the [`Poisoned`] error if the lock is - /// poisoned. A [`Poisonable`] is poisoned whenever a writer panics while - /// holding an exclusive lock. `Poisoned` will only be returned if the lock - /// would have otherwise been acquired. + /// poisoned. A [`Poisonable`] is poisoned whenever a thread panics while + /// holding a lock. `Poisoned` will only be returned if the lock would have + /// otherwise been acquired. /// /// This function will return the [`WouldBlock`] error if the lock could /// not be acquired because it was already locked exclusively. @@ -623,6 +622,7 @@ impl Poisonable { /// /// [`Poisoned`]: `TryLockPoisonableError::Poisoned` /// [`WouldBlock`]: `TryLockPoisonableError::WouldBlock` + // TODO don't poison when holding shared lock pub fn try_read(&self, key: ThreadKey) -> TryLockPoisonableResult<'_, L::ReadGuard<'_>> { unsafe { if self.inner.raw_try_read() { @@ -633,7 +633,11 @@ impl Poisonable { } } - /// Consumes the [`PoisonGuard`], and consequently unlocks its underlying lock. + /// Consumes the [`PoisonGuard`], and consequently unlocks its `Poisonable`. + /// + /// This function is equivalent to calling [`drop`] on the guard, except that + /// it returns the key that was used to create it. Alternatively, the guard + /// will be automatically dropped when it goes out of scope. /// /// # Examples /// @@ -641,9 +645,11 @@ impl Poisonable { /// use happylock::{ThreadKey, RwLock, Poisonable}; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = Poisonable::new(RwLock::new(0)); + /// let lock = Poisonable::new(RwLock::new(20)); /// /// let mut guard = lock.read(key).unwrap(); + /// assert_eq!(*guard, 20); + /// /// let key = Poisonable::>::unlock_read(guard); /// ``` pub fn unlock_read<'flag>(guard: PoisonGuard<'flag, L::ReadGuard<'flag>>) -> ThreadKey { @@ -658,7 +664,8 @@ impl Poisonable { /// # Errors /// /// If another user of this lock panicked while holding the lock, then this - /// call will return an error instead. + /// call will return an error instead. A `Poisonable` is poisoned whenever a + /// thread panics while holding a lock. /// /// # Examples /// @@ -683,7 +690,8 @@ impl Poisonable { /// # Errors /// /// If another user of this lock panicked while holding the lock, then - /// this call will return an error instead. + /// this call will return an error instead. A `Poisonable` is poisoned + /// whenever a thread panics while holding a lock. /// /// # Examples /// diff --git a/src/rwlock.rs b/src/rwlock.rs index f5c0ec5..fca132d 100644 --- a/src/rwlock.rs +++ b/src/rwlock.rs @@ -8,9 +8,6 @@ use crate::ThreadKey; mod rwlock; -mod read_lock; -mod write_lock; - mod read_guard; mod write_guard; @@ -39,7 +36,7 @@ pub type ParkingRwLock = RwLock; /// methods) to allow access to the content of the lock. /// /// Locking the mutex on a thread that already locked it is impossible, due to -/// the requirement of the [`ThreadKey`]. Therefore, this will never deadlock. +/// the requirement of the [`ThreadKey`]. This will never deadlock. /// /// [`ThreadKey`]: `crate::ThreadKey` /// [`Mutex`]: `crate::mutex::Mutex` @@ -51,37 +48,37 @@ pub struct RwLock { data: UnsafeCell, } -/// Grants read access to an [`RwLock`] -/// -/// This structure is designed to be used in a [`LockCollection`] to indicate -/// that only read access is needed to the data. +/// RAII structure that unlocks the shared read access to a [`RwLock`] when +/// dropped. /// -/// [`LockCollection`]: `crate::LockCollection` -#[repr(transparent)] -struct ReadLock<'l, T: ?Sized, R>(&'l RwLock); - -/// Grants write access to an [`RwLock`] +/// This structure is created when the [`RwLock`] is put in a wrapper type, +/// such as [`LockCollection`], and a read-only guard is obtained through the +/// wrapper. /// -/// This structure is designed to be used in a [`LockCollection`] to indicate -/// that write access is needed to the data. +/// This is similar to [`RwLockReadGuard`], except it does not hold a +/// [`ThreadKey`]. /// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` /// [`LockCollection`]: `crate::LockCollection` -#[repr(transparent)] -struct WriteLock<'l, T: ?Sized, R>(&'l RwLock); - -/// RAII structure that unlocks the shared read access to a [`RwLock`] -/// -/// This is similar to [`RwLockReadRef`], except it does not hold a -/// [`Keyable`]. pub struct RwLockReadRef<'a, T: ?Sized, R: RawRwLock>( &'a RwLock, PhantomData, ); -/// RAII structure that unlocks the exclusive write access to a [`RwLock`] +/// RAII structure that unlocks the exclusive write access to a [`RwLock`] when +/// dropped. +/// +/// This structure is created when the [`RwLock`] is put in a wrapper type, +/// such as [`LockCollection`], and a mutable guard is obtained through the +/// wrapper. /// -/// This is similar to [`RwLockWriteRef`], except it does not hold a -/// [`Keyable`]. +/// This is similar to [`RwLockWriteGuard`], except it does not hold a +/// [`ThreadKey`]. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` +/// [`LockCollection`]: `crate::LockCollection` pub struct RwLockWriteRef<'a, T: ?Sized, R: RawRwLock>( &'a RwLock, PhantomData, @@ -93,6 +90,12 @@ pub struct RwLockWriteRef<'a, T: ?Sized, R: RawRwLock>( /// This structure is created by the [`read`] and [`try_read`] methods on /// [`RwLock`]. /// +/// This guard holds a [`ThreadKey`] for its entire lifetime. Therefore, a new +/// lock cannot be acquired until this one is dropped. The [`ThreadKey`] can be +/// reacquired using [`RwLock::unlock_read`]. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` /// [`read`]: `RwLock::read` /// [`try_read`]: `RwLock::try_read` pub struct RwLockReadGuard<'a, T: ?Sized, R: RawRwLock> { @@ -106,6 +109,12 @@ pub struct RwLockReadGuard<'a, T: ?Sized, R: RawRwLock> { /// This structure is created by the [`write`] and [`try_write`] methods on /// [`RwLock`] /// +/// This guard holds a [`ThreadKey`] for its entire lifetime. Therefor, a new +/// lock cannot be acquired until this one is dropped. The [`ThreadKey`] can be +/// reacquired using [`RwLock::unlock_write`]. +/// +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` /// [`try_write`]: `RwLock::try_write` pub struct RwLockWriteGuard<'a, T: ?Sized, R: RawRwLock> { rwlock: RwLockWriteRef<'a, T, R>, @@ -114,14 +123,10 @@ pub struct RwLockWriteGuard<'a, T: ?Sized, R: RawRwLock> { #[cfg(test)] mod tests { - use crate::lockable::Lockable; - use crate::lockable::RawLock; use crate::LockCollection; use crate::RwLock; use crate::ThreadKey; - use super::*; - #[test] fn unlocked_when_initialized() { let key = ThreadKey::get().unwrap(); @@ -131,112 +136,6 @@ mod tests { assert!(lock.try_write(key).is_ok()); } - #[test] - fn read_lock_unlocked_when_initialized() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new("Hello, world!"); - let reader = ReadLock::new(&lock); - - assert!(reader.try_lock(key).is_ok()); - } - - #[test] - fn read_lock_from_works() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::from("Hello, world!"); - let reader = ReadLock::from(&lock); - - let guard = reader.lock(key); - assert_eq!(*guard, "Hello, world!"); - } - - #[test] - fn read_lock_scoped_works() { - let mut key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new(42); - let reader = ReadLock::new(&lock); - - reader.scoped_lock(&mut key, |num| assert_eq!(*num, 42)); - } - - #[test] - fn read_lock_scoped_try_fails_during_write() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new(42); - let reader = ReadLock::new(&lock); - let guard = lock.write(key); - - std::thread::scope(|s| { - s.spawn(|| { - let key = ThreadKey::get().unwrap(); - let r = reader.scoped_try_lock(key, |_| {}); - assert!(r.is_err()); - }); - }); - - drop(guard); - } - - #[test] - fn write_lock_unlocked_when_initialized() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new("Hello, world!"); - let writer = WriteLock::new(&lock); - - assert!(writer.try_lock(key).is_ok()); - } - - #[test] - #[ignore = "We've removed ReadLock"] - fn read_lock_get_ptrs() { - let rwlock = RwLock::new(5); - let readlock = ReadLock::new(&rwlock); - let mut lock_ptrs = Vec::new(); - readlock.get_ptrs(&mut lock_ptrs); - - assert_eq!(lock_ptrs.len(), 1); - assert!(std::ptr::addr_eq(lock_ptrs[0], &readlock)); - } - - #[test] - #[ignore = "We've removed WriteLock"] - fn write_lock_get_ptrs() { - let rwlock = RwLock::new(5); - let writelock = WriteLock::new(&rwlock); - let mut lock_ptrs = Vec::new(); - writelock.get_ptrs(&mut lock_ptrs); - - assert_eq!(lock_ptrs.len(), 1); - assert!(std::ptr::addr_eq(lock_ptrs[0], &writelock)); - } - - #[test] - fn write_lock_scoped_works() { - let mut key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new(42); - let writer = WriteLock::new(&lock); - - writer.scoped_lock(&mut key, |num| assert_eq!(*num, 42)); - } - - #[test] - fn write_lock_scoped_try_fails_during_write() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new(42); - let writer = WriteLock::new(&lock); - let guard = lock.write(key); - - std::thread::scope(|s| { - s.spawn(|| { - let key = ThreadKey::get().unwrap(); - let r = writer.scoped_try_lock(key, |_| {}); - assert!(r.is_err()); - }); - }); - - drop(guard); - } - #[test] fn locked_after_read() { let key = ThreadKey::get().unwrap(); @@ -248,18 +147,6 @@ mod tests { drop(guard) } - #[test] - fn locked_after_using_read_lock() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new("Hello, world!"); - let reader = ReadLock::new(&lock); - - let guard = reader.lock(key); - - assert!(lock.is_locked()); - drop(guard) - } - #[test] fn locked_after_write() { let key = ThreadKey::get().unwrap(); @@ -271,18 +158,6 @@ mod tests { drop(guard) } - #[test] - fn locked_after_using_write_lock() { - let key = ThreadKey::get().unwrap(); - let lock: crate::RwLock<_> = RwLock::new("Hello, world!"); - let writer = WriteLock::new(&lock); - - let guard = writer.lock(key); - - assert!(lock.is_locked()); - drop(guard) - } - #[test] fn locked_after_scoped_write() { let mut key = ThreadKey::get().unwrap(); @@ -423,112 +298,6 @@ mod tests { assert_eq!(*guard, "Hello, world"); } - #[test] - fn unlock_read_lock() { - let key = ThreadKey::get().unwrap(); - let lock = crate::RwLock::new("Hello, world"); - let reader = ReadLock::new(&lock); - - let guard = reader.lock(key); - let key = ReadLock::unlock(guard); - - lock.write(key); - } - - #[test] - fn unlock_write_lock() { - let key = ThreadKey::get().unwrap(); - let lock = crate::RwLock::new("Hello, world"); - let writer = WriteLock::from(&lock); - - let guard = writer.lock(key); - let key = WriteLock::unlock(guard); - - lock.write(key); - } - - #[test] - #[ignore = "We've removed ReadLock"] - fn read_lock_in_collection() { - let mut key = ThreadKey::get().unwrap(); - let lock = crate::RwLock::new("hi"); - let collection = LockCollection::try_new(ReadLock::new(&lock)).unwrap(); - - collection.scoped_lock(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }); - collection.scoped_read(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }); - assert!(collection - .scoped_try_lock(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }) - .is_ok()); - assert!(collection - .scoped_try_read(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }) - .is_ok()); - - let guard = collection.lock(key); - assert_eq!(**guard, "hi"); - - let key = LockCollection::>::unlock(guard); - let guard = collection.read(key); - assert_eq!(**guard, "hi"); - - let key = LockCollection::>::unlock(guard); - let guard = lock.write(key); - - std::thread::scope(|s| { - s.spawn(|| { - let key = ThreadKey::get().unwrap(); - let guard = collection.try_lock(key); - assert!(guard.is_err()); - }); - s.spawn(|| { - let key = ThreadKey::get().unwrap(); - let guard = collection.try_read(key); - assert!(guard.is_err()); - }); - }); - - drop(guard); - } - - #[test] - fn write_lock_in_collection() { - let mut key = ThreadKey::get().unwrap(); - let lock = crate::RwLock::new("hi"); - let collection = LockCollection::try_new(WriteLock::new(&lock)).unwrap(); - - collection.scoped_lock(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }); - assert!(collection - .scoped_try_lock(&mut key, |guard| { - assert_eq!(*guard, "hi"); - }) - .is_ok()); - - let guard = collection.lock(key); - assert_eq!(**guard, "hi"); - - let key = LockCollection::>::unlock(guard); - let guard = lock.write(key); - - std::thread::scope(|s| { - s.spawn(|| { - let key = ThreadKey::get().unwrap(); - let guard = collection.try_lock(key); - assert!(guard.is_err()); - }); - }); - - drop(guard); - } - #[test] fn read_ref_as_ref() { let key = ThreadKey::get().unwrap(); @@ -575,22 +344,4 @@ mod tests { *guard.as_mut() = "foo"; assert_eq!(*guard.as_mut(), "foo"); } - - #[test] - fn poison_read_lock() { - let lock = crate::RwLock::new("hi"); - let reader = ReadLock::new(&lock); - - reader.poison(); - assert!(lock.poison.is_poisoned()); - } - - #[test] - fn poison_write_lock() { - let lock = crate::RwLock::new("hi"); - let reader = WriteLock::new(&lock); - - reader.poison(); - assert!(lock.poison.is_poisoned()); - } } diff --git a/src/rwlock/read_lock.rs b/src/rwlock/read_lock.rs deleted file mode 100644 index f13f2b9..0000000 --- a/src/rwlock/read_lock.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::fmt::Debug; - -use lock_api::RawRwLock; - -use crate::lockable::{Lockable, RawLock, Sharable}; -use crate::{Keyable, ThreadKey}; - -use super::{ReadLock, RwLock, RwLockReadGuard, RwLockReadRef}; - -unsafe impl RawLock for ReadLock<'_, T, R> { - fn poison(&self) { - self.0.poison() - } - - unsafe fn raw_write(&self) { - self.0.raw_read() - } - - unsafe fn raw_try_write(&self) -> bool { - self.0.raw_try_read() - } - - unsafe fn raw_unlock_write(&self) { - self.0.raw_unlock_read() - } - - unsafe fn raw_read(&self) { - self.0.raw_read() - } - - unsafe fn raw_try_read(&self) -> bool { - self.0.raw_try_read() - } - - unsafe fn raw_unlock_read(&self) { - self.0.raw_unlock_read() - } -} - -unsafe impl Lockable for ReadLock<'_, T, R> { - type Guard<'g> - = RwLockReadRef<'g, T, R> - where - Self: 'g; - - type DataMut<'a> - = &'a T - where - Self: 'a; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - ptrs.push(self.0); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - RwLockReadRef::new(self.as_ref()) - } - - unsafe fn data_mut(&self) -> Self::DataMut<'_> { - self.0.data_ref() - } -} - -unsafe impl Sharable for ReadLock<'_, T, R> { - type ReadGuard<'g> - = RwLockReadRef<'g, T, R> - where - Self: 'g; - - type DataRef<'a> - = &'a T - where - Self: 'a; - - unsafe fn read_guard(&self) -> Self::Guard<'_> { - RwLockReadRef::new(self.as_ref()) - } - - unsafe fn data_ref(&self) -> Self::DataRef<'_> { - self.0.data_ref() - } -} - -#[mutants::skip] -#[cfg(not(tarpaulin_include))] -impl Debug for ReadLock<'_, T, R> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // safety: this is just a try lock, and the value is dropped - // immediately after, so there's no risk of blocking ourselves - // or any other threads - if let Some(value) = unsafe { self.try_lock_no_key() } { - f.debug_struct("ReadLock").field("data", &&*value).finish() - } else { - struct LockedPlaceholder; - impl Debug for LockedPlaceholder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("") - } - } - - f.debug_struct("ReadLock") - .field("data", &LockedPlaceholder) - .finish() - } - } -} - -impl<'l, T, R> From<&'l RwLock> for ReadLock<'l, T, R> { - fn from(value: &'l RwLock) -> Self { - Self::new(value) - } -} - -impl AsRef> for ReadLock<'_, T, R> { - fn as_ref(&self) -> &RwLock { - self.0 - } -} - -impl<'l, T, R> ReadLock<'l, T, R> { - /// Creates a new `ReadLock` which accesses the given [`RwLock`] - /// - /// # Examples - /// - /// ``` - /// use happylock::{rwlock::ReadLock, RwLock}; - /// - /// let lock = RwLock::new(5); - /// let read_lock = ReadLock::new(&lock); - /// ``` - #[must_use] - pub const fn new(rwlock: &'l RwLock) -> Self { - Self(rwlock) - } -} - -impl ReadLock<'_, T, R> { - pub fn scoped_lock<'a, Ret>(&'a self, key: impl Keyable, f: impl Fn(&'a T) -> Ret) -> Ret { - self.0.scoped_read(key, f) - } - - pub fn scoped_try_lock<'a, Key: Keyable, Ret>( - &'a self, - key: Key, - f: impl Fn(&'a T) -> Ret, - ) -> Result { - self.0.scoped_try_read(key, f) - } - - /// Locks the underlying [`RwLock`] with shared read access, blocking the - /// current thread until it can be acquired. - /// - /// The calling thread will be blocked until there are no more writers - /// which hold the lock. There may be other readers currently inside the - /// lock when this method returns. - /// - /// Returns an RAII guard which will release this thread's shared access - /// once it is dropped. - /// - /// Because this method takes a [`ThreadKey`], it's not possible for this - /// method to cause a deadlock. - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; - /// use happylock::{RwLock, ThreadKey}; - /// use happylock::rwlock::ReadLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock: RwLock<_> = RwLock::new(1); - /// let reader = ReadLock::new(&lock); - /// - /// let n = reader.lock(key); - /// assert_eq!(*n, 1); - /// ``` - /// - /// [`ThreadKey`]: `crate::ThreadKey` - #[must_use] - pub fn lock(&self, key: ThreadKey) -> RwLockReadGuard<'_, T, R> { - self.0.read(key) - } - - /// Attempts to acquire the underlying [`RwLock`] with shared read access - /// without blocking. - /// - /// If the access could not be granted at this time, then `Err` is - /// returned. Otherwise, an RAII guard is returned which will release the - /// shared access when it is dropped. - /// - /// This function does not provide any guarantees with respect to the - /// ordering of whether contentious readers or writers will acquire the - /// lock first. - /// - /// # Errors - /// - /// If the `RwLock` could not be acquired because it was already locked - /// exclusively, then an error will be returned containing the given key. - /// - /// # Examples - /// - /// ``` - /// use happylock::{RwLock, ThreadKey}; - /// use happylock::rwlock::ReadLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock = RwLock::new(1); - /// let reader = ReadLock::new(&lock); - /// - /// match reader.try_lock(key) { - /// Ok(n) => assert_eq!(*n, 1), - /// Err(_) => unreachable!(), - /// }; - /// ``` - pub fn try_lock(&self, key: ThreadKey) -> Result, ThreadKey> { - self.0.try_read(key) - } - - /// Attempts to create an exclusive lock without a key. Locking this - /// without exclusive access to the key is undefined behavior. - pub(crate) unsafe fn try_lock_no_key(&self) -> Option> { - self.0.try_read_no_key() - } - - /// Immediately drops the guard, and consequently releases the shared lock - /// on the underlying [`RwLock`]. - /// - /// This function is equivalent to calling [`drop`] on the guard, except - /// that it returns the key that was used to create it. Alternately, the - /// guard will be automatically dropped when it goes out of scope. - /// - /// # Examples - /// - /// ``` - /// use happylock::{RwLock, ThreadKey}; - /// use happylock::rwlock::ReadLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock = RwLock::new(0); - /// let reader = ReadLock::new(&lock); - /// - /// let mut guard = reader.lock(key); - /// assert_eq!(*guard, 0); - /// let key = ReadLock::unlock(guard); - /// ``` - #[must_use] - pub fn unlock(guard: RwLockReadGuard<'_, T, R>) -> ThreadKey { - RwLock::unlock_read(guard) - } -} diff --git a/src/rwlock/rwlock.rs b/src/rwlock/rwlock.rs index f1cdca5..0dce710 100644 --- a/src/rwlock/rwlock.rs +++ b/src/rwlock/rwlock.rs @@ -147,6 +147,8 @@ impl RwLock { /// use happylock::RwLock; /// /// let lock = RwLock::new(5); + /// + /// /// ``` #[must_use] pub const fn new(data: T) -> Self { @@ -211,9 +213,9 @@ impl RwLock { /// ``` /// use happylock::{RwLock, ThreadKey}; /// + /// let key = ThreadKey::get().unwrap(); /// let lock = RwLock::new(String::new()); /// { - /// let key = ThreadKey::get().unwrap(); /// let mut s = lock.write(key); /// *s = "modified".to_owned(); /// } @@ -228,7 +230,7 @@ impl RwLock { impl RwLock { /// Returns a mutable reference to the underlying data. /// - /// Since this call borrows `RwLock` mutably, no actual locking is taking + /// Since this call borrows `RwLock` mutably, no actual locking needs to take /// place. The mutable borrow statically guarantees that no locks exist. /// /// # Examples @@ -237,9 +239,9 @@ impl RwLock { /// use happylock::{ThreadKey, RwLock}; /// /// let key = ThreadKey::get().unwrap(); - /// let mut mutex = RwLock::new(0); - /// *mutex.get_mut() = 10; - /// assert_eq!(*mutex.read(key), 10); + /// let mut lock = RwLock::new(0); + /// *lock.get_mut() = 10; + /// assert_eq!(*lock.read(key), 10); /// ``` #[must_use] pub fn get_mut(&mut self) -> &mut T { @@ -349,7 +351,9 @@ impl RwLock { /// /// The calling thread will be blocked until there are no more writers /// which hold the lock. There may be other readers currently inside the - /// lock when this method returns. + /// lock when this method returns. This method does not provide any guarantees + /// with respect to the ordering of whether contentious readers or writers + /// will acquire the lock first. /// /// Returns an RAII guard which will release this thread's shared access /// once it is dropped. @@ -360,21 +364,21 @@ impl RwLock { /// # Examples /// /// ``` - /// use std::sync::Arc; /// use std::thread; /// use happylock::{RwLock, ThreadKey}; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = Arc::new(RwLock::new(1)); - /// let c_lock = Arc::clone(&lock); + /// let lock = RwLock::new(1); /// /// let n = lock.read(key); /// assert_eq!(*n, 1); /// - /// thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let r = c_lock.read(key); - /// }).join().unwrap(); + /// thread::scope(|s| { + /// s.spawn(|| { + /// let key = ThreadKey::get().unwrap(); + /// let r = lock.read(key); + /// }); + /// }); /// ``` /// /// [`ThreadKey`]: `crate::ThreadKey` @@ -400,8 +404,8 @@ impl RwLock { /// /// # Errors /// - /// If the `RwLock` could not be acquired because it was already locked - /// exclusively, then an error will be returned containing the given key. + /// This function will return an error containing the [`ThreadKey`] if the + /// `RwLock` could not be acquired because it was already locked exclusively. /// /// # Examples /// @@ -470,10 +474,11 @@ impl RwLock { /// let key = ThreadKey::get().unwrap(); /// let lock = RwLock::new(1); /// - /// match lock.try_write(key) { - /// Ok(n) => assert_eq!(*n, 1), - /// Err(_) => unreachable!(), - /// }; + /// let mut n = lock.write(key); + /// *n = 2; + /// + /// let key = RwLock::unlock_write(n); + /// assert_eq!(*lock.read(key), 2); /// ``` /// /// [`ThreadKey`]: `crate::ThreadKey` @@ -486,10 +491,11 @@ impl RwLock { } } - /// Attempts to lock this `RwLock` with exclusive write access. + /// Attempts to lock this `RwLock` with exclusive write access, without + /// blocking. /// /// This function does not block. If the lock could not be acquired at this - /// time, then `None` is returned. Otherwise, an RAII guard is returned + /// time, then `Err` is returned. Otherwise, an RAII guard is returned /// which will release the lock when it is dropped. /// /// This function does not provide any guarantees with respect to the @@ -498,8 +504,8 @@ impl RwLock { /// /// # Errors /// - /// If the `RwLock` could not be acquired because it was already locked, - /// then an error will be returned containing the given key. + /// This function will return an error containing the [`ThreadKey`] if the + /// `RwLock` could not be acquired because it was already locked exclusively. /// /// # Examples /// @@ -509,8 +515,17 @@ impl RwLock { /// let key = ThreadKey::get().unwrap(); /// let lock = RwLock::new(1); /// + /// let key = match lock.try_write(key) { + /// Ok(mut n) => { + /// assert_eq!(*n, 1); + /// *n = 2; + /// RwLock::unlock_write(n) + /// } + /// Err(_) => unreachable!(), + /// }; + /// /// let n = lock.read(key); - /// assert_eq!(*n, 1); + /// assert_eq!(*n, 2); /// ``` pub fn try_write(&self, key: ThreadKey) -> Result, ThreadKey> { unsafe { @@ -532,7 +547,7 @@ impl RwLock { /// Immediately drops the guard, and consequently releases the shared lock. /// /// This function is equivalent to calling [`drop`] on the guard, except - /// that it returns the key that was used to create it. Alternately, the + /// that it returns the key that was used to create it. Alternatively, the /// guard will be automatically dropped when it goes out of scope. /// /// # Examples @@ -556,9 +571,9 @@ impl RwLock { /// Immediately drops the guard, and consequently releases the exclusive /// lock. /// - /// This function is equivalent to calling [`drop`] on the guard, except - /// that it returns the key that was used to create it. Alternately, the - /// guard will be automatically dropped when it goes out of scope. + /// This function is equivalent to calling [`drop`] on the guard, except that + /// it returns the key that was used to create it. Alternatively, the guard + /// will be automatically dropped when it goes out of scope. /// /// # Examples /// @@ -571,6 +586,9 @@ impl RwLock { /// let mut guard = lock.write(key); /// *guard += 20; /// let key = RwLock::unlock_write(guard); + /// + /// let guard = lock.read(key); + /// assert_eq!(*guard, 20); /// ``` #[must_use] pub fn unlock_write(guard: RwLockWriteGuard<'_, T, R>) -> ThreadKey { diff --git a/src/rwlock/write_lock.rs b/src/rwlock/write_lock.rs deleted file mode 100644 index 6469a67..0000000 --- a/src/rwlock/write_lock.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::fmt::Debug; - -use lock_api::RawRwLock; - -use crate::lockable::{Lockable, RawLock}; -use crate::{Keyable, ThreadKey}; - -use super::{RwLock, RwLockWriteGuard, RwLockWriteRef, WriteLock}; - -unsafe impl RawLock for WriteLock<'_, T, R> { - fn poison(&self) { - self.0.poison() - } - - unsafe fn raw_write(&self) { - self.0.raw_write() - } - - unsafe fn raw_try_write(&self) -> bool { - self.0.raw_try_write() - } - - unsafe fn raw_unlock_write(&self) { - self.0.raw_unlock_write() - } - - unsafe fn raw_read(&self) { - self.0.raw_write() - } - - unsafe fn raw_try_read(&self) -> bool { - self.0.raw_try_write() - } - - unsafe fn raw_unlock_read(&self) { - self.0.raw_unlock_write() - } -} - -unsafe impl Lockable for WriteLock<'_, T, R> { - type Guard<'g> - = RwLockWriteRef<'g, T, R> - where - Self: 'g; - - type DataMut<'a> - = &'a mut T - where - Self: 'a; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - ptrs.push(self.0); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - RwLockWriteRef::new(self.as_ref()) - } - - unsafe fn data_mut(&self) -> Self::DataMut<'_> { - self.0.data_mut() - } -} - -// Technically, the exclusive locks can also be shared, but there's currently -// no way to express that. I don't think I want to ever express that. - -#[mutants::skip] -#[cfg(not(tarpaulin_include))] -impl Debug for WriteLock<'_, T, R> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // safety: this is just a try lock, and the value is dropped - // immediately after, so there's no risk of blocking ourselves - // or any other threads - // It makes zero sense to try using an exclusive lock for this, so this - // is the only time when WriteLock does a read. - if let Some(value) = unsafe { self.0.try_read_no_key() } { - f.debug_struct("WriteLock").field("data", &&*value).finish() - } else { - struct LockedPlaceholder; - impl Debug for LockedPlaceholder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("") - } - } - - f.debug_struct("WriteLock") - .field("data", &LockedPlaceholder) - .finish() - } - } -} - -impl<'l, T, R> From<&'l RwLock> for WriteLock<'l, T, R> { - fn from(value: &'l RwLock) -> Self { - Self::new(value) - } -} - -impl AsRef> for WriteLock<'_, T, R> { - fn as_ref(&self) -> &RwLock { - self.0 - } -} - -impl<'l, T, R> WriteLock<'l, T, R> { - /// Creates a new `WriteLock` which accesses the given [`RwLock`] - /// - /// # Examples - /// - /// ``` - /// use happylock::{rwlock::WriteLock, RwLock}; - /// - /// let lock = RwLock::new(5); - /// let write_lock = WriteLock::new(&lock); - /// ``` - #[must_use] - pub const fn new(rwlock: &'l RwLock) -> Self { - Self(rwlock) - } -} - -impl WriteLock<'_, T, R> { - pub fn scoped_lock<'a, Ret>(&'a self, key: impl Keyable, f: impl Fn(&'a mut T) -> Ret) -> Ret { - self.0.scoped_write(key, f) - } - - pub fn scoped_try_lock<'a, Key: Keyable, Ret>( - &'a self, - key: Key, - f: impl Fn(&'a mut T) -> Ret, - ) -> Result { - self.0.scoped_try_write(key, f) - } - - /// Locks the underlying [`RwLock`] with exclusive write access, blocking - /// the current until it can be acquired. - /// - /// This function will not return while other writers or readers currently - /// have access to the lock. - /// - /// Returns an RAII guard which will drop the write access of this `RwLock` - /// when dropped. - /// - /// Because this method takes a [`ThreadKey`], it's not possible for this - /// method to cause a deadlock. - /// - /// # Examples - /// - /// ``` - /// use happylock::{ThreadKey, RwLock}; - /// use happylock::rwlock::WriteLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock = RwLock::new(1); - /// let writer = WriteLock::new(&lock); - /// - /// let mut n = writer.lock(key); - /// *n += 2; - /// ``` - /// - /// [`ThreadKey`]: `crate::ThreadKey` - #[must_use] - pub fn lock(&self, key: ThreadKey) -> RwLockWriteGuard<'_, T, R> { - self.0.write(key) - } - - /// Attempts to lock the underlying [`RwLock`] with exclusive write access. - /// - /// This function does not block. If the lock could not be acquired at this - /// time, then `None` is returned. Otherwise, an RAII guard is returned - /// which will release the lock when it is dropped. - /// - /// This function does not provide any guarantees with respect to the - /// ordering of whether contentious readers or writers will acquire the - /// lock first. - /// - /// # Errors - /// - /// If the [`RwLock`] could not be acquired because it was already locked, - /// then an error will be returned containing the given key. - /// - /// # Examples - /// - /// ``` - /// use happylock::{RwLock, ThreadKey}; - /// use happylock::rwlock::WriteLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock = RwLock::new(1); - /// let writer = WriteLock::new(&lock); - /// - /// match writer.try_lock(key) { - /// Ok(n) => assert_eq!(*n, 1), - /// Err(_) => unreachable!(), - /// }; - /// ``` - pub fn try_lock(&self, key: ThreadKey) -> Result, ThreadKey> { - self.0.try_write(key) - } - - // There's no `try_lock_no_key`. Instead, `try_read_no_key` is called on - // the referenced `RwLock`. - - /// Immediately drops the guard, and consequently releases the exclusive - /// lock on the underlying [`RwLock`]. - /// - /// This function is equivalent to calling [`drop`] on the guard, except - /// that it returns the key that was used to create it. Alternately, the - /// guard will be automatically dropped when it goes out of scope. - /// - /// # Examples - /// - /// ``` - /// use happylock::{RwLock, ThreadKey}; - /// use happylock::rwlock::WriteLock; - /// - /// let key = ThreadKey::get().unwrap(); - /// let lock = RwLock::new(0); - /// let writer = WriteLock::new(&lock); - /// - /// let mut guard = writer.lock(key); - /// *guard += 20; - /// let key = WriteLock::unlock(guard); - /// ``` - #[must_use] - pub fn unlock(guard: RwLockWriteGuard<'_, T, R>) -> ThreadKey { - RwLock::unlock_write(guard) - } -} -- cgit v1.2.3