summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBotahamec <botahamec@outlook.com>2024-09-26 22:12:52 -0400
committerBotahamec <botahamec@outlook.com>2024-09-26 22:12:52 -0400
commit00991791bba57a8757ba207a573e360224f43e6e (patch)
treeb108bf3172ee31d676da8e6418d1a5e31aa7394e
parent4fd5136e0c0ec9cc92bb2b5735c0b3b68acdd755 (diff)
Docs and improvements
-rw-r--r--src/lib.rs3
-rw-r--r--src/poisonable.rs60
-rw-r--r--src/poisonable/error.rs96
-rw-r--r--src/poisonable/flag.rs2
-rw-r--r--src/poisonable/guard.rs15
-rw-r--r--src/poisonable/poisonable.rs212
6 files changed, 367 insertions, 21 deletions
diff --git a/src/lib.rs b/src/lib.rs
index d034ffe..ec43121 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -187,6 +187,9 @@ pub use mutex::SpinLock;
/// [`BoxedLockCollection`]: collection::BoxedLockCollection
pub type LockCollection<L> = collection::BoxedLockCollection<L>;
+/// A re-export for [`poisonable::Poisonable`]
+pub type Poisonable<L> = poisonable::Poisonable<L>;
+
/// A mutual exclusion primitive useful for protecting shared data, which cannot deadlock.
///
/// By default, this uses `parking_lot` as a backend.
diff --git a/src/poisonable.rs b/src/poisonable.rs
index 49979e5..a492084 100644
--- a/src/poisonable.rs
+++ b/src/poisonable.rs
@@ -1,5 +1,3 @@
-#[cfg(not(panic = "unwind"))]
-use std::convert::Infallible;
use std::marker::PhantomData;
use std::sync::atomic::AtomicBool;
@@ -10,37 +8,87 @@ mod flag;
mod guard;
mod poisonable;
+/// 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)]
struct PoisonFlag(#[cfg(panic = "unwind")] AtomicBool);
+/// A wrapper around [`Lockable`] types which will enable poisoning.
+///
+/// A lock is "poisoned" when the thread panics while holding the lock. Once a
+/// lock is poisoned, all other threads are unable to access the data by
+/// 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.
+///
+/// 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`].
+///
+/// [`lock`]: `Poisonable::lock`
+/// [`try_lock`]: `Poisonable::try_lock`
+/// [`into_inner`]: `PoisonError::into_inner`
+/// [`clear_poison`]: `Poisonable::clear_poison`
#[derive(Debug, Default)]
pub struct Poisonable<L: Lockable + RawLock> {
inner: L,
poisoned: PoisonFlag,
}
-pub struct PoisonRef<'flag, G> {
+/// An RAII guard for a [`Poisonable`].
+///
+/// This is similar to a [`PoisonGuard`], except that it does not hold a
+/// [`Keyable`]
+///
+/// [`Keyable`]: `crate::Keyable`
+pub struct PoisonRef<'a, G> {
guard: G,
#[cfg(panic = "unwind")]
- flag: &'flag PoisonFlag,
+ flag: &'a PoisonFlag,
+ _phantom: PhantomData<&'a ()>,
}
-pub struct PoisonGuard<'flag, 'key, G, Key> {
- guard: PoisonRef<'flag, G>,
+/// An RAII guard for a [`Poisonable`].
+///
+/// This is created by calling methods like [`Poisonable::lock`].
+pub struct PoisonGuard<'a, 'key, G, Key> {
+ guard: PoisonRef<'a, G>,
key: Key,
_phantom: PhantomData<&'key ()>,
}
+/// A type of error which can be returned when acquiring a [`Poisonable`] lock.
pub struct PoisonError<Guard> {
guard: Guard,
}
+/// An enumeration of possible errors associated with
+/// [`TryLockPoisonableResult`] which can occur while trying to acquire a lock
+/// (i.e.: [`Poisonable::try_lock`]).
pub enum TryLockPoisonableError<'flag, 'key, G, Key: 'key> {
Poisoned(PoisonError<PoisonGuard<'flag, 'key, G, Key>>),
WouldBlock(Key),
}
+/// 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
+/// *also* carries the associated guard, and it can be acquired through the
+/// [`into_inner`] method.
+///
+/// [`into_inner`]: `PoisonError::into_inner`
pub type PoisonResult<Guard> = Result<Guard, PoisonError<Guard>>;
+/// A type alias for the result of a nonblocking locking method.
+///
+/// For more information, see [`PoisonResult`]. A `TryLockPoisonableResult`
+/// doesn't necessarily hold the associated guard in the [`Err`] type as the
+/// lock might not have been acquired for other reasons.
pub type TryLockPoisonableResult<'flag, 'key, G, Key> =
Result<PoisonGuard<'flag, 'key, G, Key>, TryLockPoisonableError<'flag, 'key, G, Key>>;
diff --git a/src/poisonable/error.rs b/src/poisonable/error.rs
index 2384953..1c4d60a 100644
--- a/src/poisonable/error.rs
+++ b/src/poisonable/error.rs
@@ -18,21 +18,117 @@ impl<Guard> fmt::Display for PoisonError<Guard> {
impl<Guard> Error for PoisonError<Guard> {}
impl<Guard> PoisonError<Guard> {
+ /// Creates a `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 {
Self { guard }
}
+ /// Consumes the error indicating that a lock is poisonmed, returning the
+ /// underlying guard to allow access regardless.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// 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())));
+ ///
+ /// // 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();
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let p_err = mutex.lock(key).unwrap_err();
+ /// let data = p_err.into_inner();
+ /// println!("recovered {} items", data.len());
+ /// ```
#[must_use]
pub fn into_inner(self) -> Guard {
self.guard
}
+ /// Reaches into this error indicating that a lock is poisoned, returning a
+ /// reference to the underlying guard to allow access regardless.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// 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())));
+ ///
+ /// // 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();
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let p_err = mutex.lock(key).unwrap_err();
+ /// let data = p_err.get_ref();
+ /// println!("recovered {} items", data.len());
+ /// ```
#[must_use]
pub const fn get_ref(&self) -> &Guard {
&self.guard
}
+ /// Reaches into this error indicating that a lock is poisoned, returning a
+ /// mutable reference to the underlying guard to allow access regardless.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// 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())));
+ ///
+ /// // 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();
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let mut p_err = mutex.lock(key).unwrap_err();
+ /// let data = p_err.get_mut();
+ /// data.insert(20);
+ /// println!("recovered {} items", data.len());
+ /// ```
#[must_use]
pub fn get_mut(&mut self) -> &mut Guard {
&mut self.guard
diff --git a/src/poisonable/flag.rs b/src/poisonable/flag.rs
index 0775c71..be38a38 100644
--- a/src/poisonable/flag.rs
+++ b/src/poisonable/flag.rs
@@ -14,7 +14,7 @@ impl PoisonFlag {
}
pub fn clear_poison(&self) {
- self.0.store(true, Relaxed)
+ self.0.store(false, Relaxed)
}
}
diff --git a/src/poisonable/guard.rs b/src/poisonable/guard.rs
index 97b0028..a8a54fe 100644
--- a/src/poisonable/guard.rs
+++ b/src/poisonable/guard.rs
@@ -1,10 +1,23 @@
use std::fmt::{Debug, Display};
+use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::Ordering::Relaxed;
use crate::Keyable;
-use super::{PoisonGuard, PoisonRef};
+use super::{PoisonFlag, PoisonGuard, PoisonRef};
+
+impl<'a, Guard> PoisonRef<'a, Guard> {
+ // This is used so that we don't keep accidentally adding the flag reference
+ pub(super) const fn new(flag: &'a PoisonFlag, guard: Guard) -> Self {
+ Self {
+ guard,
+ #[cfg(panic = "unwind")]
+ flag,
+ _phantom: PhantomData,
+ }
+ }
+}
impl<'flag, Guard> Drop for PoisonRef<'flag, Guard> {
fn drop(&mut self) {
diff --git a/src/poisonable/poisonable.rs b/src/poisonable/poisonable.rs
index f774e2d..4d8d1eb 100644
--- a/src/poisonable/poisonable.rs
+++ b/src/poisonable/poisonable.rs
@@ -18,10 +18,7 @@ unsafe impl<L: Lockable + RawLock> Lockable for Poisonable<L> {
}
unsafe fn guard(&self) -> Self::Guard<'_> {
- let ref_guard = PoisonRef {
- guard: self.inner.guard(),
- flag: &self.poisoned,
- };
+ let ref_guard = PoisonRef::new(&self.poisoned, self.inner.guard());
if self.is_poisoned() {
Ok(ref_guard)
@@ -31,10 +28,7 @@ unsafe impl<L: Lockable + RawLock> Lockable for Poisonable<L> {
}
unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
- let ref_guard = PoisonRef {
- guard: self.inner.read_guard(),
- flag: &self.poisoned,
- };
+ let ref_guard = PoisonRef::new(&self.poisoned, self.inner.read_guard());
if self.is_poisoned() {
Ok(ref_guard)
@@ -51,6 +45,15 @@ impl<L: Lockable + RawLock> From<L> for Poisonable<L> {
}
impl<L: Lockable + RawLock> Poisonable<L> {
+ /// Creates a new `Poisonable`
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, Poisonable};
+ ///
+ /// let mutex = Poisonable::new(Mutex::new(0));
+ /// ```
pub const fn new(value: L) -> Self {
Self {
inner: value,
@@ -63,21 +66,49 @@ impl<L: Lockable + RawLock> Poisonable<L> {
key: Key,
) -> PoisonResult<PoisonGuard<'flag, 'key, L::Guard<'flag>, Key>> {
let guard = PoisonGuard {
- guard: PoisonRef {
- guard: self.inner.guard(),
- flag: &self.poisoned,
- },
+ guard: PoisonRef::new(&self.poisoned, self.inner.guard()),
key,
_phantom: PhantomData,
};
- if !self.is_poisoned() {
+ if self.is_poisoned() {
return Err(PoisonError::new(guard));
}
Ok(guard)
}
+ /// 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
+ /// 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
+ /// this call will return an error once thr 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);
+ ///
+ /// thread::spawn(move || {
+ /// let key = ThreadKey::get().unwrap();
+ /// **c_mutex.lock(key).unwrap() = 10;
+ /// }).join().expect("thread::spawn failed");
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// assert_eq!(**mutex.lock(key).unwrap(), 10);
+ /// ```
pub fn lock<'flag, 'key, Key: Keyable + 'key>(
&'flag self,
key: Key,
@@ -88,6 +119,47 @@ impl<L: Lockable + RawLock> Poisonable<L> {
}
}
+ /// Attempts to acquire this lock.
+ ///
+ /// 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.
+ ///
+ /// This function does not block.
+ ///
+ /// # 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 the mutex 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);
+ ///
+ /// thread::spawn(move || {
+ /// let key = ThreadKey::get().unwrap();
+ /// let mut lock = c_mutex.try_lock(key);
+ /// if let Ok(ref mut mutex) = lock {
+ /// ***mutex = 10;
+ /// } else {
+ /// println!("try_lock failed");
+ /// }
+ /// }).join().expect("thread::spawn failed");
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// assert_eq!(**mutex.lock(key).unwrap(), 10);
+ /// ```
pub fn try_lock<'flag, 'key, Key: Keyable + 'key>(
&'flag self,
key: Key,
@@ -101,6 +173,21 @@ impl<L: Lockable + RawLock> Poisonable<L> {
}
}
+ /// Consumes the [`PoisonGuard`], and consequently unlocks its `Poisonable`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{ThreadKey, Mutex, Poisonable};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let mutex = Poisonable::new(Mutex::new(0));
+ ///
+ /// let mut guard = mutex.lock(key).unwrap();
+ /// **guard += 20;
+ ///
+ /// let key = Poisonable::<Mutex<_>>::unlock(guard);
+ /// ```
pub fn unlock<'flag, 'key, Key: Keyable + 'key>(
guard: PoisonGuard<'flag, 'key, L::Guard<'flag>, Key>,
) -> Key {
@@ -108,14 +195,92 @@ impl<L: Lockable + RawLock> Poisonable<L> {
guard.key
}
+ /// Determines whether the mutex 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
+ /// 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 _ = thread::spawn(move || {
+ /// let key = ThreadKey::get().unwrap();
+ /// let _lock = c_mutex.lock(key).unwrap();
+ /// panic!(); // the mutex gets poisoned
+ /// }).join();
+ ///
+ /// assert_eq!(mutex.is_poisoned(), true);
+ /// ```
pub fn is_poisoned(&self) -> bool {
self.poisoned.is_poisoned()
}
+ /// Clear the poisoned state from a lock.
+ ///
+ /// If the lock is poisoned, it will remain poisoned until this function
+ /// is called. This allows recovering from a poisoned state and marking
+ /// that it has recovered. For example, if the value is overwritten by a
+ /// known-good value, then the lock can be marked as un-poisoned. Or
+ /// possibly, the value could by inspected to determine if it is in a
+ /// consistent state, and if so the poison is removed.
+ ///
+ /// # 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 _ = thread::spawn(move || {
+ /// let key = ThreadKey::get().unwrap();
+ /// let _lock = c_mutex.lock(key).unwrap();
+ /// panic!(); // the mutex gets poisoned
+ /// }).join();
+ ///
+ /// assert_eq!(mutex.is_poisoned(), true);
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let x = mutex.lock(key).unwrap_or_else(|mut e| {
+ /// ***e.get_mut() = 1;
+ /// mutex.clear_poison();
+ /// e.into_inner()
+ /// });
+ ///
+ /// assert_eq!(mutex.is_poisoned(), false);
+ /// assert_eq!(**x, 1);
+ /// ```
pub fn clear_poison(&self) {
self.poisoned.clear_poison()
}
+ /// Consumes this `Poisonable`, returning the underlying lock.
+ ///
+ /// # Errors
+ ///
+ /// If another user of this lock panicked while holding the lock, then this
+ /// call will return an error instead.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, Poisonable};
+ ///
+ /// let mutex = Poisonable::new(Mutex::new(0));
+ /// assert_eq!(mutex.into_inner().unwrap().into_inner(), 0);
+ /// ```
pub fn into_inner(self) -> PoisonResult<L> {
if self.is_poisoned() {
Err(PoisonError::new(self.inner))
@@ -124,6 +289,27 @@ impl<L: Lockable + RawLock> Poisonable<L> {
}
}
+ /// Returns a mutable reference to the underlying lock.
+ ///
+ /// Since this call borrows the `Poisonable` mutable, no actual locking
+ /// needs to take place - the mutable borrow statically guarantees no locks
+ /// exist.
+ ///
+ /// # Errors
+ ///
+ /// If another user of this lock panicked while holding the lock, then
+ /// this call will return an error instead.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, Poisonable, ThreadKey};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let mut mutex = Poisonable::new(Mutex::new(0));
+ /// *mutex.get_mut().unwrap().as_mut() = 10;
+ /// assert_eq!(**mutex.lock(key).unwrap(), 10);
+ /// ```
pub fn get_mut(&mut self) -> PoisonResult<&mut L> {
if self.is_poisoned() {
Err(PoisonError::new(&mut self.inner))