diff options
Diffstat (limited to 'src/poisonable')
| -rw-r--r-- | src/poisonable/poisonable.rs | 364 |
1 files changed, 274 insertions, 90 deletions
diff --git a/src/poisonable/poisonable.rs b/src/poisonable/poisonable.rs index 2c7eeff..ddd8038 100644 --- a/src/poisonable/poisonable.rs +++ b/src/poisonable/poisonable.rs @@ -1,7 +1,9 @@ use std::marker::PhantomData; use std::panic::{RefUnwindSafe, UnwindSafe}; -use crate::lockable::{Lockable, LockableAsMut, LockableIntoInner, RawLock}; +use crate::lockable::{ + Lockable, LockableAsMut, LockableIntoInner, OwnedLockable, RawLock, Sharable, +}; use crate::Keyable; use super::{ @@ -9,9 +11,42 @@ use super::{ TryLockPoisonableError, TryLockPoisonableResult, }; -unsafe impl<L: Lockable + RawLock> Lockable for Poisonable<L> { - type Guard<'g> = PoisonResult<PoisonRef<'g, L::Guard<'g>>> where Self: 'g; - type ReadGuard<'g> = PoisonResult<PoisonRef<'g, L::ReadGuard<'g>>> where Self: 'g; +unsafe impl<L: Lockable + RawLock> RawLock for Poisonable<L> { + unsafe fn lock(&self) { + self.inner.lock() + } + + unsafe fn try_lock(&self) -> bool { + self.inner.try_lock() + } + + unsafe fn unlock(&self) { + self.inner.unlock() + } + + unsafe fn read(&self) { + self.inner.read() + } + + unsafe fn try_read(&self) -> bool { + self.inner.try_read() + } + + unsafe fn unlock_read(&self) { + self.inner.unlock_read() + } +} + +unsafe impl<L: Lockable> Lockable for Poisonable<L> { + type Guard<'g> + = PoisonResult<PoisonRef<'g, L::Guard<'g>>> + where + Self: 'g; + + type ReadGuard<'g> + = PoisonResult<PoisonRef<'g, L::ReadGuard<'g>>> + where + Self: 'g; fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { self.inner.get_ptrs(ptrs) @@ -38,13 +73,16 @@ unsafe impl<L: Lockable + RawLock> Lockable for Poisonable<L> { } } -impl<L: Lockable + RawLock> From<L> for Poisonable<L> { +unsafe impl<L: Sharable> Sharable for Poisonable<L> {} +unsafe impl<L: OwnedLockable> OwnedLockable for Poisonable<L> {} + +impl<L> From<L> for Poisonable<L> { fn from(value: L) -> Self { Self::new(value) } } -impl<L: Lockable + RawLock> Poisonable<L> { +impl<L> Poisonable<L> { /// Creates a new `Poisonable` /// /// # Examples @@ -61,6 +99,137 @@ impl<L: Lockable + RawLock> Poisonable<L> { } } + /// 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. + /// + /// 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 + /// call will return an error instead. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, Poisonable}; + /// + /// let mutex = Poisonable::new(Mutex::new(0)); + /// assert_eq!(mutex.into_child().unwrap().into_inner(), 0); + /// ``` + pub fn into_child(self) -> PoisonResult<L> { + if self.is_poisoned() { + Err(PoisonError::new(self.inner)) + } else { + Ok(self.inner) + } + } + + /// 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 + /// 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.child_mut().unwrap().as_mut() = 10; + /// assert_eq!(*mutex.lock(key).unwrap(), 10); + /// ``` + pub fn child_mut(&mut self) -> PoisonResult<&mut L> { + if self.is_poisoned() { + Err(PoisonError::new(&mut self.inner)) + } else { + Ok(&mut self.inner) + } + } + + // NOTE: `child_ref` isn't implemented because it would make this not `RefUnwindSafe` + // +} + +impl<L: Lockable> Poisonable<L> { + /// Creates a guard for the poisonable, without locking it unsafe fn guard<'flag, 'key, Key: Keyable + 'key>( &'flag self, key: Key, @@ -77,7 +246,9 @@ impl<L: Lockable + RawLock> Poisonable<L> { Ok(guard) } +} +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 @@ -88,7 +259,7 @@ impl<L: Lockable + RawLock> Poisonable<L> { /// # Errors /// /// If another use of this mutex panicked while holding the mutex, then - /// this call will return an error once thr mutex is acquired. + /// this call will return an error once the mutex is acquired. /// /// # Examples /// @@ -197,12 +368,38 @@ impl<L: Lockable + RawLock> Poisonable<L> { drop(guard.guard); guard.key } +} - /// Determines whether the mutex is poisoned. +impl<L: Sharable + RawLock> Poisonable<L> { + unsafe fn read_guard<'flag, 'key, Key: Keyable + 'key>( + &'flag self, + key: Key, + ) -> PoisonResult<PoisonGuard<'flag, 'key, L::ReadGuard<'flag>, Key>> { + let guard = PoisonGuard { + guard: PoisonRef::new(&self.poisoned, self.inner.read_guard()), + key, + _phantom: PhantomData, + }; + + if self.is_poisoned() { + return Err(PoisonError::new(guard)); + } + + Ok(guard) + } + + /// Locks with shared read access, blocking the current thread until it can + /// be acquired. /// - /// 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. + /// This function will block the current thread until there are no writers + /// which hold the lock. This method does not provide any guarantee with + /// respect to the ordering of contentious readers or writers will acquire + /// the lock. + /// + /// # Errors + /// + /// If another use of this lock panicked while holding the lock, then + /// this call will return an error once the lock is acquired. /// /// # Examples /// @@ -210,115 +407,102 @@ impl<L: Lockable + RawLock> Poisonable<L> { /// use std::sync::Arc; /// use std::thread; /// - /// use happylock::{Mutex, Poisonable, ThreadKey}; + /// use happylock::{RwLock, Poisonable, ThreadKey}; /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// let key = ThreadKey::get().unwrap(); + /// let lock = Arc::new(Poisonable::new(RwLock::new(0))); + /// let c_lock = Arc::clone(&lock); /// - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let _lock = c_mutex.lock(key).unwrap(); - /// panic!(); // the mutex gets poisoned - /// }).join(); + /// let n = lock.read(key).unwrap(); + /// assert_eq!(*n, 0); /// - /// assert_eq!(mutex.is_poisoned(), true); + /// thread::spawn(move || { + /// let key = ThreadKey::get().unwrap(); + /// assert!(c_lock.read(key).is_ok()); + /// }).join().expect("thread::spawn failed"); /// ``` - pub fn is_poisoned(&self) -> bool { - self.poisoned.is_poisoned() + pub fn read<'flag, 'key, Key: Keyable + 'key>( + &'flag self, + key: Key, + ) -> PoisonResult<PoisonGuard<'flag, 'key, L::ReadGuard<'flag>, Key>> { + unsafe { + self.inner.read(); + self.read_guard(key) + } } - /// 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. + /// Attempts to acquire the lock with shared read access. /// - /// # Examples + /// 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. /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; + /// This function does not block. /// - /// use happylock::{Mutex, Poisonable, ThreadKey}; + /// This function does not provide any guarantees with respect to the ordering + /// of whether contentious readers or writers will acquire the lock first. /// - /// let mutex = Arc::new(Poisonable::new(Mutex::new(0))); - /// let c_mutex = Arc::clone(&mutex); + /// # Errors /// - /// let _ = thread::spawn(move || { - /// let key = ThreadKey::get().unwrap(); - /// let _lock = c_mutex.lock(key).unwrap(); - /// panic!(); // the mutex gets poisoned - /// }).join(); + /// 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. /// - /// assert_eq!(mutex.is_poisoned(), true); + /// This function will return the [`WouldBlock`] error if the lock could + /// not be acquired because it was already locked exclusively. /// - /// 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() - /// }); + /// # Examples /// - /// 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. + /// use happylock::{Poisonable, RwLock, ThreadKey}; /// - /// # Errors - /// - /// If another user of this lock panicked while holding the lock, then this - /// call will return an error instead. - /// - /// # Examples + /// let key = ThreadKey::get().unwrap(); + /// let lock = Poisonable::new(RwLock::new(1)); /// + /// match lock.try_read(key) { + /// Ok(n) => assert_eq!(*n, 1), + /// Err(_) => unreachable!(), + /// }; /// ``` - /// use happylock::{Mutex, Poisonable}; /// - /// let mutex = Poisonable::new(Mutex::new(0)); - /// assert_eq!(mutex.into_inner_lock().unwrap().into_inner(), 0); - /// ``` - pub fn into_inner_lock(self) -> PoisonResult<L> { - if self.is_poisoned() { - Err(PoisonError::new(self.inner)) - } else { - Ok(self.inner) + /// [`Poisoned`]: `TryLockPoisonableError::Poisoned` + /// [`WouldBlock`]: `TryLockPoisonableError::WouldBlock` + pub fn try_read<'flag, 'key, Key: Keyable + 'key>( + &'flag self, + key: Key, + ) -> TryLockPoisonableResult<'flag, 'key, L::ReadGuard<'flag>, Key> { + unsafe { + if self.inner.try_read() { + Ok(self.read_guard(key)?) + } else { + Err(TryLockPoisonableError::WouldBlock(key)) + } } } - /// Returns a mutable reference to the underlying lock. - /// - /// # Errors - /// - /// If another user of this lock panicked while holding the lock, then - /// this call will return an error instead. + /// Consumes the [`PoisonGuard`], and consequently unlocks its underlying lock. /// /// # Examples /// /// ``` - /// use happylock::{Mutex, Poisonable, ThreadKey}; + /// use happylock::{ThreadKey, RwLock, Poisonable}; /// /// let key = ThreadKey::get().unwrap(); - /// let mut mutex = Poisonable::new(Mutex::new(0)); - /// *mutex.inner_lock_mut().unwrap().as_mut() = 10; - /// assert_eq!(*mutex.lock(key).unwrap(), 10); + /// let lock = Poisonable::new(RwLock::new(0)); + /// + /// let mut guard = lock.read(key).unwrap(); + /// let key = Poisonable::<RwLock<_>>::unlock_read(guard); /// ``` - pub fn inner_lock_mut(&mut self) -> PoisonResult<&mut L> { - if self.is_poisoned() { - Err(PoisonError::new(&mut self.inner)) - } else { - Ok(&mut self.inner) - } + pub fn unlock_read<'flag, 'key, Key: Keyable + 'key>( + guard: PoisonGuard<'flag, 'key, L::ReadGuard<'flag>, Key>, + ) -> Key { + drop(guard.guard); + guard.key } } -impl<L: LockableIntoInner + RawLock> Poisonable<L> { +impl<L: LockableIntoInner> Poisonable<L> { /// Consumes this `Poisonable`, returning the underlying data. /// /// # Errors @@ -374,5 +558,5 @@ impl<L: LockableAsMut + RawLock> Poisonable<L> { } } -impl<L: Lockable + RawLock> RefUnwindSafe for Poisonable<L> {} -impl<L: Lockable + RawLock> UnwindSafe for Poisonable<L> {} +impl<L: UnwindSafe> RefUnwindSafe for Poisonable<L> {} +impl<L: UnwindSafe> UnwindSafe for Poisonable<L> {} |
