From f6b38f7425a3183214dae79445446b042154688f Mon Sep 17 00:00:00 2001 From: Botahamec Date: Wed, 5 Feb 2025 20:31:00 -0500 Subject: Tests and optimization --- src/collection/boxed.rs | 102 +++++++++++++++++++++++++++++++++++++-- src/collection/guard.rs | 63 +++++++++++++++++++++++- src/collection/owned.rs | 3 ++ src/collection/ref.rs | 2 + src/collection/retry.rs | 111 +++++++++++++++++++++++++------------------ src/collection/utils.rs | 46 ++++++++---------- src/key.rs | 6 +-- src/lockable.rs | 4 +- src/mutex.rs | 30 ++++++++++++ src/mutex/guard.rs | 8 ++++ src/mutex/mutex.rs | 1 + src/poisonable/error.rs | 4 ++ src/poisonable/flag.rs | 1 + src/poisonable/guard.rs | 8 ++++ src/poisonable/poisonable.rs | 1 + src/rwlock/read_guard.rs | 8 ++++ src/rwlock/read_lock.rs | 3 +- src/rwlock/rwlock.rs | 1 + src/rwlock/write_guard.rs | 8 ++++ src/rwlock/write_lock.rs | 1 + 20 files changed, 328 insertions(+), 83 deletions(-) (limited to 'src') diff --git a/src/collection/boxed.rs b/src/collection/boxed.rs index 98d7632..72489bf 100644 --- a/src/collection/boxed.rs +++ b/src/collection/boxed.rs @@ -22,6 +22,7 @@ fn contains_duplicates(l: &[&dyn RawLock]) -> bool { unsafe impl RawLock for BoxedLockCollection { #[mutants::skip] // this should never be called + #[cfg(not(tarpaulin_include))] fn poison(&self) { for lock in &self.locks { lock.poison(); @@ -33,6 +34,7 @@ unsafe impl RawLock for BoxedLockCollection { } unsafe fn raw_try_lock(&self) -> bool { + println!("{}", self.locks().len()); utils::ordered_try_lock(self.locks()) } @@ -136,6 +138,7 @@ unsafe impl Sync for BoxedLockCollection {} impl Drop for BoxedLockCollection { #[mutants::skip] // i can't test for a memory leak + #[cfg(not(tarpaulin_include))] fn drop(&mut self) { unsafe { // safety: this collection will never be locked again @@ -155,6 +158,7 @@ impl> AsRef for BoxedLockCollection { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for BoxedLockCollection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!(BoxedLockCollection)) @@ -197,8 +201,8 @@ impl BoxedLockCollection { #[must_use] pub fn into_child(mut self) -> L { unsafe { - // safety: this collection will never be locked again - self.locks.clear(); + // safety: this collection will never be used again + std::ptr::drop_in_place(&mut self.locks); // safety: this was allocated using a box, and is now unique let boxed: Box> = Box::from_raw(self.data.cast_mut()); // to prevent a double free @@ -621,7 +625,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{Mutex, ThreadKey}; + use crate::{Mutex, RwLock, ThreadKey}; #[test] fn non_duplicates_allowed() { @@ -636,6 +640,98 @@ mod tests { assert!(BoxedLockCollection::try_new([&mutex1, &mutex1]).is_none()) } + #[test] + fn contains_duplicates_empty() { + assert!(!contains_duplicates(&[])) + } + + #[test] + fn try_lock_works() { + let key = ThreadKey::get().unwrap(); + let collection = BoxedLockCollection::new([Mutex::new(1), Mutex::new(2)]); + let guard = collection.try_lock(key); + + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let guard = collection.try_lock(key); + assert!(guard.is_err()); + }); + }); + + assert!(guard.is_ok()); + } + + #[test] + fn try_read_works() { + let key = ThreadKey::get().unwrap(); + let collection = BoxedLockCollection::new([RwLock::new(1), RwLock::new(2)]); + let guard = collection.try_read(key); + + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let guard = collection.try_read(key); + assert!(guard.is_ok()); + }); + }); + + assert!(guard.is_ok()); + } + + #[test] + fn try_lock_fails_with_one_exclusive_lock() { + let key = ThreadKey::get().unwrap(); + let locks = [Mutex::new(1), Mutex::new(2)]; + let collection = BoxedLockCollection::new_ref(&locks); + let guard = locks[1].try_lock(key); + + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let guard = collection.try_lock(key); + assert!(guard.is_err()); + }); + }); + + assert!(guard.is_ok()); + } + + #[test] + fn try_read_fails_during_exclusive_lock() { + let key = ThreadKey::get().unwrap(); + let collection = BoxedLockCollection::new([RwLock::new(1), RwLock::new(2)]); + let guard = collection.try_lock(key); + + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let guard = collection.try_read(key); + assert!(guard.is_err()); + }); + }); + + assert!(guard.is_ok()); + } + + #[test] + fn try_read_fails_with_one_exclusive_lock() { + let key = ThreadKey::get().unwrap(); + let locks = [RwLock::new(1), RwLock::new(2)]; + let collection = BoxedLockCollection::new_ref(&locks); + let guard = locks[1].try_write(key); + + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let guard = collection.try_read(key); + assert!(guard.is_err()); + }); + }); + + assert!(guard.is_ok()); + } + #[test] fn works_in_collection() { let key = ThreadKey::get().unwrap(); diff --git a/src/collection/guard.rs b/src/collection/guard.rs index 9412343..eea13ed 100644 --- a/src/collection/guard.rs +++ b/src/collection/guard.rs @@ -7,6 +7,7 @@ use crate::key::Keyable; use super::LockGuard; #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialEq for LockGuard<'_, Guard, Key> { fn eq(&self, other: &Self) -> bool { self.guard.eq(&other.guard) @@ -14,6 +15,7 @@ impl PartialEq for LockGuard<'_, Guard, Key> { } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialOrd for LockGuard<'_, Guard, Key> { fn partial_cmp(&self, other: &Self) -> Option { self.guard.partial_cmp(&other.guard) @@ -21,9 +23,11 @@ impl PartialOrd for LockGuard<'_, Guard, Key> { } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Eq for LockGuard<'_, Guard, Key> {} #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Ord for LockGuard<'_, Guard, Key> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.guard.cmp(&other.guard) @@ -31,6 +35,7 @@ impl Ord for LockGuard<'_, Guard, Key> { } #[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for LockGuard<'_, Guard, Key> { fn hash(&self, state: &mut H) { self.guard.hash(state) @@ -38,6 +43,7 @@ impl Hash for LockGuard<'_, Guard, Key> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for LockGuard<'_, Guard, Key> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) @@ -79,7 +85,7 @@ impl AsMut for LockGuard<'_, Guard, Key> { #[cfg(test)] mod tests { use crate::collection::OwnedLockCollection; - use crate::{RwLock, ThreadKey}; + use crate::{LockCollection, Mutex, RwLock, ThreadKey}; #[test] fn guard_display_works() { @@ -88,4 +94,59 @@ mod tests { let guard = lock.read(key); assert_eq!(guard.to_string(), "Hello, world!".to_string()); } + + #[test] + fn deref_mut_works() { + let mut key = ThreadKey::get().unwrap(); + let locks = (Mutex::new(1), Mutex::new(2)); + let lock = LockCollection::new_ref(&locks); + let mut guard = lock.lock(&mut key); + *guard.0 = 3; + drop(guard); + + let guard = locks.0.lock(&mut key); + assert_eq!(*guard, 3); + drop(guard); + + let guard = locks.1.lock(&mut key); + assert_eq!(*guard, 2); + drop(guard); + } + + #[test] + fn as_ref_works() { + let mut key = ThreadKey::get().unwrap(); + let locks = (Mutex::new(1), Mutex::new(2)); + let lock = LockCollection::new_ref(&locks); + let mut guard = lock.lock(&mut key); + *guard.0 = 3; + drop(guard); + + let guard = locks.0.lock(&mut key); + assert_eq!(guard.as_ref(), &3); + drop(guard); + + let guard = locks.1.lock(&mut key); + assert_eq!(guard.as_ref(), &2); + drop(guard); + } + + #[test] + fn as_mut_works() { + let mut key = ThreadKey::get().unwrap(); + let locks = (Mutex::new(1), Mutex::new(2)); + let lock = LockCollection::new_ref(&locks); + let mut guard = lock.lock(&mut key); + let guard_mut = guard.as_mut(); + *guard_mut.0 = 3; + drop(guard); + + let guard = locks.0.lock(&mut key); + assert_eq!(guard.as_ref(), &3); + drop(guard); + + let guard = locks.1.lock(&mut key); + assert_eq!(guard.as_ref(), &2); + drop(guard); + } } diff --git a/src/collection/owned.rs b/src/collection/owned.rs index a96300d..4a0d1ef 100644 --- a/src/collection/owned.rs +++ b/src/collection/owned.rs @@ -8,6 +8,7 @@ use crate::Keyable; use super::{utils, LockGuard, OwnedLockCollection}; #[mutants::skip] // it's hard to test individual locks in an OwnedLockCollection +#[cfg(not(tarpaulin_include))] fn get_locks(data: &L) -> Vec<&dyn RawLock> { let mut locks = Vec::new(); data.get_ptrs(&mut locks); @@ -16,6 +17,7 @@ fn get_locks(data: &L) -> Vec<&dyn RawLock> { unsafe impl RawLock for OwnedLockCollection { #[mutants::skip] // this should never run + #[cfg(not(tarpaulin_include))] fn poison(&self) { let locks = get_locks(&self.data); for lock in locks { @@ -63,6 +65,7 @@ unsafe impl Lockable for OwnedLockCollection { Self: 'g; #[mutants::skip] // It's hard to test lkocks in an OwnedLockCollection, because they're owned + #[cfg(not(tarpaulin_include))] fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { self.data.get_ptrs(ptrs) } diff --git a/src/collection/ref.rs b/src/collection/ref.rs index 512bdec..37973f6 100644 --- a/src/collection/ref.rs +++ b/src/collection/ref.rs @@ -41,6 +41,7 @@ where unsafe impl RawLock for RefLockCollection<'_, L> { #[mutants::skip] // this should never run + #[cfg(not(tarpaulin_include))] fn poison(&self) { for lock in &self.locks { lock.poison(); @@ -109,6 +110,7 @@ impl> AsRef for RefLockCollection<'_, L> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RefLockCollection<'_, L> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!(RefLockCollection)) diff --git a/src/collection/retry.rs b/src/collection/retry.rs index fe0a5b8..331b669 100644 --- a/src/collection/retry.rs +++ b/src/collection/retry.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::Cell; use std::collections::HashSet; use std::marker::PhantomData; @@ -9,6 +9,7 @@ use crate::lockable::{ }; use crate::Keyable; +use super::utils::{attempt_to_recover_locks_from_panic, attempt_to_recover_reads_from_panic}; use super::{LockGuard, RetryingLockCollection}; /// Get all raw locks in the collection @@ -37,6 +38,7 @@ fn contains_duplicates(data: L) -> bool { unsafe impl RawLock for RetryingLockCollection { #[mutants::skip] // this should never run + #[cfg(not(tarpaulin_include))] fn poison(&self) { let locks = get_locks(&self.data); for lock in locks { @@ -45,7 +47,6 @@ unsafe impl RawLock for RetryingLockCollection { } unsafe fn raw_lock(&self) { - let mut first_index = 0; let locks = get_locks(&self.data); if locks.is_empty() { @@ -54,16 +55,17 @@ unsafe impl RawLock for RetryingLockCollection { } // these will be unlocked in case of a panic - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let first_index = Cell::new(0); + let locked = Cell::new(0); handle_unwind( || unsafe { 'outer: loop { // This prevents us from entering a spin loop waiting for // the same lock to be unlocked // safety: we have the thread key - locks[first_index].raw_lock(); + locks[first_index.get()].raw_lock(); for (i, lock) in locks.iter().enumerate() { - if i == first_index { + if i == first_index.get() { // we've already locked this one continue; } @@ -74,23 +76,21 @@ unsafe impl RawLock for RetryingLockCollection { // immediately after, causing a panic // safety: we have the thread key if lock.raw_try_lock() { - locked.borrow_mut().push(*lock) + locked.set(locked.get() + 1); } else { - for lock in locked.borrow().iter() { - // safety: we already locked all of these - lock.raw_unlock(); + // safety: we already locked all of these + attempt_to_recover_locks_from_panic(&locks[0..i]); + if first_index.get() >= i { + // safety: this is already locked and can't be + // unlocked by the previous loop + locks[first_index.get()].raw_unlock(); } - // these are no longer locked - locked.borrow_mut().clear(); - if first_index >= i { - // safety: this is already locked and can't be unlocked - // by the previous loop - locks[first_index].raw_unlock(); - } + // nothing is locked anymore + locked.set(0); // call lock on this to prevent a spin loop - first_index = i; + first_index.set(i); continue 'outer; } } @@ -99,7 +99,12 @@ unsafe impl RawLock for RetryingLockCollection { break; } }, - || utils::attempt_to_recover_locks_from_panic(&locked), + || { + utils::attempt_to_recover_locks_from_panic(&locks[0..locked.get()]); + if first_index.get() >= locked.get() { + locks[first_index.get()].raw_unlock(); + } + }, ) } @@ -113,25 +118,23 @@ unsafe impl RawLock for RetryingLockCollection { } // these will be unlocked in case of a panic - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || unsafe { for (i, lock) in locks.iter().enumerate() { // safety: we have the thread key if lock.raw_try_lock() { - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } else { - for lock in locks.iter().take(i) { - // safety: we already locked all of these - lock.raw_unlock(); - } + // safety: we already locked all of these + attempt_to_recover_locks_from_panic(&locks[0..i]); return false; } } true }, - || utils::attempt_to_recover_locks_from_panic(&locked), + || utils::attempt_to_recover_locks_from_panic(&locks[0..locked.get()]), ) } @@ -144,7 +147,6 @@ unsafe impl RawLock for RetryingLockCollection { } unsafe fn raw_read(&self) { - let mut first_index = 0; let locks = get_locks(&self.data); if locks.is_empty() { @@ -152,35 +154,35 @@ unsafe impl RawLock for RetryingLockCollection { return; } - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); + let first_index = Cell::new(0); handle_unwind( || 'outer: loop { // safety: we have the thread key - locks[first_index].raw_read(); + locks[first_index.get()].raw_read(); for (i, lock) in locks.iter().enumerate() { - if i == first_index { + if i == first_index.get() { continue; } // safety: we have the thread key if lock.raw_try_read() { - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } else { - for lock in locked.borrow().iter() { - // safety: we already locked all of these - lock.raw_unlock_read(); - } - // these are no longer locked - locked.borrow_mut().clear(); + // safety: we already locked all of these + attempt_to_recover_reads_from_panic(&locks[0..i]); - if first_index >= i { + if first_index.get() >= i { // safety: this is already locked and can't be unlocked // by the previous loop - locks[first_index].raw_unlock_read(); + locks[first_index.get()].raw_unlock_read(); } + // these are no longer locked + locked.set(0); + // don't go into a spin loop, wait for this one to lock - first_index = i; + first_index.set(i); continue 'outer; } } @@ -188,7 +190,12 @@ unsafe impl RawLock for RetryingLockCollection { // safety: we locked all the data break; }, - || utils::attempt_to_recover_reads_from_panic(&locked), + || { + utils::attempt_to_recover_reads_from_panic(&locks[0..locked.get()]); + if first_index.get() >= locked.get() { + locks[first_index.get()].raw_unlock_read(); + } + }, ) } @@ -201,25 +208,23 @@ unsafe impl RawLock for RetryingLockCollection { return true; } - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || unsafe { for (i, lock) in locks.iter().enumerate() { // safety: we have the thread key if lock.raw_try_read() { - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } else { - for lock in locks.iter().take(i) { - // safety: we already locked all of these - lock.raw_unlock_read(); - } + // safety: we already locked all of these + attempt_to_recover_reads_from_panic(&locks[0..i]); return false; } } true }, - || utils::attempt_to_recover_reads_from_panic(&locked), + || utils::attempt_to_recover_reads_from_panic(&locks[0..locked.get()]), ) } @@ -901,4 +906,16 @@ mod tests { assert_eq!(collection.into_inner().len(), 2); } + + #[test] + fn lock_empty_lock_collection() { + let mut key = ThreadKey::get().unwrap(); + let collection: RetryingLockCollection<[RwLock; 0]> = RetryingLockCollection::new([]); + + let guard = collection.lock(&mut key); + assert!(guard.len() == 0); + + let guard = collection.read(&mut key); + assert!(guard.len() == 0); + } } diff --git a/src/collection/utils.rs b/src/collection/utils.rs index d368773..7f29037 100644 --- a/src/collection/utils.rs +++ b/src/collection/utils.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::Cell; use crate::handle_unwind::handle_unwind; use crate::lockable::RawLock; @@ -6,31 +6,31 @@ use crate::lockable::RawLock; /// Lock a set of locks in the given order. It's UB to call this without a `ThreadKey` pub unsafe fn ordered_lock(locks: &[&dyn RawLock]) { // these will be unlocked in case of a panic - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || { for lock in locks { lock.raw_lock(); - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } }, - || attempt_to_recover_locks_from_panic(&locked), + || attempt_to_recover_locks_from_panic(&locks[0..locked.get()]), ) } /// Lock a set of locks in the given order. It's UB to call this without a `ThreadKey` pub unsafe fn ordered_read(locks: &[&dyn RawLock]) { - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || { for lock in locks { lock.raw_read(); - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } }, - || attempt_to_recover_reads_from_panic(&locked), + || attempt_to_recover_reads_from_panic(&locks[0..locked.get()]), ) } @@ -38,14 +38,14 @@ pub unsafe fn ordered_read(locks: &[&dyn RawLock]) { /// locks contain duplicates, or if this is called by multiple threads with the /// locks in different orders. pub unsafe fn ordered_try_lock(locks: &[&dyn RawLock]) -> bool { - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || unsafe { for (i, lock) in locks.iter().enumerate() { // safety: we have the thread key if lock.raw_try_lock() { - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } else { for lock in &locks[0..i] { // safety: this lock was already acquired @@ -59,7 +59,7 @@ pub unsafe fn ordered_try_lock(locks: &[&dyn RawLock]) -> bool { }, || // safety: everything in locked is locked - attempt_to_recover_locks_from_panic(&locked), + attempt_to_recover_locks_from_panic(&locks[0..locked.get()]), ) } @@ -67,14 +67,14 @@ pub unsafe fn ordered_try_lock(locks: &[&dyn RawLock]) -> bool { /// is called by multiple threads with the locks in different orders. pub unsafe fn ordered_try_read(locks: &[&dyn RawLock]) -> bool { // these will be unlocked in case of a panic - let locked = RefCell::new(Vec::with_capacity(locks.len())); + let locked = Cell::new(0); handle_unwind( || unsafe { for (i, lock) in locks.iter().enumerate() { // safety: we have the thread key if lock.raw_try_read() { - locked.borrow_mut().push(*lock); + locked.set(locked.get() + 1); } else { for lock in &locks[0..i] { // safety: this lock was already acquired @@ -88,34 +88,30 @@ pub unsafe fn ordered_try_read(locks: &[&dyn RawLock]) -> bool { }, || // safety: everything in locked is locked - attempt_to_recover_reads_from_panic(&locked), + attempt_to_recover_reads_from_panic(&locks[0..locked.get()]), ) } /// Unlocks the already locked locks in order to recover from a panic -pub unsafe fn attempt_to_recover_locks_from_panic(locked: &RefCell>) { +pub unsafe fn attempt_to_recover_locks_from_panic(locks: &[&dyn RawLock]) { handle_unwind( || { - let mut locked = locked.borrow_mut(); - while let Some(locked_lock) = locked.pop() { - locked_lock.raw_unlock(); - } + // safety: the caller assumes that these are already locked + locks.iter().for_each(|lock| lock.raw_unlock()); }, // if we get another panic in here, we'll just have to poison what remains - || locked.borrow().iter().for_each(|l| l.poison()), + || locks.iter().for_each(|l| l.poison()), ) } /// Unlocks the already locked locks in order to recover from a panic -pub unsafe fn attempt_to_recover_reads_from_panic(locked: &RefCell>) { +pub unsafe fn attempt_to_recover_reads_from_panic(locked: &[&dyn RawLock]) { handle_unwind( || { - let mut locked = locked.borrow_mut(); - while let Some(locked_lock) = locked.pop() { - locked_lock.raw_unlock_read(); - } + // safety: the caller assumes these are already locked + locked.iter().for_each(|lock| lock.raw_unlock()); }, // if we get another panic in here, we'll just have to poison what remains - || locked.borrow().iter().for_each(|l| l.poison()), + || locked.iter().for_each(|l| l.poison()), ) } diff --git a/src/key.rs b/src/key.rs index c7369be..4cd145d 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,7 +1,6 @@ -use std::cell::Cell; +use std::cell::{Cell, LazyCell}; use std::fmt::{self, Debug}; use std::marker::PhantomData; -use std::sync::LazyLock; use sealed::Sealed; @@ -16,7 +15,7 @@ mod sealed { } thread_local! { - static KEY: LazyLock = LazyLock::new(KeyCell::default); + static KEY: LazyCell = LazyCell::new(KeyCell::default); } /// The key for the current thread. @@ -44,6 +43,7 @@ unsafe impl Keyable for &mut ThreadKey {} unsafe impl Sync for ThreadKey {} #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for ThreadKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ThreadKey") diff --git a/src/lockable.rs b/src/lockable.rs index 70d442a..4f7cfe5 100644 --- a/src/lockable.rs +++ b/src/lockable.rs @@ -276,10 +276,8 @@ macro_rules! tuple_impls { unsafe impl<$($generic: Lockable,)*> Lockable for ($($generic,)*) { type Guard<'g> = ($($generic::Guard<'g>,)*) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); + $(self.$value.get_ptrs(ptrs));* } unsafe fn guard(&self) -> Self::Guard<'_> { diff --git a/src/mutex.rs b/src/mutex.rs index 99d0981..d6cba7d 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -213,6 +213,36 @@ mod tests { assert!(guard.0 != guard.2) } + #[test] + fn ref_as_mut() { + let mut key = ThreadKey::get().unwrap(); + let collection = LockCollection::new(crate::Mutex::new(0)); + let mut guard = collection.lock(&mut key); + let guard_mut = guard.as_mut().as_mut(); + + *guard_mut = 3; + drop(guard); + + let guard = collection.lock(&mut key); + + assert_eq!(guard.as_ref().as_ref(), &3); + } + + #[test] + fn guard_as_mut() { + let mut key = ThreadKey::get().unwrap(); + let mutex = crate::Mutex::new(0); + let mut guard = mutex.lock(&mut key); + let guard_mut = guard.as_mut(); + + *guard_mut = 3; + drop(guard); + + let guard = mutex.lock(&mut key); + + assert_eq!(guard.as_ref(), &3); + } + #[test] fn dropping_guard_releases_mutex() { let mut key = ThreadKey::get().unwrap(); diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs index f7a01a4..4e4d5f1 100644 --- a/src/mutex/guard.rs +++ b/src/mutex/guard.rs @@ -34,6 +34,7 @@ impl Ord for MutexRef<'_, T, R> { } #[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for MutexRef<'_, T, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -41,6 +42,7 @@ impl Hash for MutexRef<'_, T, R> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for MutexRef<'_, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) @@ -106,6 +108,7 @@ impl<'a, T: ?Sized, R: RawMutex> MutexRef<'a, T, R> { // there's nothing i can do about that #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialEq for MutexGuard<'_, '_, T, Key, R> { fn eq(&self, other: &Self) -> bool { self.deref().eq(&**other) @@ -113,9 +116,11 @@ impl PartialEq for MutexGuard< } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Eq for MutexGuard<'_, '_, T, Key, R> {} #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialOrd for MutexGuard<'_, '_, T, Key, R> { @@ -125,6 +130,7 @@ impl PartialOrd } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Ord for MutexGuard<'_, '_, T, Key, R> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.deref().cmp(&**other) @@ -132,6 +138,7 @@ impl Ord for MutexGuard<'_, '_, T, K } #[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for MutexGuard<'_, '_, T, Key, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -139,6 +146,7 @@ impl Hash for MutexGuard<'_, '_, T, } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for MutexGuard<'_, '_, T, Key, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) diff --git a/src/mutex/mutex.rs b/src/mutex/mutex.rs index 5b838a2..0bd5286 100644 --- a/src/mutex/mutex.rs +++ b/src/mutex/mutex.rs @@ -130,6 +130,7 @@ impl Mutex { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for Mutex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // safety: this is just a try lock, and the value is dropped diff --git a/src/poisonable/error.rs b/src/poisonable/error.rs index 9721ce4..bff011d 100644 --- a/src/poisonable/error.rs +++ b/src/poisonable/error.rs @@ -4,6 +4,7 @@ use std::error::Error; use super::{PoisonError, PoisonGuard, TryLockPoisonableError}; #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl fmt::Debug for PoisonError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PoisonError").finish_non_exhaustive() @@ -12,6 +13,7 @@ impl fmt::Debug for PoisonError { impl fmt::Display for PoisonError { #[cfg_attr(test, mutants::skip)] + #[cfg(not(tarpaulin_include))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { "poisoned lock: another task failed inside".fmt(f) } @@ -151,6 +153,7 @@ impl PoisonError { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl fmt::Debug for TryLockPoisonableError<'_, '_, G, Key> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -162,6 +165,7 @@ impl fmt::Debug for TryLockPoisonableError<'_, '_, G, Key> { impl fmt::Display for TryLockPoisonableError<'_, '_, G, Key> { #[cfg_attr(test, mutants::skip)] + #[cfg(not(tarpaulin_include))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Poisoned(..) => "poisoned lock: another task failed inside", diff --git a/src/poisonable/flag.rs b/src/poisonable/flag.rs index 6b567c8..9186bbc 100644 --- a/src/poisonable/flag.rs +++ b/src/poisonable/flag.rs @@ -29,6 +29,7 @@ impl PoisonFlag { } #[mutants::skip] // None of the tests have panic = "abort", so this can't be tested + #[cfg(not(tarpaulin_include))] pub fn is_poisoned(&self) -> bool { false } diff --git a/src/poisonable/guard.rs b/src/poisonable/guard.rs index 36566f5..3f85d25 100644 --- a/src/poisonable/guard.rs +++ b/src/poisonable/guard.rs @@ -49,6 +49,7 @@ impl Ord for PoisonRef<'_, Guard> { } #[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for PoisonRef<'_, Guard> { fn hash(&self, state: &mut H) { self.guard.hash(state) @@ -56,6 +57,7 @@ impl Hash for PoisonRef<'_, Guard> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for PoisonRef<'_, Guard> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) @@ -95,6 +97,7 @@ impl AsMut for PoisonRef<'_, Guard> { } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialEq for PoisonGuard<'_, '_, Guard, Key> { fn eq(&self, other: &Self) -> bool { self.guard.eq(&other.guard) @@ -102,6 +105,7 @@ impl PartialEq for PoisonGuard<'_, '_, Guard, Ke } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl PartialOrd for PoisonGuard<'_, '_, Guard, Key> { fn partial_cmp(&self, other: &Self) -> Option { self.guard.partial_cmp(&other.guard) @@ -109,9 +113,11 @@ impl PartialOrd for PoisonGuard<'_, '_, Guard, } #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Eq for PoisonGuard<'_, '_, Guard, Key> {} #[mutants::skip] // it's hard to get two guards safely +#[cfg(not(tarpaulin_include))] impl Ord for PoisonGuard<'_, '_, Guard, Key> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.guard.cmp(&other.guard) @@ -119,6 +125,7 @@ impl Ord for PoisonGuard<'_, '_, Guard, Key> { } #[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for PoisonGuard<'_, '_, Guard, Key> { fn hash(&self, state: &mut H) { self.guard.hash(state) @@ -126,6 +133,7 @@ impl Hash for PoisonGuard<'_, '_, Guard, Key> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for PoisonGuard<'_, '_, Guard, Key> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&self.guard, f) diff --git a/src/poisonable/poisonable.rs b/src/poisonable/poisonable.rs index 0bc2b03..2dac4bb 100644 --- a/src/poisonable/poisonable.rs +++ b/src/poisonable/poisonable.rs @@ -13,6 +13,7 @@ use super::{ unsafe impl RawLock for Poisonable { #[mutants::skip] // this should never run + #[cfg(not(tarpaulin_include))] fn poison(&self) { self.inner.poison() } diff --git a/src/rwlock/read_guard.rs b/src/rwlock/read_guard.rs index 2195e44..bd22837 100644 --- a/src/rwlock/read_guard.rs +++ b/src/rwlock/read_guard.rs @@ -34,6 +34,7 @@ impl Ord for RwLockReadRef<'_, T, R> { } #[mutants::skip] // hashing involves PRNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for RwLockReadRef<'_, T, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -41,6 +42,7 @@ impl Hash for RwLockReadRef<'_, T, R> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RwLockReadRef<'_, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) @@ -88,6 +90,7 @@ impl<'a, T: ?Sized, R: RawRwLock> RwLockReadRef<'a, T, R> { } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl PartialEq for RwLockReadGuard<'_, '_, T, Key, R> { @@ -97,9 +100,11 @@ impl PartialEq } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl Eq for RwLockReadGuard<'_, '_, T, Key, R> {} #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl PartialOrd for RwLockReadGuard<'_, '_, T, Key, R> { @@ -109,6 +114,7 @@ impl PartialOrd } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl Ord for RwLockReadGuard<'_, '_, T, Key, R> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.deref().cmp(&**other) @@ -116,6 +122,7 @@ impl Ord for RwLockReadGuard<'_, '_ } #[mutants::skip] // hashing involves PRNG and is hard to test +#[cfg(not(tarpaulin_include))] impl Hash for RwLockReadGuard<'_, '_, T, Key, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -123,6 +130,7 @@ impl Hash for RwLockReadGuard<'_, } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RwLockReadGuard<'_, '_, T, Key, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) diff --git a/src/rwlock/read_lock.rs b/src/rwlock/read_lock.rs index 5ac0bbb..5dd83a7 100644 --- a/src/rwlock/read_lock.rs +++ b/src/rwlock/read_lock.rs @@ -34,6 +34,7 @@ unsafe impl Sharable for ReadLock<'_, T, R> } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for ReadLock<'_, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // safety: this is just a try lock, and the value is dropped @@ -108,7 +109,7 @@ impl ReadLock<'_, T, R> { /// use happylock::rwlock::ReadLock; /// /// let key = ThreadKey::get().unwrap(); - /// let lock: &'static mut RwLock<_> = Box::leak(Box::new(RwLock::new(1))); + /// let lock: RwLock<_> = RwLock::new(1); /// let reader = ReadLock::new(&lock); /// /// let n = reader.lock(key); diff --git a/src/rwlock/rwlock.rs b/src/rwlock/rwlock.rs index 7a105d7..038e6c7 100644 --- a/src/rwlock/rwlock.rs +++ b/src/rwlock/rwlock.rs @@ -141,6 +141,7 @@ impl RwLock { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RwLock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // safety: this is just a try lock, and the value is dropped diff --git a/src/rwlock/write_guard.rs b/src/rwlock/write_guard.rs index ff559b8..c971260 100644 --- a/src/rwlock/write_guard.rs +++ b/src/rwlock/write_guard.rs @@ -34,6 +34,7 @@ impl Ord for RwLockWriteRef<'_, T, R> { } #[mutants::skip] // hashing involves PRNG and is difficult to test +#[cfg(not(tarpaulin_include))] impl Hash for RwLockWriteRef<'_, T, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -41,6 +42,7 @@ impl Hash for RwLockWriteRef<'_, T, R> { } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RwLockWriteRef<'_, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) @@ -103,6 +105,7 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> RwLockWriteRef<'a, T, R> { } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl PartialEq for RwLockWriteGuard<'_, '_, T, Key, R> { @@ -112,9 +115,11 @@ impl PartialEq } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl Eq for RwLockWriteGuard<'_, '_, T, Key, R> {} #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl PartialOrd for RwLockWriteGuard<'_, '_, T, Key, R> { @@ -124,6 +129,7 @@ impl PartialOrd } #[mutants::skip] // it's hard to get two read guards safely +#[cfg(not(tarpaulin_include))] impl Ord for RwLockWriteGuard<'_, '_, T, Key, R> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.deref().cmp(&**other) @@ -131,6 +137,7 @@ impl Ord for RwLockWriteGuard<'_, ' } #[mutants::skip] // hashing involves PRNG and is difficult to test +#[cfg(not(tarpaulin_include))] impl Hash for RwLockWriteGuard<'_, '_, T, Key, R> { fn hash(&self, state: &mut H) { self.deref().hash(state) @@ -138,6 +145,7 @@ impl Hash for RwLockWriteGuard<'_, } #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for RwLockWriteGuard<'_, '_, T, Key, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(&**self, f) diff --git a/src/rwlock/write_lock.rs b/src/rwlock/write_lock.rs index 443fbcd..cc96953 100644 --- a/src/rwlock/write_lock.rs +++ b/src/rwlock/write_lock.rs @@ -26,6 +26,7 @@ unsafe impl Lockable for WriteLock<'_, T, R // no way to express that. I don't think I want to ever express that. #[mutants::skip] +#[cfg(not(tarpaulin_include))] impl Debug for WriteLock<'_, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // safety: this is just a try lock, and the value is dropped -- cgit v1.2.3