summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/collection/boxed.rs2
-rwxr-xr-xsrc/collection/owned.rs40
-rw-r--r--src/context.rs617
-rw-r--r--src/context/context.rs158
-rw-r--r--src/context/guard.rs (renamed from src/iterator/guard.rs)16
-rw-r--r--src/context/iterator.rs695
-rw-r--r--src/context/tuple.rs847
-rw-r--r--src/iterator.rs29
-rw-r--r--src/iterator/context.rs52
-rw-r--r--src/iterator/iterator.rs118
-rw-r--r--src/iterator/tuple.rs202
-rwxr-xr-xsrc/lib.rs5
-rwxr-xr-xsrc/lockable.rs6
-rwxr-xr-xsrc/mutex.rs2
-rwxr-xr-xsrc/poisonable/poisonable.rs22
15 files changed, 2394 insertions, 417 deletions
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<L: Sharable> BoxedLockCollection<L> {
}
}
+impl<L: OwnedLockable> BoxedLockCollection<L> {}
+
impl<L: LockableIntoInner> BoxedLockCollection<L> {
/// 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<L: Lockable> Lockable for OwnedLockCollection<L> {
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<L: OwnedLockable> OwnedLockCollection<L> {
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<L: Sharable> OwnedLockCollection<L> {
}
impl<L> OwnedLockCollection<L> {
- 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<ThreadKey>,
+ 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<L>,
+ 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<i32>; 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<bool>, Mutex<i32>); 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<ThreadKey> {
+ self.key.take()
+ }
+}
+
+impl<L: Lockable> 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/iterator/guard.rs b/src/context/guard.rs
index cb220ac..0898c1f 100644
--- a/src/iterator/guard.rs
+++ b/src/context/guard.rs
@@ -2,11 +2,11 @@ use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
-use super::IteratorGuard;
+use super::ContextGuard;
#[mutants::skip] // hashing involves RNG and is hard to test
#[cfg(not(tarpaulin_include))]
-impl<Guard: Hash, Key> Hash for IteratorGuard<'_, Guard, Key> {
+impl<Guard: Hash, Key> Hash for ContextGuard<'_, Guard, Key> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.guard.hash(state)
}
@@ -19,19 +19,19 @@ impl<Guard: Hash, Key> Hash for IteratorGuard<'_, Guard, Key> {
#[mutants::skip]
#[cfg(not(tarpaulin_include))]
-impl<Guard: Debug, Key> Debug for IteratorGuard<'_, Guard, Key> {
+impl<Guard: Debug, Key> Debug for ContextGuard<'_, Guard, Key> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&**self, f)
}
}
-impl<Guard: Display, Key> Display for IteratorGuard<'_, Guard, Key> {
+impl<Guard: Display, Key> Display for ContextGuard<'_, Guard, Key> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&**self, f)
}
}
-impl<Guard, Key> Deref for IteratorGuard<'_, Guard, Key> {
+impl<Guard, Key> Deref for ContextGuard<'_, Guard, Key> {
type Target = Guard;
fn deref(&self) -> &Self::Target {
@@ -39,19 +39,19 @@ impl<Guard, Key> Deref for IteratorGuard<'_, Guard, Key> {
}
}
-impl<Guard, Key> DerefMut for IteratorGuard<'_, Guard, Key> {
+impl<Guard, Key> DerefMut for ContextGuard<'_, Guard, Key> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}
-impl<Guard, Key> AsRef<Guard> for IteratorGuard<'_, Guard, Key> {
+impl<Guard, Key> AsRef<Guard> for ContextGuard<'_, Guard, Key> {
fn as_ref(&self) -> &Guard {
&self.guard
}
}
-impl<Guard, Key> AsMut<Guard> for IteratorGuard<'_, Guard, Key> {
+impl<Guard, Key> AsMut<Guard> 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<M>(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<Item = I>, 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<LockingIterator<'c, <I as IntoIterator>::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<LockingIterator<'c, <I as IntoIterator>::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<Item = &'c T>, 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<LockingTuple<'c, T, T, Self>> {
+ 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<LockingTuple<'c, T, T, O>> {
+ 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<Item = &'c I>, 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<ContextGuard<'c, <I as Lockable>::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<ContextGuard<'c, <I as Lockable>::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<Item = &'c I>, I: 'c + RawLock + Lockable, O>
+ LockingIterator<'c, Peekable<L>, 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<ContextGuard<'c, <I as Lockable>::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<Item = &'c I>, 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<ContextGuard<'c, <I as Sharable>::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<ContextGuard<'c, <I as Sharable>::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<Item = &'c I>, I: 'c + RawLock + Sharable, O>
+ LockingIterator<'c, Peekable<L>, 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<ContextGuard<'c, <I as Sharable>::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<usize>) {
+ 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<L>, 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<L>, 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<L>, 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<L>, 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<C>(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, <Guarded as Lockable>::Guard<'a>, ThreadKey>,
+ LockingTuple<'context, L, C, O>,
+);
+
+type TryLockReturn<'a, 'context, Guarded, L, C, O, This> =
+ Result<LockReturn<'a, 'context, Guarded, L, C, O>, This>;
+
+type ReadReturn<'a, 'context, Guarded, L, C, O> = (
+ ContextGuard<'a, <Guarded as Sharable>::ReadGuard<'a>, ThreadKey>,
+ LockingTuple<'context, L, C, O>,
+);
+
+type TryReadReturn<'a, 'context, Guarded, L, C, O, This> =
+ Result<ReadReturn<'a, 'context, Guarded, L, C, O>, 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<T, C, Outer> 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<ContextGuard<'_, A::Guard<'_>, 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<ContextGuard<'_, A::ReadGuard<'_>, 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<ContextGuard<'_, A::Guard<'_>, 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<ContextGuard<'_, A::ReadGuard<'_>, 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, <A as Lockable>::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<ContextGuard<'_, B::Guard<'_>, 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<ContextGuard<'_, B::ReadGuard<'_>, 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<ContextGuard<'_, A::Guard<'_>, 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<ContextGuard<'_, A::ReadGuard<'_>, 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<ContextGuard<'_, B::Guard<'_>, 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<ContextGuard<'_, B::ReadGuard<'_>, 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<ContextGuard<'_, C::Guard<'_>, 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<ContextGuard<'_, C::ReadGuard<'_>, 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<L>,
- key: &'context ThreadKey,
- tuple: &'context C,
-}
-
-pub struct LockContext<'l, L> {
- key: Option<ThreadKey>,
- 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<L: Lockable> 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/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<M>(self, f: impl FnOnce(I) -> M) -> LockingIterator<'l, M> {
- LockingIterator {
- key: self.key,
- iterator: f(self.iterator),
- }
- }
-}
-
-impl<'c, L: 'c + Iterator<Item = &'c I>, I: 'c + RawLock + Lockable> LockingIterator<'c, L> {
- pub fn lock_next(
- &mut self,
- ) -> Option<IteratorGuard<'c, <I as Lockable>::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<IteratorGuard<'c, <I as Lockable>::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<Item = &'c I>, I: 'c + RawLock + Sharable> LockingIterator<'c, L> {
- pub fn read_next(
- &mut self,
- ) -> Option<IteratorGuard<'c, <I as Sharable>::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<IteratorGuard<'c, <I as Sharable>::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<L::Item> {
- 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<usize>) {
- self.iterator.size_hint()
- }
-
- #[must_use]
- pub fn enumerate(self) -> LockingIterator<'l, Enumerate<L>> {
- self.with_iterator(Iterator::enumerate)
- }
-
- #[must_use]
- pub fn skip(self, n: usize) -> LockingIterator<'l, Skip<L>> {
- self.with_iterator(|i| i.skip(n))
- }
-
- #[must_use]
- pub fn take(self, n: usize) -> LockingIterator<'l, Take<L>> {
- self.with_iterator(|i| i.take(n))
- }
-
- #[must_use]
- pub fn fuse(self) -> LockingIterator<'l, Fuse<L>> {
- 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<C>(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, <Guarded as Lockable>::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, <A as Lockable>::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<L> = collection::BoxedLockCollection<L>;
+/// A re-export for [`context::LockContext`]
+pub type LockContext<'l, L> = context::LockContext<'l, L>;
+
/// A re-export for [`poisonable::Poisonable`]
pub type Poisonable<L> = poisonable::Poisonable<L>;
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<T, R>, 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<L: LockableGetMut + RawLock> Poisonable<L> {
}
}
+impl<L: OwnedLockable> Poisonable<OwnedLockCollection<L>> {
+ /// 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<LockContext<'_, L>> {
+ if self.is_poisoned() {
+ Ok(self.inner.context())
+ } else {
+ Err(PoisonError::new(self.inner.context()))
+ }
+ }
+}
+
impl<L: UnwindSafe> RefUnwindSafe for Poisonable<L> {}
impl<L: UnwindSafe> UnwindSafe for Poisonable<L> {}