From 55b3a2425b242fbc5c6e471f220eeb8b949e8751 Mon Sep 17 00:00:00 2001 From: Mica White Date: Wed, 26 Aug 2026 20:46:31 -0400 Subject: Add tests --- src/collection/boxed.rs | 2 + src/collection/owned.rs | 40 +- src/context.rs | 617 +++++++++++++++++++++++++++++++ src/context/context.rs | 158 ++++++++ src/context/guard.rs | 58 +++ src/context/iterator.rs | 695 +++++++++++++++++++++++++++++++++++ src/context/tuple.rs | 847 +++++++++++++++++++++++++++++++++++++++++++ src/iterator.rs | 29 -- src/iterator/context.rs | 52 --- src/iterator/guard.rs | 58 --- src/iterator/iterator.rs | 118 ------ src/iterator/tuple.rs | 202 ----------- src/lib.rs | 5 +- src/lockable.rs | 6 + src/mutex.rs | 2 + src/poisonable/poisonable.rs | 22 +- tarpaulin-report.html | 299 ++++++++++----- 17 files changed, 2657 insertions(+), 553 deletions(-) create mode 100644 src/context.rs create mode 100644 src/context/context.rs create mode 100644 src/context/guard.rs create mode 100644 src/context/iterator.rs create mode 100644 src/context/tuple.rs delete mode 100644 src/iterator.rs delete mode 100644 src/iterator/context.rs delete mode 100644 src/iterator/guard.rs delete mode 100644 src/iterator/iterator.rs delete mode 100644 src/iterator/tuple.rs diff --git a/src/collection/boxed.rs b/src/collection/boxed.rs index 1478120..66e6df0 100755 --- a/src/collection/boxed.rs +++ b/src/collection/boxed.rs @@ -721,6 +721,8 @@ impl BoxedLockCollection { } } +impl BoxedLockCollection {} + impl BoxedLockCollection { /// Consumes this `BoxedLockCollection`, returning the underlying data. /// diff --git a/src/collection/owned.rs b/src/collection/owned.rs index 07a5402..483bc09 100755 --- a/src/collection/owned.rs +++ b/src/collection/owned.rs @@ -1,4 +1,4 @@ -use crate::iterator::LockContext; +use crate::context::LockContext; use crate::lockable::{ Lockable, LockableGetMut, LockableIntoInner, OwnedLockable, RawLock, Sharable, }; @@ -61,7 +61,7 @@ unsafe impl Lockable for OwnedLockCollection { where Self: 'a; - #[mutants::skip] // It's hard to test lkocks in an OwnedLockCollection, because they're owned + #[mutants::skip] // It's hard to test locks in an OwnedLockCollection, because they're owned #[cfg(not(tarpaulin_include))] fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { // It's ok to use self here, because the values in the collection already @@ -350,6 +350,38 @@ impl OwnedLockCollection { Ok(LockGuard { guard, key }) } + /// Creates a context that can be used to iterate over the items in order. + /// + /// Sometimes, partial allocation of locks is useful. For example, you may + /// want to acquire a lock on the first element of a list before deciding if + /// the second element should be locked. This function creates a + /// [`LockContext`] which is capable of doing exactly that. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// + /// let (use_other, tuple) = tuple.lock_0(); + /// let number = if **use_other { + /// tuple.lock_2().0 + /// } else { + /// tuple.lock_1().0 + /// }; + /// assert_eq!(**number, 67); + /// ``` + #[must_use] + pub const fn context(&self) -> LockContext<'_, L> { + LockContext::new(&self.child) + } + /// Unlocks the underlying lockable data type, returning the key that's /// associated with it. /// @@ -559,10 +591,6 @@ impl OwnedLockCollection { } impl OwnedLockCollection { - pub const fn context(&self) -> LockContext<'_, L> { - LockContext::new(&self.child) - } - /// Gets the underlying collection, consuming this collection. /// /// # Examples diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..2f9ca64 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,617 @@ +use std::marker::PhantomData; + +use crate::ThreadKey; + +mod context; +mod guard; +pub mod iterator; +pub mod tuple; + +/// Allows iterating over a lock collection, without locking every element at +/// once. +/// +/// Sometimes, partial allocation of locks is useful. For example, you may +/// want to acquire a lock on the first element of a list before deciding if +/// the second element should be locked. This function creates a +/// [`LockContext`] which is capable of doing exactly that. +/// +/// Upon using this context, the [`ThreadKey`] is stored inside this context. +/// This ensures that nothing else, besides the types exposed by the context, +/// can be locked until this context is dropped. To re-acquire the `ThreadKey`, +/// call [`LockContext::unlock`]. +/// +/// A [`LockContext`] can be created by calling [`OwnedLockCollection::context`]. +/// +/// # Examples +/// +/// Iterating through a tuple. +/// +/// ``` +/// use happylock::{Mutex, ThreadKey}; +/// use happylock::collection::OwnedLockCollection; +/// +/// let key = ThreadKey::get().unwrap(); +/// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67)); +/// let locks = OwnedLockCollection::new(data); +/// let mut ctx = locks.context(); +/// let tuple = ctx.tuple(key); +/// +/// let (use_other, tuple) = tuple.lock_0(); +/// let number = if **use_other { +/// tuple.lock_2().0 +/// } else { +/// tuple.lock_1().0 +/// }; +/// assert_eq!(**number, 67); +/// ``` +/// +/// Iterating through a list +/// +/// ``` +/// use happylock::{Mutex, ThreadKey}; +/// use happylock::collection::OwnedLockCollection; +/// +/// let key = ThreadKey::get().unwrap(); +/// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; +/// let locks = OwnedLockCollection::new(data); +/// let mut ctx = locks.context(); +/// let mut iter = ctx.iter(key); +/// +/// let mut sum = 0; +/// while let Some(item) = iter.lock_next() { +/// sum += **item; +/// } +/// +/// assert_eq!(sum, 12); +/// ``` +/// +/// [`OwnedLockCollection::context`]: crate::collection::OwnedLockCollection::context +pub struct LockContext<'l, L> { + key: Option, + lockable: &'l L, +} + +/// Iterates through a collection of locks, allowing for partial allocation of +/// locks, or for some locks to be skipped. +/// +/// Sometimes, partial allocation of locks is useful. For example, you may +/// want to acquire a lock on the first element of a list before deciding if +/// the second element should be locked. If the list is iterable, then a +/// [`LockingIterator`] is capable of doing exactly that. +/// +/// A [`LockingIterator`] can be created by calling the [`LockContext::iter`] +/// method. +/// +/// # Example +/// +/// ``` +/// use happylock::{Mutex, ThreadKey}; +/// use happylock::collection::OwnedLockCollection; +/// +/// let key = ThreadKey::get().unwrap(); +/// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; +/// let locks = OwnedLockCollection::new(data); +/// let mut ctx = locks.context(); +/// let mut iter = ctx.iter(key); +/// +/// let mut sum = 0; +/// while let Some(item) = iter.lock_next() { +/// sum += **item; +/// } +/// +/// assert_eq!(sum, 12); +/// ``` +pub struct LockingIterator<'context, I, Outer = ()> { + key: &'context ThreadKey, + iterator: I, + outer: Outer, +} + +/// Iterates through a tuple of locks, requiring that elements are only locked +/// before any successive elements are locked. +/// +/// Sometimes, partial allocation of locks is useful. For example, you may want +/// to acquire a lock on one item before deciding if the second item should be +/// locked. If the locks can be organized into a tuple, [`LockingTuple`] is +/// capable of doing exactly that. +/// +/// A [`LockingTuple`] can be created by calling the [`LockContext::iter`] +/// method. +/// +/// # Example +/// +/// ``` +/// use happylock::{Mutex, ThreadKey}; +/// use happylock::collection::OwnedLockCollection; +/// +/// let key = ThreadKey::get().unwrap(); +/// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67)); +/// let locks = OwnedLockCollection::new(data); +/// let mut ctx = locks.context(); +/// let tuple = ctx.tuple(key); +/// +/// let (use_other, tuple) = tuple.lock_0(); +/// let number = if **use_other { +/// tuple.lock_2().0 +/// } else { +/// tuple.lock_1().0 +/// }; +/// assert_eq!(**number, 67); +/// ``` +pub struct LockingTuple<'context, L, C, Outer = ()> { + _lockable: PhantomData, + key: &'context ThreadKey, + tuple: &'context C, + outer: Outer, +} + +/// An RAII implementation of a “scoped lock”. 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`] +/// +/// Unlike other guards in this crate, this guard holds a reference to a +/// [`ThreadKey`], which is stored in the [`LockContext`]. This ensures that +/// context cannot be dropped until all guards created with the context are +/// dropped. The `ThreadKey` can be re-acquired by calling +/// [`LockContext::unlock`]. +/// +/// [`Mutex`]: `crate::mutex::Mutex` +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` +/// [`lock`]: `crate::mutex::Mutex::lock` +/// [`try_lock`]: `crate::Mutex::try_lock` +pub struct ContextGuard<'a, Guard, Key> { + _key: &'a Key, + guard: Guard, +} + +#[cfg(test)] +mod tests { + use crate::{ + collection::OwnedLockCollection, context::iterator::TryLockNextError, Mutex, RwLock, + ThreadKey, + }; + + #[test] + fn display_works_for_guard() { + let key = ThreadKey::get().unwrap(); + let collection = OwnedLockCollection::new((Mutex::new("Hello, world!"),)); + let mut context = collection.context(); + let tuple = context.tuple(key); + let (guard, _) = tuple.lock_0(); + assert_eq!(guard.to_string(), "Hello, world!".to_string()); + } + + #[test] + fn try_lock_mut_works_single_element_tuple() { + let key = ThreadKey::get().unwrap(); + let collection = OwnedLockCollection::new((RwLock::new(42),)); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let mut guard = tuple.try_lock_mut_0().unwrap(); + **guard = 67; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_none()); + }); + }); + drop(guard); + + let result = tuple.try_read_mut_0().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_0(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_some()); + drop(guard); + }); + }); + assert_eq!(**result, 67); + } + + #[test] + fn try_lock_mut_works_double_element_tuple() { + let key = ThreadKey::get().unwrap(); + let collection = OwnedLockCollection::new((RwLock::new(42), RwLock::new(67))); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + + let mut guard = tuple.try_lock_mut_0().unwrap(); + **guard *= 2; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_none()); + }); + }); + drop(guard); + let mut guard = tuple.try_lock_mut_1().unwrap(); + **guard *= 2; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_1(); + assert!(guard.is_none()); + }); + }); + drop(guard); + + let result = tuple.try_read_mut_0().unwrap(); + assert_eq!(**result, 84); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_0(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_some()); + drop(guard); + }); + }); + drop(result); + let result = tuple.try_read_mut_1().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_1(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_1(); + assert!(guard.is_some()); + drop(guard); + }); + }); + assert_eq!(**result, 134); + } + + #[test] + fn try_lock_mut_works_triple_element_tuple() { + let key = ThreadKey::get().unwrap(); + let collection = OwnedLockCollection::new((RwLock::new(1), RwLock::new(2), RwLock::new(3))); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + + let mut guard = tuple.try_lock_mut_0().unwrap(); + **guard *= 2; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_none()); + }); + }); + drop(guard); + let mut guard = tuple.try_lock_mut_1().unwrap(); + **guard *= 2; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_1(); + assert!(guard.is_none()); + }); + }); + drop(guard); + let mut guard = tuple.try_lock_mut_2().unwrap(); + **guard *= 2; + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_read_mut_2(); + assert!(guard.is_none()); + }); + }); + drop(guard); + + let result = tuple.try_read_mut_0().unwrap(); + assert_eq!(**result, 2); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_0(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_0(); + assert!(guard.is_some()); + drop(guard); + }); + }); + drop(result); + let result = tuple.try_read_mut_1().unwrap(); + assert_eq!(**result, 4); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_1(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_1(); + assert!(guard.is_some()); + drop(guard); + }); + }); + drop(result); + let result = tuple.try_read_mut_2().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = collection.context(); + let mut tuple = context.tuple(key); + let guard = tuple.try_lock_mut_2(); + assert!(guard.is_none()); + drop(guard); + let guard = tuple.try_read_mut_2(); + assert!(guard.is_some()); + drop(guard); + }); + }); + assert_eq!(**result, 6); + } + + #[test] + fn basic_iteration() { + let key = ThreadKey::get().unwrap(); + let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let mut iter = ctx.iter(key); + + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 1); + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 3); + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 8); + assert!(iter.lock_next().is_none()); + } + + #[test] + fn recurse_tuple_of_lists() { + let key = ThreadKey::get().unwrap(); + let data = ( + [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + Mutex::new(true), + ); + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let tuple = ctx.tuple(key); + let mut iter = tuple.recurse_0_iter(); + + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 1); + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 2); + let item = iter.lock_next().unwrap(); + assert_eq!(**item, 3); + assert!(iter.lock_next().is_none()); + + let tuple = iter.exit(); + let (should_assert, _) = tuple.lock_1(); + assert!(**should_assert); + } + + #[test] + fn recurse_list_of_lists() { + let key = ThreadKey::get().unwrap(); + let data = [ + [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + ]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let mut iter = ctx.iter(key); + + let mut list = iter.recurse_next().unwrap(); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 1); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 2); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 3); + assert!(list.lock_next().is_none()); + iter = list.exit(); + let mut list = iter.recurse_next().unwrap(); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 4); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 5); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 6); + assert!(list.lock_next().is_none()); + } + + #[test] + fn recurse_last_list_of_lists() { + let key = ThreadKey::get().unwrap(); + let data = [ + [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + ]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let iter = ctx.iter(key); + + let mut list = iter.recurse_last().unwrap(); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 4); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 5); + let item = list.lock_next().unwrap(); + assert_eq!(**item, 6); + assert!(list.lock_next().is_none()); + } + + #[test] + fn recurse_last_list_of_empty_list() { + let key = ThreadKey::get().unwrap(); + let data: [[Mutex; 0]; 0] = []; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let iter = ctx.iter(key); + + let list = iter.recurse_last(); + assert!(list.is_none()) + } + + #[test] + fn recurse_list_of_tuples() { + let key = ThreadKey::get().unwrap(); + let data = [ + (Mutex::new(true), Mutex::new(1)), + (Mutex::new(false), Mutex::new(2)), + (Mutex::new(true), Mutex::new(3)), + ]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let mut iter = ctx.iter(key); + + let tuple = iter.recurse_next_tuple().unwrap(); + let (should_count, mut tuple) = tuple.lock_0(); + assert!(**should_count); + let num = tuple.lock_mut_1(); + assert_eq!(**num, 1); + drop(num); + iter = tuple.exit(); + let tuple = iter.recurse_next_tuple().unwrap(); + let (should_count, mut tuple) = tuple.lock_0(); + assert!(!**should_count); + let num = tuple.lock_mut_1(); + assert_eq!(**num, 2); + drop(num); + iter = tuple.exit(); + let tuple = iter.recurse_next_tuple().unwrap(); + let (should_count, mut tuple) = tuple.lock_0(); + assert!(**should_count); + let num = tuple.lock_mut_1(); + assert_eq!(**num, 3); + drop(num); + iter = tuple.exit(); + assert!(iter.recurse_next_tuple().is_none()); + } + + #[test] + fn recurse_last_list_of_tuples() { + let key = ThreadKey::get().unwrap(); + let data = [ + (Mutex::new(true), Mutex::new(1)), + (Mutex::new(false), Mutex::new(2)), + (Mutex::new(true), Mutex::new(3)), + ]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let iter = ctx.iter(key); + + let tuple = iter.recurse_last_tuple().unwrap(); + let (should_count, mut tuple) = tuple.lock_0(); + if **should_count { + let num = tuple.lock_mut_1(); + assert_eq!(**num, 3); + } else { + panic!(); + } + } + + #[test] + fn recurse_last_of_empty_list_of_tuples() { + let key = ThreadKey::get().unwrap(); + let data: [(Mutex, Mutex); 0] = []; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let iter = ctx.iter(key); + + let tuple = iter.recurse_last_tuple(); + assert!(tuple.is_none()); + } + + #[test] + fn lock_last_of_list() { + let key = ThreadKey::get().unwrap(); + let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let iter = ctx.iter(key); + let last = iter.lock_last().unwrap(); + assert_eq!(**last, 8); + } + + #[test] + fn try_lock_next_works() { + let key = ThreadKey::get().unwrap(); + let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + let locks = OwnedLockCollection::new(data); + let mut ctx = locks.context(); + let mut iter = ctx.iter(key).peekable(); + + let item = iter.try_lock_next().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = locks.context(); + let mut iter = context.iter(key).peekable(); + let guard = iter.try_lock_next(); + assert_eq!(guard.unwrap_err(), TryLockNextError::WouldBlock); + }); + }); + assert_eq!(**item, 1); + let item = iter.try_lock_next().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = locks.context(); + let mut iter = context.iter(key).peekable(); + iter.skip_next(); + let guard = iter.try_lock_next(); + assert_eq!(guard.unwrap_err(), TryLockNextError::WouldBlock); + }); + }); + assert_eq!(**item, 3); + let item = iter.try_lock_next().unwrap(); + std::thread::scope(|s| { + s.spawn(|| { + let key = ThreadKey::get().unwrap(); + let mut context = locks.context(); + let mut iter = context.iter(key).peekable(); + iter.skip_mut(2); + let guard = iter.try_lock_next(); + assert_eq!(guard.unwrap_err(), TryLockNextError::WouldBlock); + }); + }); + assert_eq!(**item, 8); + assert_eq!( + iter.try_lock_next().unwrap_err(), + TryLockNextError::FinishedIteration + ); + } +} diff --git a/src/context/context.rs b/src/context/context.rs new file mode 100644 index 0000000..adc684f --- /dev/null +++ b/src/context/context.rs @@ -0,0 +1,158 @@ +use std::marker::PhantomData; + +use crate::{ + context::{LockContext, LockingIterator, LockingTuple}, + lockable::{Lockable, OwnedLockable}, + ThreadKey, +}; + +impl<'l, L> LockContext<'l, L> { + pub(crate) const fn new(lockable: &'l L) -> Self + where + L: OwnedLockable, + { + Self { + key: None, + lockable, + } + } + + /// Unlocks all locks in the collection, returning the [`ThreadKey`]. + /// + /// This requires a mutable reference to the context, so it cannot be called + /// without first dropping any [`ContextGuard`]s that reference this context. + /// This method will also return `None` if the context has not been locked + /// with a `ThreadKey`. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(42), Mutex::new(true)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// + /// let (use_other, tuple) = tuple.lock_1(); + /// if **use_other { + /// drop(use_other); + /// drop(tuple); + /// let key = ctx.unlock().unwrap(); + /// let tuple = ctx.tuple(key); + /// let (mut item, _) = tuple.lock_0(); + /// **item = 67; + /// } else { + /// drop(use_other); + /// drop(tuple); + /// }; + /// + /// let key = ctx.unlock().unwrap(); + /// let tuple = ctx.tuple(key); + /// let (number, _) = tuple.lock_0(); + /// assert_eq!(**number, 67); + /// ``` + /// + /// [`ContextGuard`]: `crate::context::ContextGuard` + pub fn unlock(&mut self) -> Option { + self.key.take() + } +} + +impl LockContext<'_, L> { + /// Creates a [`LockingTuple`], which can lock a subset of a tuple of locks, + /// in a specific order. + /// + /// Sometimes, partial allocation of locks is useful. For example, you may want + /// to acquire a lock on one item before deciding if the second item should be + /// locked. If the locks can be organized into a tuple, [`LockingTuple`] is + /// capable of doing exactly that. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// + /// let (use_other, tuple) = tuple.lock_0(); + /// let number = if **use_other { + /// tuple.lock_2().0 + /// } else { + /// tuple.lock_1().0 + /// }; + /// assert_eq!(**number, 67); + /// ``` + pub fn tuple(&mut self, key: ThreadKey) -> LockingTuple<'_, L, L> { + unsafe { + self.key = Some(key); + + LockingTuple { + _lockable: PhantomData, + // safety: we just inserted a key + key: self.key.as_ref().unwrap_unchecked(), + tuple: self.lockable, + outer: (), + } + } + } +} + +impl<'l, L> LockContext<'l, L> +where + &'l L: IntoIterator, +{ + /// Creates a [`LockingIterator`] to iterate through a collection of locks + /// without locking everything at once. + /// + /// Sometimes, partial allocation of locks is useful. For example, you may + /// want to acquire a lock on the first element of a list before deciding if + /// the second element should be locked. If the list is iterable, then a + /// [`LockingIterator`] is capable of doing exactly that. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + // TODO: support scoped locks + // TODO: implement get_disjoint + // TODO: support some sort of index tower thing + #[expect(clippy::iter_not_returning_iterator)] + pub fn iter( + &mut self, + key: ThreadKey, + ) -> LockingIterator<'_, <&'l L as IntoIterator>::IntoIter> { + unsafe { + self.key = Some(key); + + LockingIterator { + // safety: we just inserted a key + key: self.key.as_ref().unwrap_unchecked(), + iterator: self.lockable.into_iter(), + outer: (), + } + } + } +} diff --git a/src/context/guard.rs b/src/context/guard.rs new file mode 100644 index 0000000..0898c1f --- /dev/null +++ b/src/context/guard.rs @@ -0,0 +1,58 @@ +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; + +use super::ContextGuard; + +#[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] +impl Hash for ContextGuard<'_, Guard, Key> { + fn hash(&self, state: &mut H) { + self.guard.hash(state) + } +} + +// No implementations of Eq, PartialEq, PartialOrd, or Ord +// You can't implement both PartialEq and PartialEq +// It's easier to just implement neither and ask users to dereference +// This is less of a problem when using the scoped lock API + +#[mutants::skip] +#[cfg(not(tarpaulin_include))] +impl Debug for ContextGuard<'_, Guard, Key> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&**self, f) + } +} + +impl Display for ContextGuard<'_, Guard, Key> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&**self, f) + } +} + +impl Deref for ContextGuard<'_, Guard, Key> { + type Target = Guard; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +impl DerefMut for ContextGuard<'_, Guard, Key> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard + } +} + +impl AsRef for ContextGuard<'_, Guard, Key> { + fn as_ref(&self) -> &Guard { + &self.guard + } +} + +impl AsMut for ContextGuard<'_, Guard, Key> { + fn as_mut(&mut self) -> &mut Guard { + &mut self.guard + } +} diff --git a/src/context/iterator.rs b/src/context/iterator.rs new file mode 100644 index 0000000..cc3bd7c --- /dev/null +++ b/src/context/iterator.rs @@ -0,0 +1,695 @@ +use std::{ + iter::{Fuse, Peekable, Skip, Take}, + marker::PhantomData, +}; + +use super::{ContextGuard, LockingIterator}; + +use crate::{ + context::LockingTuple, + lockable::{Lockable, RawLock, Sharable}, + ThreadKey, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TryLockNextError { + FinishedIteration, + WouldBlock, +} + +impl<'l, I, O> LockingIterator<'l, I, O> { + fn with_iterator(self, f: impl FnOnce(I) -> M) -> LockingIterator<'l, M, O> { + LockingIterator { + key: self.key, + iterator: f(self.iterator), + outer: self.outer, + } + } + + /// Exit out of the current scope of the locking iterator into the parent. + /// + /// After using one the recurse methods, it is possible to regain access to + /// the parent by exiting out of the scope of the child. Doing this will make + /// it impossible to re-enter this scope again. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = ([Mutex::new(1), Mutex::new(2), Mutex::new(3)], Mutex::new(true)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// let mut iter = tuple.recurse_0_iter(); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// let tuple = iter.exit(); + /// let (should_assert, _) = tuple.lock_1(); + /// if **should_assert { + /// assert_eq!(sum, 6); + /// } + /// ``` + pub fn exit(self) -> O { + self.outer + } +} + +impl<'c, L: Iterator, I: IntoIterator, O> LockingIterator<'c, L, O> { + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + /// [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sums = Vec::new(); + /// while let Some(mut list) = iter.recurse_next() { + /// let mut sum = 0; + /// while let Some(item) = list.lock_next() { + /// sum += **item; + /// } + /// sums.push(sum); + /// iter = list.exit(); + /// } + /// + /// assert_eq!(sums, vec![6, 15]); + /// ``` + pub fn recurse_next( + mut self, + ) -> Option::IntoIter, Self>> { + if let Some(iterator) = self.iterator.next() { + Some(LockingIterator { + key: self.key, + iterator: iterator.into_iter(), + outer: self, + }) + } else { + None + } + } + + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + /// [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let mut list = iter.recurse_last().unwrap(); + /// let mut sum = 0; + /// while let Some(item) = list.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 15); + /// ``` + pub fn recurse_last(self) -> Option::IntoIter, O>> { + if let Some(iterator) = self.iterator.last() { + Some(LockingIterator { + key: self.key, + iterator: iterator.into_iter(), + outer: self.outer, + }) + } else { + None + } + } +} + +impl<'c, L: Iterator, T: 'c, O> LockingIterator<'c, L, O> { + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// (Mutex::new(true), Mutex::new(1)), + /// (Mutex::new(false), Mutex::new(2)), + /// (Mutex::new(true), Mutex::new(3)), + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(tuple) = iter.recurse_next_tuple() { + /// let (should_count, mut tuple) = tuple.lock_0(); + /// if **should_count { + /// let num = tuple.lock_mut_1(); + /// sum += **num; + /// } + /// iter = tuple.exit(); + /// } + /// + /// assert_eq!(sum, 4); + /// ``` + pub fn recurse_next_tuple(mut self) -> Option> { + if let Some(tuple) = self.iterator.next() { + Some(LockingTuple { + key: self.key, + _lockable: PhantomData, + tuple, + outer: self, + }) + } else { + None + } + } + + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// (Mutex::new(true), Mutex::new(1)), + /// (Mutex::new(false), Mutex::new(2)), + /// (Mutex::new(true), Mutex::new(3)), + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let tuple = iter.recurse_last_tuple().unwrap(); + /// let (should_count, mut tuple) = tuple.lock_0(); + /// if **should_count { + /// let num = tuple.lock_mut_1(); + /// assert_eq!(**num, 3); + /// } else { + /// panic!(); + /// } + /// ``` + pub fn recurse_last_tuple(self) -> Option> { + if let Some(tuple) = self.iterator.last() { + Some(LockingTuple { + key: self.key, + _lockable: PhantomData, + tuple, + outer: self.outer, + }) + } else { + None + } + } +} + +impl<'c, L: 'c + Iterator, I: 'c + RawLock + Lockable, O> LockingIterator<'c, L, O> { + /// Advances the iterator, locking the next element and returning a guard to + /// the inner data. + /// + /// Returns `None` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling `next()` + /// again may or may not eventually start returning `Some(Item)` again at some + /// point. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn lock_next(&mut self) -> Option::Guard<'c>, ThreadKey>> { + if let Some(lock) = self.iterator.next() { + unsafe { + lock.raw_write(); + let guard = lock.guard(); + + Some(ContextGuard { + _key: self.key, + guard, + }) + } + } else { + None + } + } + + /// Consumes the iterator, returning the last element, without locking any + /// other elements. + /// + /// This method will evaluate the iterator until it returns `None`. While + /// doing so, it keeps track of the current element. After `None` is returned, + /// `lock_last()` will then lock the last element it saw and return the + /// lock's data. + /// + /// # Panics + /// + /// This function might panic if the iterator is infinite. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let last = iter.lock_last().unwrap(); + /// assert_eq!(**last, 8); + /// ``` + pub fn lock_last(self) -> Option::Guard<'c>, ThreadKey>> { + self.iterator.last().map(|lock| unsafe { + lock.raw_write(); + let guard = lock.guard(); + + ContextGuard { + _key: self.key, + guard, + } + }) + } +} + +impl<'c, L: 'c + Iterator, I: 'c + RawLock + Lockable, O> + LockingIterator<'c, Peekable, O> +{ + /// Attempts to lock the next element and returning a guard to + /// the inner data. + /// + /// # Errors + /// + /// Returns `Err(TryLockNextError::FinishedIteration)` when iteration is + /// finished. Individual iterator implementations may choose to resume + /// iteration, and so calling `next()` again may or may not eventually start + /// returning `Some(Item)` again at some point. + /// + /// Returns `Err(TryLockNextError::WouldBlock)` if the next lock in the + /// iterator is already locked. This will not advance the iterator. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// use happylock::context::iterator::TryLockNextError; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).peekable(); + /// + /// let mut sum = 0; + /// loop { + /// match iter.try_lock_next() { + /// Ok(item) => sum += **item, + /// Err(TryLockNextError::WouldBlock) => continue, + /// Err(TryLockNextError::FinishedIteration) => break, + /// } + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn try_lock_next( + &mut self, + ) -> Result::Guard<'c>, ThreadKey>, TryLockNextError> { + if let Some(lock) = self.iterator.peek().copied() { + unsafe { + if lock.raw_try_write() { + // safety: we just saw that there is a valid value + let lock = self.iterator.next().unwrap_unchecked(); + let guard = lock.guard(); + + Ok(ContextGuard { + _key: self.key, + guard, + }) + } else { + Err(TryLockNextError::WouldBlock) + } + } + } else { + Err(TryLockNextError::FinishedIteration) + } + } +} + +impl<'c, L: 'c + Iterator, I: 'c + RawLock + Sharable, O> LockingIterator<'c, L, O> { + /// Advances the iterator, acquiring a shared lock to the next element and + /// returning a guard to the inner data. + /// + /// Returns `None` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling `next()` + /// again may or may not eventually start returning `Some(Item)` again at some + /// point. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.read_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn read_next( + &mut self, + ) -> Option::ReadGuard<'c>, ThreadKey>> { + if let Some(lock) = self.iterator.next() { + unsafe { + lock.raw_read(); + let guard = lock.read_guard(); + + Some(ContextGuard { + _key: self.key, + guard, + }) + } + } else { + None + } + } + + /// Consumes the iterator, returning the last element with readonly access, + /// without locking any other elements. + /// + /// This method will evaluate the iterator until it returns `None`. While + /// doing so, it keeps track of the current element. After `None` is returned, + /// `lock_last()` will then lock the last element it saw and return the + /// lock's data. + /// + /// # Panics + /// + /// This function might panic if the iterator is infinite. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let last = iter.read_last().unwrap(); + /// assert_eq!(**last, 8); + /// ``` + pub fn read_last(self) -> Option::ReadGuard<'c>, ThreadKey>> { + self.iterator.last().map(|lock| unsafe { + lock.raw_read(); + let guard = lock.read_guard(); + + ContextGuard { + _key: self.key, + guard, + } + }) + } +} + +impl<'c, L: 'c + Iterator, I: 'c + RawLock + Sharable, O> + LockingIterator<'c, Peekable, O> +{ + /// Attempts to acquire a shared lock the next element and returning a guard + /// to the inner data. + /// + /// # Errors + /// + /// Returns `Err(TryLockNextError::FinishedIteration)` when iteration is + /// finished. Individual iterator implementations may choose to resume + /// iteration, and so calling `next()` again may or may not eventually start + /// returning `Some(Item)` again at some point. + /// + /// Returns `Err(TryLockNextError::WouldBlock)` if the next lock in the + /// iterator is already locked. This will not advance the iterator. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// use happylock::context::iterator::TryLockNextError; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).peekable(); + /// + /// let mut sum = 0; + /// loop { + /// match iter.try_read_next() { + /// Ok(item) => sum += **item, + /// Err(TryLockNextError::WouldBlock) => continue, + /// Err(TryLockNextError::FinishedIteration) => break, + /// } + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn try_read_next( + &mut self, + ) -> Result::ReadGuard<'c>, ThreadKey>, TryLockNextError> { + if let Some(lock) = self.iterator.peek().copied() { + unsafe { + if lock.raw_try_read() { + // safety: we just saw that there is a valid value + let lock = self.iterator.next().unwrap_unchecked(); + let guard = lock.read_guard(); + + Ok(ContextGuard { + _key: self.key, + guard, + }) + } else { + Err(TryLockNextError::WouldBlock) + } + } + } else { + Err(TryLockNextError::FinishedIteration) + } + } +} + +impl<'l, L: Iterator, O> LockingIterator<'l, L, O> { + /// Advances the iterator, without locking the next element in the iterator. + /// + /// Returns `false` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling + /// `skip_next()` again may or may not eventually start returning `true` again + /// at some point. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// iter.skip_next(); + /// assert!(iter.lock_next().is_some_and(|v| **v == 3)); + /// ``` + pub fn skip_next(&mut self) -> bool { + self.iterator.next().is_some() + } + + /// Advances the iterator, skipping `n` elements without locking. + /// + /// See [`Iterator::skip`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// iter.skip_mut(2); + /// assert!(iter.lock_next().is_some_and(|v| **v == 8)); + /// ``` + pub fn skip_mut(&mut self, n: usize) { + for _ in 0..n { + self.iterator.next(); + } + } + + /// Returns the bounds on the remaining length of the iterator. + /// + /// See [`Iterator::size_hint`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// assert_eq!((3, Some(3)), iter.size_hint()); + /// let _ = iter.skip_next(); + /// assert_eq!((2, Some(2)), iter.size_hint()); + /// ``` + #[must_use] + pub fn size_hint(&self) -> (usize, Option) { + self.iterator.size_hint() + } + + /// Creates a new [`LockingIterator`] that skips the first `n` elements. + /// + /// Unlike `skip_next` or `skip_mut`, this method does not modify the iterator + /// in place. Instead, it returns a new iterator which skips the first `n` + /// elements. + /// + /// See [`Iterator::skip`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).skip(2); + /// + /// assert!(iter.lock_next().is_some_and(|v| **v == 3)); + /// assert!(iter.lock_next().is_none()); + /// ``` + #[must_use] + pub fn skip(self, n: usize) -> LockingIterator<'l, Skip, O> { + self.with_iterator(|i| i.skip(n)) + } + + /// Creates a new [`LockingIterator`] that yields only the first `n` elements, + /// or fewer if the iterator ends sooner. + /// + /// See [`Iterator::take`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).take(2); + /// + /// assert!(iter.lock_next().is_some_and(|v| **v == 1)); + /// assert!(iter.lock_next().is_some_and(|v| **v == 2)); + /// assert!(iter.lock_next().is_none()); + /// ``` + #[must_use] + pub fn take(self, n: usize) -> LockingIterator<'l, Take, O> { + self.with_iterator(|i| i.take(n)) + } + + /// Creates a new [`LockingIterator`] that ends after the first `None` + /// + /// See [`Iterator::fuse`] for more information + #[must_use] + pub fn fuse(self) -> LockingIterator<'l, Fuse, O> { + self.with_iterator(Iterator::fuse) + } + + /// Creates a new [`LockingIterator`] which has access to the + /// [`try_lock_next`] and/or [`try_read_next`] methods. + /// + /// See [`Iterator::peekable`] for more information + /// + /// [`try_lock_next`]: `LockingIterator::try_lock_next` + /// [`try_read_next`]: `LockingIterator::try_read_next` + #[must_use] + pub fn peekable(self) -> LockingIterator<'l, Peekable, O> { + self.with_iterator(Iterator::peekable) + } +} diff --git a/src/context/tuple.rs b/src/context/tuple.rs new file mode 100644 index 0000000..9bca72e --- /dev/null +++ b/src/context/tuple.rs @@ -0,0 +1,847 @@ +use std::marker::PhantomData; + +use crate::{ + context::{ContextGuard, LockingIterator, LockingTuple}, + lockable::{Lockable, RawLock, Sharable}, + ThreadKey, +}; + +impl<'c, A, B, O> LockingTuple<'c, A, B, O> { + fn transmute(self) -> LockingTuple<'c, C, B, O> { + LockingTuple { + _lockable: PhantomData, + key: self.key, + tuple: self.tuple, + outer: self.outer, + } + } +} + +macro_rules! lock_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_write(); + ( + ContextGuard { + _key: &$self.key, + guard: $self.tuple.$field.guard(), + }, + $self.transmute(), + ) + } + }; +} + +macro_rules! try_lock_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_write() { + Ok(( + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + }, + $self.transmute(), + )) + } else { + Err($self) + } + } + }; +} + +macro_rules! lock_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_write(); + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + } + } + }; +} + +macro_rules! try_lock_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_write() { + Some(ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + }) + } else { + None + } + } + }; +} + +macro_rules! read_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_read(); + ( + ContextGuard { + _key: &$self.key, + guard: $self.tuple.$field.read_guard(), + }, + $self.transmute(), + ) + } + }; +} + +macro_rules! try_read_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_read() { + Ok(( + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + }, + $self.transmute(), + )) + } else { + Err($self) + } + } + }; +} + +macro_rules! read_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_read(); + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + } + } + }; +} + +macro_rules! try_read_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_read() { + Some(ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + }) + } else { + None + } + } + }; +} + +macro_rules! recurse_impl { + ($self: expr, $field: tt) => { + LockingTuple { + _lockable: PhantomData, + key: $self.key, + tuple: &$self.tuple.$field, + outer: $self.transmute(), + } + }; +} + +macro_rules! recurse_iter_impl { + ($self: expr, $field: tt) => { + LockingIterator { + key: $self.key, + iterator: $self.tuple.$field.into_iter(), + outer: $self.transmute(), + } + }; +} + +type LockReturn<'a, 'context, Guarded, L, C, O> = ( + ContextGuard<'a, ::Guard<'a>, ThreadKey>, + LockingTuple<'context, L, C, O>, +); + +type TryLockReturn<'a, 'context, Guarded, L, C, O, This> = + Result, This>; + +type ReadReturn<'a, 'context, Guarded, L, C, O> = ( + ContextGuard<'a, ::ReadGuard<'a>, ThreadKey>, + LockingTuple<'context, L, C, O>, +); + +type TryReadReturn<'a, 'context, Guarded, L, C, O, This> = + Result, This>; + +type RecurseReturn<'context, Inner, L, C, O> = + LockingTuple<'context, Inner, Inner, LockingTuple<'context, L, C, O>>; + +type RecurseIterReturn<'context, Inner, L, C, O> = LockingIterator< + 'context, + <&'context Inner as IntoIterator>::IntoIter, + LockingTuple<'context, L, C, O>, +>; + +impl LockingTuple<'_, T, C, Outer> { + /// Exit out of the current scope of the locking tuple into the parent. + pub fn exit(self) -> Outer { + self.outer + } +} + +impl<'context, A: RawLock + Lockable, Outer> LockingTuple<'context, (A,), (A,), Outer> { + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((),), (A,), Outer> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_0<'a>(self) -> TryLockReturn<'a, 'context, A, ((),), (A,), Outer, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, Outer> LockingTuple<'context, (A,), (A,), Outer> { + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((),), (A,), Outer> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking, and + /// return a new tuple where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_0<'a>(self) -> TryReadReturn<'a, 'context, A, ((),), (A,), Outer, Self> + where + 'context: 'a, + { + try_read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, O> LockingTuple<'context, (A,), (A,), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((),), (A,), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((),), (A,), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Lockable, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B), (A, B0), O> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_0<'a>(self) -> TryLockReturn<'a, 'context, A, ((), B), (A, B0), O, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((), B), (A, B0), O> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking, and + /// return a new tuple where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_0<'a>(self) -> TryReadReturn<'a, 'context, A, ((), B), (A, B0), O, Self> + where + 'context: 'a, + { + try_read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B), (A, B0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B), (A, B0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A: Lockable + RawLock, B, O> LockingTuple<'context, (A, B), (A, B), O> { + /// Lock the first element, and return the second element as a new tuple. + #[must_use] + pub fn lock_and_recurse<'a>( + self, + ) -> ( + ContextGuard<'a, ::Guard<'a>, ThreadKey>, + LockingTuple<'context, B, B, O>, + ) + where + 'context: 'a, + { + unsafe { + self.tuple.0.raw_write(); + ( + ContextGuard { + _key: self.key, + guard: self.tuple.0.guard(), + }, + LockingTuple { + _lockable: PhantomData, + key: self.key, + tuple: &self.tuple.1, + outer: self.outer, + }, + ) + } + } +} + +impl<'context, A, A0, B: RawLock + Lockable, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Lock the second element, and return a new tuple where the first and second + /// elements are inaccessible. + #[must_use] + pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), ()), (A0, B), O> + where + 'context: 'a, + { + lock_impl!(self, 1) + } + + /// Lock the second element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_1(&mut self) -> ContextGuard<'_, B::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_1<'a>(self) -> TryLockReturn<'a, 'context, B, ((), ()), (A0, B), O, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_1(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B: RawLock + Sharable, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Acquire a shared lock to the second element, and return a new tuple where + /// the second element is inaccessible. + #[must_use] + pub fn read_1<'a>(self) -> ReadReturn<'a, 'context, B, ((), ()), (A0, B), O> + where + 'context: 'a, + { + read_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking, and + /// return a new tuple where the second element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_1<'a>(self) -> TryReadReturn<'a, 'context, B, ((), ()), (A0, B), O, Self> + where + 'context: 'a, + { + try_read_impl!(self, 1) + } + + /// Acquire a shared lock to the second element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_1(&mut self) -> ContextGuard<'_, B::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_1(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), ()), (A0, B), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + pub fn recurse_1_iter(self) -> RecurseIterReturn<'context, B, (A, ()), (A0, B), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A: RawLock + Lockable, B, B0, C, C0, O> + LockingTuple<'context, (A, B, C), (A, B0, C0), O> +{ + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B, C), (A, B0, C0), O> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, B, B0, C, C0, O> + LockingTuple<'context, (A, B, C), (A, B0, C0), O> +{ + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((), B, C), (A, B0, C0), O> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, B, B0, C, C0, O> LockingTuple<'context, (A, B, C), (A, B0, C0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B, C), (A, B0, C0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B, C), (A, B0, C0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A, A0, B: RawLock + Lockable, C, C0, O> + LockingTuple<'context, (A, B, C), (A0, B, C0), O> +{ + /// Lock the second element, and return a new tuple where the first and second + /// elements are inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), (), C), (A0, B, C0), O> + where + 'context: 'a, + { + lock_impl!(self, 1) + } + + /// Lock the second element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_1(&mut self) -> ContextGuard<'_, B::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_1(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B: RawLock + Sharable, C, C0, O> + LockingTuple<'context, (A, B, C), (A0, B, C0), O> +{ + /// Acquire a shared lock to the second element, and return a new tuple where + /// the second element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_1<'a>(self) -> ReadReturn<'a, 'context, B, ((), (), C), (A0, B, C0), O> + where + 'context: 'a, + { + read_impl!(self, 1) + } + + /// Acquire a shared lock to the second element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_1(&mut self) -> ContextGuard<'_, B::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_1(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B, C, C0, O> LockingTuple<'context, (A, B, C), (A0, B, C0), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), (), C), (A0, B, C0), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1_iter(self) -> RecurseIterReturn<'context, B, ((), (), C), (A0, B, C0), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A, A0, B, B0, C: RawLock + Lockable, O> + LockingTuple<'context, (A, B, C), (A0, B0, C), O> +{ + /// Lock the third element, and return a new tuple where all elements are + /// inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_2<'a>(self) -> LockReturn<'a, 'context, C, ((), (), ()), (A0, B0, C), O> + where + 'context: 'a, + { + lock_impl!(self, 2) + } + + /// Lock the third element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_2(&mut self) -> ContextGuard<'_, C::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 2) + } + + /// Attempts to lock the third element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_2(&mut self) -> Option, ThreadKey>> { + try_lock_mut_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C: RawLock + Sharable, O> + LockingTuple<'context, (A, B, C), (A0, B0, C), O> +{ + /// Acquire a shared lock to the third element, and return a new tuple where + /// the third element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_2<'a>(self) -> ReadReturn<'a, 'context, C, ((), (), ()), (A0, B0, C), O> + where + 'context: 'a, + { + read_impl!(self, 2) + } + + /// Acquire a shared lock to the third element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_2(&mut self) -> ContextGuard<'_, C::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 2) + } + + /// Attempts to acquire a shared lock the third element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_2(&mut self) -> Option, ThreadKey>> { + try_read_mut_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C, O> LockingTuple<'context, (A, B, C), (A0, B0, C), O> { + /// Consume the tuple, and return the third element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2(self) -> RecurseReturn<'context, C, ((), (), ()), (A0, B0, C), O> { + recurse_impl!(self, 2) + } + + /// Consume the tuple, and return the third element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2_iter(self) -> RecurseIterReturn<'context, C, ((), (), ()), (A0, B0, C), O> + where + &'context C: IntoIterator, + { + recurse_iter_impl!(self, 2) + } +} + +impl<'context, A, B, B0, C, C0, D, D0, O> LockingTuple<'context, (A, B, C, D), (A, B0, C0, D0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B, C, D), (A, B0, C0, D0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B, C, D), (A, B0, C0, D0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A, A0, B, C, C0, D, D0, O> LockingTuple<'context, (A, B, C, D), (A0, B, C0, D0), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), (), C, D), (A0, B, C0, D0), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1_iter( + self, + ) -> RecurseIterReturn<'context, B, ((), (), C, D), (A0, B, C0, D0), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A, A0, B, B0, C, D, D0, O> LockingTuple<'context, (A, B, C, D), (A0, B0, C, D0), O> { + /// Consume the tuple, and return the third element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2(self) -> RecurseReturn<'context, C, ((), (), (), D), (A0, B0, C, D0), O> { + recurse_impl!(self, 2) + } + + /// Consume the tuple, and return the third element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2_iter( + self, + ) -> RecurseIterReturn<'context, C, ((), (), (), D), (A0, B0, C, D0), O> + where + &'context C: IntoIterator, + { + recurse_iter_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C, C0, D, O> LockingTuple<'context, (A, B, C, D), (A0, B0, C0, D), O> { + /// Consume the tuple, and return the fourth element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_3(self) -> RecurseReturn<'context, D, ((), (), (), ()), (A0, B0, C0, D), O> { + recurse_impl!(self, 3) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_3_iter( + self, + ) -> RecurseIterReturn<'context, D, ((), (), (), ()), (A0, B0, C0, D), O> + where + &'context D: IntoIterator, + { + recurse_iter_impl!(self, 3) + } +} diff --git a/src/iterator.rs b/src/iterator.rs deleted file mode 100644 index 79a2cc5..0000000 --- a/src/iterator.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::marker::PhantomData; - -use crate::ThreadKey; - -mod context; -mod guard; -mod iterator; -mod tuple; - -pub struct LockingIterator<'context, I> { - key: &'context ThreadKey, - iterator: I, -} - -pub struct LockingTuple<'context, L, C> { - _lockable: PhantomData, - key: &'context ThreadKey, - tuple: &'context C, -} - -pub struct LockContext<'l, L> { - key: Option, - lockable: &'l L, -} - -pub struct IteratorGuard<'a, Guard, Key> { - _key: &'a Key, - guard: Guard, -} diff --git a/src/iterator/context.rs b/src/iterator/context.rs deleted file mode 100644 index a5c9123..0000000 --- a/src/iterator/context.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::marker::PhantomData; - -use crate::{ - iterator::{LockContext, LockingIterator, LockingTuple}, - lockable::Lockable, - ThreadKey, -}; - -impl<'l, L> LockContext<'l, L> { - pub(crate) const fn new(lockable: &'l L) -> Self { - Self { - key: None, - lockable, - } - } -} - -impl LockContext<'_, L> { - pub fn tuple(&mut self, key: ThreadKey) -> LockingTuple<'_, L, L> { - unsafe { - self.key = Some(key); - - LockingTuple { - _lockable: PhantomData, - // safety: we just inserted a key - key: self.key.as_ref().unwrap_unchecked(), - tuple: self.lockable, - } - } - } -} - -impl<'l, L> LockContext<'l, L> -where - &'l L: IntoIterator, -{ - #[expect(clippy::iter_not_returning_iterator)] - pub fn iter( - &mut self, - key: ThreadKey, - ) -> LockingIterator<'_, <&'l L as IntoIterator>::IntoIter> { - unsafe { - self.key = Some(key); - - LockingIterator { - // safety: we just inserted a key - key: self.key.as_ref().unwrap_unchecked(), - iterator: self.lockable.into_iter(), - } - } - } -} diff --git a/src/iterator/guard.rs b/src/iterator/guard.rs deleted file mode 100644 index cb220ac..0000000 --- a/src/iterator/guard.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::hash::Hash; -use std::ops::{Deref, DerefMut}; - -use super::IteratorGuard; - -#[mutants::skip] // hashing involves RNG and is hard to test -#[cfg(not(tarpaulin_include))] -impl Hash for IteratorGuard<'_, Guard, Key> { - fn hash(&self, state: &mut H) { - self.guard.hash(state) - } -} - -// No implementations of Eq, PartialEq, PartialOrd, or Ord -// You can't implement both PartialEq and PartialEq -// It's easier to just implement neither and ask users to dereference -// This is less of a problem when using the scoped lock API - -#[mutants::skip] -#[cfg(not(tarpaulin_include))] -impl Debug for IteratorGuard<'_, Guard, Key> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&**self, f) - } -} - -impl Display for IteratorGuard<'_, Guard, Key> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&**self, f) - } -} - -impl Deref for IteratorGuard<'_, Guard, Key> { - type Target = Guard; - - fn deref(&self) -> &Self::Target { - &self.guard - } -} - -impl DerefMut for IteratorGuard<'_, Guard, Key> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.guard - } -} - -impl AsRef for IteratorGuard<'_, Guard, Key> { - fn as_ref(&self) -> &Guard { - &self.guard - } -} - -impl AsMut for IteratorGuard<'_, Guard, Key> { - fn as_mut(&mut self) -> &mut Guard { - &mut self.guard - } -} diff --git a/src/iterator/iterator.rs b/src/iterator/iterator.rs deleted file mode 100644 index 316a276..0000000 --- a/src/iterator/iterator.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::iter::{Enumerate, Fuse, Skip, Take}; - -use super::{IteratorGuard, LockingIterator}; - -use crate::{ - lockable::{Lockable, RawLock, Sharable}, - ThreadKey, -}; - -impl<'l, I> LockingIterator<'l, I> { - fn with_iterator(self, f: impl FnOnce(I) -> M) -> LockingIterator<'l, M> { - LockingIterator { - key: self.key, - iterator: f(self.iterator), - } - } -} - -impl<'c, L: 'c + Iterator, I: 'c + RawLock + Lockable> LockingIterator<'c, L> { - pub fn lock_next( - &mut self, - ) -> Option::Guard<'c>, ThreadKey>> { - if let Some(lock) = self.iterator.next() { - unsafe { - lock.raw_write(); - let guard = lock.guard(); - - Some(IteratorGuard { - _key: self.key, - guard, - }) - } - } else { - None - } - } - - pub fn lock_last(self) -> Option::Guard<'c>, ThreadKey>> { - self.iterator.last().map(|lock| unsafe { - lock.raw_write(); - let guard = lock.guard(); - - IteratorGuard { - _key: self.key, - guard, - } - }) - } -} - -impl<'c, L: 'c + Iterator, I: 'c + RawLock + Sharable> LockingIterator<'c, L> { - pub fn read_next( - &mut self, - ) -> Option::ReadGuard<'c>, ThreadKey>> { - if let Some(lock) = self.iterator.next() { - unsafe { - lock.raw_read(); - let guard = lock.read_guard(); - - Some(IteratorGuard { - _key: self.key, - guard, - }) - } - } else { - None - } - } - - pub fn read_last(self) -> Option::ReadGuard<'c>, ThreadKey>> { - self.iterator.last().map(|lock| unsafe { - lock.raw_read(); - let guard = lock.read_guard(); - - IteratorGuard { - _key: self.key, - guard, - } - }) - } -} - -impl<'l, L: Iterator> LockingIterator<'l, L> { - pub fn skip_next(&mut self) -> Option { - self.iterator.next() - } - - pub fn skip_mut(&mut self, n: usize) { - for _ in 0..n { - self.iterator.next(); - } - } - - #[must_use] - pub fn size_hint(&self) -> (usize, Option) { - self.iterator.size_hint() - } - - #[must_use] - pub fn enumerate(self) -> LockingIterator<'l, Enumerate> { - self.with_iterator(Iterator::enumerate) - } - - #[must_use] - pub fn skip(self, n: usize) -> LockingIterator<'l, Skip> { - self.with_iterator(|i| i.skip(n)) - } - - #[must_use] - pub fn take(self, n: usize) -> LockingIterator<'l, Take> { - self.with_iterator(|i| i.take(n)) - } - - #[must_use] - pub fn fuse(self) -> LockingIterator<'l, Fuse> { - self.with_iterator(Iterator::fuse) - } -} diff --git a/src/iterator/tuple.rs b/src/iterator/tuple.rs deleted file mode 100644 index 855cdc7..0000000 --- a/src/iterator/tuple.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::marker::PhantomData; - -use crate::{ - iterator::{IteratorGuard, LockingTuple}, - lockable::{Lockable, RawLock}, - ThreadKey, -}; - -impl<'c, A, B> LockingTuple<'c, A, B> { - const fn transmute(self) -> LockingTuple<'c, C, B> { - LockingTuple { - _lockable: PhantomData, - key: self.key, - tuple: self.tuple, - } - } -} - -macro_rules! lock_impl { - ($self: expr, $field: tt) => { - unsafe { - $self.tuple.$field.raw_write(); - ( - IteratorGuard { - _key: &$self.key, - guard: $self.tuple.$field.guard(), - }, - $self.transmute(), - ) - } - }; -} - -macro_rules! recurse_impl { - ($self: expr, $field: tt) => { - LockingTuple { - _lockable: PhantomData, - key: $self.key, - tuple: &$self.tuple.$field, - } - }; -} - -type LockReturn<'a, 'context, Guarded, L, C> = ( - IteratorGuard<'a, ::Guard<'a>, ThreadKey>, - LockingTuple<'context, L, C>, -); - -impl<'context, A: RawLock + Lockable> LockingTuple<'context, (A,), (A,)> { - #[must_use] - pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((),), (A,)> - where - 'context: 'a, - { - lock_impl!(self, 0) - } -} - -impl<'context, A> LockingTuple<'context, (A,), (A,)> { - #[must_use] - pub const fn recurse_0(self) -> LockingTuple<'context, A, A> { - recurse_impl!(self, 0) - } -} - -impl<'context, A: RawLock + Lockable, B, B0> LockingTuple<'context, (A, B), (A, B0)> { - #[must_use] - pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B), (A, B0)> - where - 'context: 'a, - { - lock_impl!(self, 0) - } -} - -impl<'context, A, B, B0> LockingTuple<'context, (A, B), (A, B0)> { - #[must_use] - pub const fn recurse_0(self) -> LockingTuple<'context, A, A> { - recurse_impl!(self, 0) - } -} - -impl<'context, A: Lockable + RawLock, B> LockingTuple<'context, (A, B), (A, B)> { - #[must_use] - pub fn lock_and_recurse<'a>( - self, - ) -> ( - IteratorGuard<'a, ::Guard<'a>, ThreadKey>, - LockingTuple<'context, B, B>, - ) - where - 'context: 'a, - { - unsafe { - self.tuple.0.raw_write(); - ( - IteratorGuard { - _key: self.key, - guard: self.tuple.0.guard(), - }, - LockingTuple { - _lockable: PhantomData, - key: self.key, - tuple: &self.tuple.1, - }, - ) - } - } -} - -impl<'context, A, A0, B: RawLock + Lockable> LockingTuple<'context, (A, B), (A0, B)> { - #[must_use] - pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), ()), (A0, B)> - where - 'context: 'a, - { - lock_impl!(self, 1) - } -} - -impl<'context, A, A0, B> LockingTuple<'context, (A, B), (A0, B)> { - #[must_use] - pub const fn recurse_1(self) -> LockingTuple<'context, B, B> { - recurse_impl!(self, 1) - } -} - -impl<'context, A: RawLock + Lockable, B, B0, C, C0> LockingTuple<'context, (A, B, C), (A, B0, C0)> { - #[must_use] - pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B, C), (A, B0, C0)> - where - 'context: 'a, - { - lock_impl!(self, 0) - } -} - -impl<'context, A, B, B0, C, C0> LockingTuple<'context, (A, B, C), (A, B0, C0)> { - #[must_use] - pub const fn recurse_0(self) -> LockingTuple<'context, A, A> { - recurse_impl!(self, 0) - } -} - -impl<'context, A, A0, B: RawLock + Lockable, C, C0> LockingTuple<'context, (A, B, C), (A0, B, C0)> { - #[must_use] - pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), (), C), (A0, B, C0)> - where - 'context: 'a, - { - lock_impl!(self, 1) - } -} - -impl<'context, A, A0, B, C, C0> LockingTuple<'context, (A, B, C), (A0, B, C0)> { - #[must_use] - pub const fn recurse_1(self) -> LockingTuple<'context, B, B> { - recurse_impl!(self, 1) - } -} - -impl<'context, A, A0, B, B0, C: RawLock + Lockable> LockingTuple<'context, (A, B, C), (A0, B0, C)> { - #[must_use] - pub fn lock_2<'a>(self) -> LockReturn<'a, 'context, C, ((), (), ()), (A0, B0, C)> - where - 'context: 'a, - { - lock_impl!(self, 2) - } -} - -impl<'context, A, A0, B, B0, C> LockingTuple<'context, (A, B, C), (A0, B0, C)> { - #[must_use] - pub const fn recurse_2(self) -> LockingTuple<'context, C, C> { - recurse_impl!(self, 2) - } -} - -impl<'context, A, B, B0, C, C0, D, D0> LockingTuple<'context, (A, B, C, D), (A, B0, C0, D0)> { - #[must_use] - pub const fn recurse_0(self) -> LockingTuple<'context, A, A> { - recurse_impl!(self, 0) - } -} -impl<'context, A, A0, B, C, C0, D, D0> LockingTuple<'context, (A, B, C, D), (A0, B, C0, D0)> { - #[must_use] - pub const fn recurse_1(self) -> LockingTuple<'context, B, B> { - recurse_impl!(self, 1) - } -} -impl<'context, A, A0, B, B0, C, D, D0> LockingTuple<'context, (A, B, C, D), (A0, B0, C, D0)> { - #[must_use] - pub const fn recurse_2(self) -> LockingTuple<'context, C, C> { - recurse_impl!(self, 2) - } -} -impl<'context, A, A0, B, B0, C, C0, D> LockingTuple<'context, (A, B, C, D), (A0, B0, C0, D)> { - #[must_use] - pub const fn recurse_3(self) -> LockingTuple<'context, D, D> { - recurse_impl!(self, 3) - } -} diff --git a/src/lib.rs b/src/lib.rs index fc9e495..e6e5e4c 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,7 +198,7 @@ mod handle_unwind; mod key; pub mod collection; -pub mod iterator; +pub mod context; pub mod lockable; pub mod mutex; pub mod poisonable; @@ -219,6 +219,9 @@ pub use mutex::SpinLock; /// [`BoxedLockCollection`]: collection::BoxedLockCollection pub type LockCollection = collection::BoxedLockCollection; +/// A re-export for [`context::LockContext`] +pub type LockContext<'l, L> = context::LockContext<'l, L>; + /// A re-export for [`poisonable::Poisonable`] pub type Poisonable = poisonable::Poisonable; diff --git a/src/lockable.rs b/src/lockable.rs index badbe8e..705887f 100755 --- a/src/lockable.rs +++ b/src/lockable.rs @@ -197,6 +197,12 @@ pub unsafe trait Sharable: Lockable { /// /// There must not be any two values which can unlock the value at the same /// time, i.e., this must either be an owned value or a mutable reference. +/// +/// The implementation of [`Lockable::get_ptrs`] must return the locks in the +/// same order that they would be locked in if this lockable were passed into a +/// [`LockContext`]. +/// +/// [`LockContext`]: `crate::context::LockContext` pub unsafe trait OwnedLockable: Lockable {} /// A trait which indicates that `into_inner` is a valid operation for a diff --git a/src/mutex.rs b/src/mutex.rs index 0d6aa73..d7b0176 100755 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -170,6 +170,8 @@ pub struct MutexRef<'a, T: ?Sized + 'a, R: RawMutex>(&'a Mutex, PhantomDat // // This is the most lifetime-intensive thing I've ever written. Can I graduate // from borrow checker university now? +// +// As an update, I've now written `LockContext`. That was even more challenging pub struct MutexGuard<'a, T: ?Sized + 'a, R: RawMutex> { mutex: MutexRef<'a, T, R>, // this way we don't need to re-implement Drop thread_key: ThreadKey, diff --git a/src/poisonable/poisonable.rs b/src/poisonable/poisonable.rs index dd12cea..0134ff1 100755 --- a/src/poisonable/poisonable.rs +++ b/src/poisonable/poisonable.rs @@ -1,10 +1,11 @@ use std::panic::{RefUnwindSafe, UnwindSafe}; +use crate::collection::OwnedLockCollection; use crate::handle_unwind::handle_unwind; use crate::lockable::{ Lockable, LockableGetMut, LockableIntoInner, OwnedLockable, RawLock, Sharable, }; -use crate::{Keyable, ThreadKey}; +use crate::{Keyable, LockContext, ThreadKey}; use super::{ PoisonError, PoisonFlag, PoisonGuard, PoisonRef, PoisonResult, Poisonable, @@ -853,5 +854,24 @@ impl Poisonable { } } +impl Poisonable> { + /// Creates a context that can be used to iterate over the items in order. + /// + /// For more information, see [`OwnedLockCollection::context`]. + /// + /// # Errors + /// + /// If another user of this lock panicked while holding the lock, then + /// this call will return an error instead. A `Poisonable` is poisoned + /// whenever a thread panics while holding a lock. + pub fn context(&self) -> PoisonResult> { + if self.is_poisoned() { + Ok(self.inner.context()) + } else { + Err(PoisonError::new(self.inner.context())) + } + } +} + impl RefUnwindSafe for Poisonable {} impl UnwindSafe for Poisonable {} diff --git a/tarpaulin-report.html b/tarpaulin-report.html index bac34c6..1525bec 100755 --- a/tarpaulin-report.html +++ b/tarpaulin-report.html @@ -2,9 +2,36 @@ -
\ No newline at end of file -- cgit v1.3.1