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 // This is only available from OwnedLockCollection because it ensures the lock // order is the same as the one provided by this function. Tuples may have // their elements re-ordered by the compiler. // // This struct is just a fancy place to hold the ThreadKey while it's being // borrowed by multiple other guards. pub struct LockContext<'l, L> { // Unfortunately there is no better way to get this into the context than to // use an option, initialize to `None` and insert it later. If we returned a // new type, that would move the thread key while it's being borrowed. 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> { // The idea behind having this be a shared reference is that it allows // guards to exist at once. And while these borrows exist, the thread key will // not be able to be reused somewhere else. _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 ); } }