summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/collection.rs38
-rw-r--r--src/collection/boxed.rs29
-rw-r--r--src/key.rs2
-rw-r--r--src/lockable.rs42
-rw-r--r--src/mutex.rs40
-rw-r--r--src/mutex/mutex.rs71
-rw-r--r--src/poisonable.rs64
-rw-r--r--src/poisonable/error.rs60
-rw-r--r--src/poisonable/poisonable.rs148
-rw-r--r--src/rwlock.rs317
-rw-r--r--src/rwlock/read_lock.rs251
-rw-r--r--src/rwlock/rwlock.rs74
-rw-r--r--src/rwlock/write_lock.rs229
13 files changed, 374 insertions, 991 deletions
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<L> {
+ // TODO: rename to child
data: L,
}
@@ -42,7 +42,7 @@ pub struct OwnedLockCollection<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 [`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<L> {
/// 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<L> {
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: 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<L> BoxedLockCollection<L> {
/// ```
/// 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<L> BoxedLockCollection<L> {
/// ```
/// 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<L: Lockable> BoxedLockCollection<L> {
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<T> = Mutex<T, parking_lot::RawMutex>;
/// 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<T: ?Sized, R> {
data: UnsafeCell<T>,
}
-/// 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<T, R>, PhantomData<R::GuardMarker>);
-/// 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<T, R: RawMutex> LockableGetMut for Mutex<T, R> {
unsafe impl<T, R: RawMutex> OwnedLockable for Mutex<T, R> {}
impl<T, R: RawMutex> Mutex<T, R> {
- /// Create a new unlocked `Mutex`.
+ /// Creates a `Mutex` in an unlocked state ready for use.
///
/// # Examples
///
@@ -283,25 +283,30 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> {
}
impl<T: ?Sized, R: RawMutex> Mutex<T, R> {
- /// 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<T: ?Sized, R: RawMutex> Mutex<T, R> {
}
}
- /// 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<T: ?Sized, R: RawMutex> Mutex<T, R> {
/// 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<T: ?Sized, R: RawMutex> Mutex<T, R> {
/// *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<L> {
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: 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<Guard> PoisonError<Guard> {
///
/// 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<Guard> PoisonError<Guard> {
///
/// ```
/// 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<Guard> PoisonError<Guard> {
///
/// ```
/// 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<Guard> PoisonError<Guard> {
///
/// ```
/// 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<L> Poisonable<L> {
}
}
- /// 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<L> Poisonable<L> {
/// # 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<L> Poisonable<L> {
/// 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<L> Poisonable<L> {
/// 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<L> Poisonable<L> {
}
// NOTE: `child_ref` isn't implemented because it would make this not `RefUnwindSafe`
- //
}
impl<L: Lockable> Poisonable<L> {
@@ -366,30 +359,30 @@ impl<L: Lockable + RawLock> Poisonable<L> {
/// 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<L: Lockable + RawLock> Poisonable<L> {
///
/// # 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<L: Lockable + RawLock> Poisonable<L> {
/// 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<L: Sharable + RawLock> Poisonable<L> {
///
/// # 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<PoisonGuard<'_, L::ReadGuard<'_>>> {
unsafe {
@@ -586,23 +586,22 @@ impl<L: Sharable + RawLock> Poisonable<L> {
}
}
- /// 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<L: Sharable + RawLock> Poisonable<L> {
///
/// [`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<L: Sharable + RawLock> Poisonable<L> {
}
}
- /// 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<L: Sharable + RawLock> Poisonable<L> {
/// 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::<RwLock<_>>::unlock_read(guard);
/// ```
pub fn unlock_read<'flag>(guard: PoisonGuard<'flag, L::ReadGuard<'flag>>) -> ThreadKey {
@@ -658,7 +664,8 @@ impl<L: LockableIntoInner> Poisonable<L> {
/// # 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<L: LockableGetMut + RawLock> Poisonable<L> {
/// # 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<T> = RwLock<T, parking_lot::RawRwLock>;
/// 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<T: ?Sized, R> {
data: UnsafeCell<T>,
}
-/// 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<T, R>);
-
-/// 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<T, R>);
-
-/// 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<T, R>,
PhantomData<R::GuardMarker>,
);
-/// 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<T, R>,
PhantomData<R::GuardMarker>,
@@ -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();
@@ -132,112 +137,6 @@ mod tests {
}
#[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();
let lock: crate::RwLock<_> = RwLock::new("Hello, world!");
@@ -249,18 +148,6 @@ mod tests {
}
#[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();
let lock: crate::RwLock<_> = RwLock::new("Hello, world!");
@@ -272,18 +159,6 @@ mod tests {
}
#[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();
let lock = crate::RwLock::new("Hello, world!");
@@ -424,112 +299,6 @@ mod tests {
}
#[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::<ReadLock<_, _>>::unlock(guard);
- let guard = collection.read(key);
- assert_eq!(**guard, "hi");
-
- let key = LockCollection::<ReadLock<_, _>>::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::<WriteLock<_, _>>::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();
let lock = LockCollection::new(crate::RwLock::new("hi"));
@@ -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<T, R: RawRwLock> 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<T, R: RawRwLock> 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<T, R: RawRwLock> 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<T: Debug, R: RawRwLock> 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("<locked>")
- }
- }
-
- f.debug_struct("ReadLock")
- .field("data", &LockedPlaceholder)
- .finish()
- }
- }
-}
-
-impl<'l, T, R> From<&'l RwLock<T, R>> for ReadLock<'l, T, R> {
- fn from(value: &'l RwLock<T, R>) -> Self {
- Self::new(value)
- }
-}
-
-impl<T: ?Sized, R> AsRef<RwLock<T, R>> for ReadLock<'_, T, R> {
- fn as_ref(&self) -> &RwLock<T, R> {
- 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<T, R>) -> Self {
- Self(rwlock)
- }
-}
-
-impl<T, R: RawRwLock> 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<Ret, Key> {
- 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<RwLockReadGuard<'_, T, R>, 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<RwLockReadRef<'_, T, R>> {
- 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<T, R: RawRwLock> RwLock<T, R> {
/// use happylock::RwLock;
///
/// let lock = RwLock::new(5);
+ ///
+ ///
/// ```
#[must_use]
pub const fn new(data: T) -> Self {
@@ -211,9 +213,9 @@ impl<T, R> RwLock<T, R> {
/// ```
/// 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<T, R> RwLock<T, R> {
impl<T: ?Sized, R> RwLock<T, R> {
/// 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<T: ?Sized, R> RwLock<T, R> {
/// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
///
/// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// # 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
///
/// # 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
}
}
- /// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
///
/// # 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// 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<RwLockWriteGuard<'_, T, R>, ThreadKey> {
unsafe {
@@ -532,7 +547,7 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// 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<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// 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<T, R: RawRwLock> 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<T, R: RawRwLock> 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<T: Debug, R: RawRwLock> 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("<locked>")
- }
- }
-
- f.debug_struct("WriteLock")
- .field("data", &LockedPlaceholder)
- .finish()
- }
- }
-}
-
-impl<'l, T, R> From<&'l RwLock<T, R>> for WriteLock<'l, T, R> {
- fn from(value: &'l RwLock<T, R>) -> Self {
- Self::new(value)
- }
-}
-
-impl<T: ?Sized, R> AsRef<RwLock<T, R>> for WriteLock<'_, T, R> {
- fn as_ref(&self) -> &RwLock<T, R> {
- 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<T, R>) -> Self {
- Self(rwlock)
- }
-}
-
-impl<T, R: RawRwLock> 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<Ret, Key> {
- 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<RwLockWriteGuard<'_, T, R>, 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)
- }
-}