diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/poisonable.rs | 60 | ||||
| -rw-r--r-- | src/poisonable/error.rs | 96 | ||||
| -rw-r--r-- | src/poisonable/flag.rs | 2 | ||||
| -rw-r--r-- | src/poisonable/guard.rs | 15 | ||||
| -rw-r--r-- | src/poisonable/poisonable.rs | 212 |
6 files changed, 367 insertions, 21 deletions
@@ -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)) |
