summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/collection.rs125
-rw-r--r--src/collection/boxed.rs510
-rw-r--r--src/collection/collection.rs291
-rw-r--r--src/collection/guard.rs33
-rw-r--r--src/collection/owned.rs347
-rw-r--r--src/collection/ref.rs399
-rw-r--r--src/collection/retry.rs619
-rw-r--r--src/collection/utils.rs44
-rw-r--r--src/key.rs9
-rw-r--r--src/lib.rs120
-rw-r--r--src/lockable.rs877
-rw-r--r--src/mutex.rs29
-rw-r--r--src/mutex/guard.rs75
-rw-r--r--src/mutex/mutex.rs38
-rw-r--r--src/rwlock.rs8
-rw-r--r--src/rwlock/read_guard.rs52
-rw-r--r--src/rwlock/read_lock.rs22
-rw-r--r--src/rwlock/rwlock.rs79
-rw-r--r--src/rwlock/write_guard.rs37
-rw-r--r--src/rwlock/write_lock.rs35
20 files changed, 2740 insertions, 1009 deletions
diff --git a/src/collection.rs b/src/collection.rs
index c6cbe2d..27ec1c4 100644
--- a/src/collection.rs
+++ b/src/collection.rs
@@ -1,23 +1,130 @@
use std::marker::PhantomData;
-use crate::{key::Keyable, lockable::Lockable};
+use crate::{key::Keyable, lockable::RawLock};
-mod collection;
+mod boxed;
mod guard;
+mod owned;
+mod r#ref;
+mod retry;
+mod utils;
-/// A type which can be locked.
+/// Locks a collection of locks, which cannot be shared immutably.
///
/// This could be a tuple of [`Lockable`] types, an array, or a `Vec`. But it
-/// can be safely locked without causing a deadlock. To do this, it is very
-/// important that no duplicate locks are included within.
-#[derive(Debug, Clone, Copy)]
-pub struct LockCollection<L> {
+/// can be safely locked without causing a deadlock.
+///
+/// The data in this collection is guaranteed to not contain duplicates because
+/// `L` must always implement [`OwnedLockable`]. The underlying data may not be
+/// immutably referenced and locked. Because of this, there is no need for
+/// sorting the locks in the collection, or checking for duplicates, because it
+/// can be guaranteed that until the underlying collection is mutated (which
+/// requires releasing all acquired locks in the collection to do), then the
+/// locks will stay in the same order and be locked in that order, preventing
+/// cyclic wait.
+///
+/// [`Lockable`]: `crate::lockable::Lockable`
+/// [`OwnedLockable`]: `crate::lockable::OwnedLockable`
+
+// this type caches the idea that no immutable references to the underlying
+// collection exist
+#[derive(Debug)]
+pub struct OwnedLockCollection<L> {
+ data: L,
+}
+
+/// Locks a reference to a collection of locks, by sorting them by memory
+/// address.
+///
+/// This could be a tuple of [`Lockable`] types, an array, or a `Vec`. But it
+/// can be safely locked without causing a deadlock.
+///
+/// Upon construction, it must be confirmed that the collection contains no
+/// duplicate locks. This can be done by either using [`OwnedLockable`] or by
+/// checking. Regardless of how this is done, the locks will be sorted by their
+/// memory address before locking them. The sorted order of the locks is stored
+/// within this collection.
+///
+/// Unlike [`BoxedLockCollection`], this type does not allocate memory for the
+/// data, although it does allocate memory for the sorted list of lock
+/// references. This makes it slightly faster, but lifetimes must be handled.
+///
+/// [`Lockable`]: `crate::lockable::Lockable`
+/// [`OwnedLockable`]: `crate::lockable::OwnedLockable`
+
+// This type was born when I eventually realized that I needed a self
+// referential structure. That used boxing, so I elected to make a more
+// efficient implementation (polonius please save us)
+
+// This type caches the sorting order of the locks and the fact that it doesn't
+// contain any duplicates.
+pub struct RefLockCollection<'a, L> {
+ data: &'a L,
+ locks: Vec<&'a dyn RawLock>,
+}
+
+/// Locks a collection of locks, stored in the heap, by sorting them by memory
+/// address.
+///
+/// This could be a tuple of [`Lockable`] types, an array, or a `Vec`. But it
+/// can be safely locked without causing a deadlock.
+///
+/// Upon construction, it must be confirmed that the collection contains no
+/// duplicate locks. This can be done by either using [`OwnedLockable`] or by
+/// checking. Regardless of how this is done, the locks will be sorted by their
+/// memory address before locking them. The sorted order of the locks is stored
+/// within this collection.
+///
+/// Unlike [`RefLockCollection`], this is a self-referential type which boxes
+/// the data that is given to it. This means no lifetimes are necessary on the
+/// type itself, but it is slightly slower because of the memory allocation.
+///
+/// [`Lockable`]: `crate::lockable::Lockable`
+/// [`OwnedLockable`]: `crate::lockable::OwnedLockable`
+
+// This type caches the sorting order of the locks and the fact that it doesn't
+// contain any duplicates.
+pub struct BoxedLockCollection<L> {
+ data: Box<L>,
+ locks: Vec<&'static dyn RawLock>, // As far as you know, it's static.
+ // Believe it or not, saying the lifetime
+ // is static when it's not isn't UB
+}
+
+/// Locks a collection of locks using a retrying algorithm.
+///
+/// This could be a tuple of [`Lockable`] types, an array, or a `Vec`. But it
+/// can be safely locked without causing a deadlock.
+///
+/// The data in this collection is guaranteed to not contain duplicates, but it
+/// also not be sorted. In some cases the lack of sorting can increase
+/// performance. However, in most cases, this collection will be slower. Cyclic
+/// wait is not guaranteed here, so the locking algorithm must release all its
+/// locks if one of the lock attempts blocks. This results in wasted time and
+/// potential [livelocking].
+///
+/// However, one case where this might be faster than [`RefLockCollection`] is
+/// when the first lock in the collection is always the first in any
+/// collection, and the other locks in the collection are always locked after
+/// that first lock is acquired. This means that as soon as it is locked, there
+/// will be no need to unlock it later on subsequent lock attempts, because
+/// they will always succeed.
+///
+/// [`Lockable`]: `crate::lockable::Lockable`
+/// [`OwnedLockable`]: `crate::lockable::OwnedLockable`
+/// [livelocking]: https://en.wikipedia.org/wiki/Deadlock#Livelock
+
+// This type caches the fact that there are no duplicates
+#[derive(Debug)]
+pub struct RetryingLockCollection<L> {
data: L,
}
/// A RAII guard for a generic [`Lockable`] type.
-pub struct LockGuard<'a, 'key: 'a, L: Lockable<'a>, Key: Keyable + 'key> {
- guard: L::Output,
+///
+/// [`Lockable`]: `crate::lockable::Lockable`
+pub struct LockGuard<'key, Guard, Key: Keyable + 'key> {
+ guard: Guard,
key: Key,
_phantom: PhantomData<&'key ()>,
}
diff --git a/src/collection/boxed.rs b/src/collection/boxed.rs
new file mode 100644
index 0000000..5ced6d1
--- /dev/null
+++ b/src/collection/boxed.rs
@@ -0,0 +1,510 @@
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+use crate::lockable::{Lockable, OwnedLockable, RawLock, Sharable};
+use crate::Keyable;
+
+use super::{utils, BoxedLockCollection, LockGuard};
+
+/// returns `true` if the sorted list contains a duplicate
+#[must_use]
+fn contains_duplicates(l: &[&dyn RawLock]) -> bool {
+ l.windows(2)
+ .any(|window| std::ptr::eq(window[0], window[1]))
+}
+
+unsafe impl<L: Lockable> Lockable for BoxedLockCollection<L> {
+ type Guard<'g> = L::Guard<'g> where Self: 'g;
+
+ type ReadGuard<'g> = L::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ self.data.get_ptrs(ptrs)
+ }
+
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ self.data.guard()
+ }
+
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ self.data.read_guard()
+ }
+}
+
+unsafe impl<L: Sharable> Sharable for BoxedLockCollection<L> {}
+
+unsafe impl<L: OwnedLockable> OwnedLockable for BoxedLockCollection<L> {}
+
+impl<L> IntoIterator for BoxedLockCollection<L>
+where
+ L: IntoIterator,
+{
+ type Item = <L as IntoIterator>::Item;
+ type IntoIter = <L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<'a, L> IntoIterator for &'a BoxedLockCollection<L>
+where
+ &'a L: IntoIterator,
+{
+ type Item = <&'a L as IntoIterator>::Item;
+ type IntoIter = <&'a L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<'a, L> IntoIterator for &'a mut BoxedLockCollection<L>
+where
+ &'a mut L: IntoIterator,
+{
+ type Item = <&'a mut L as IntoIterator>::Item;
+ type IntoIter = <&'a mut L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<L: OwnedLockable, I: FromIterator<L> + OwnedLockable> FromIterator<L>
+ for BoxedLockCollection<I>
+{
+ fn from_iter<T: IntoIterator<Item = L>>(iter: T) -> Self {
+ let iter: I = iter.into_iter().collect();
+ Self::new(iter)
+ }
+}
+
+impl<E: OwnedLockable + Extend<L>, L: OwnedLockable> Extend<L> for BoxedLockCollection<E> {
+ fn extend<T: IntoIterator<Item = L>>(&mut self, iter: T) {
+ self.data.extend(iter)
+ }
+}
+
+impl<L> AsRef<L> for BoxedLockCollection<L> {
+ fn as_ref(&self) -> &L {
+ &self.data
+ }
+}
+
+impl<L> AsMut<L> for BoxedLockCollection<L> {
+ fn as_mut(&mut self) -> &mut L {
+ &mut self.data
+ }
+}
+
+impl<L: Debug> Debug for BoxedLockCollection<L> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct(stringify!(BoxedLockCollection))
+ .field("data", &self.data)
+ .finish_non_exhaustive()
+ }
+}
+
+impl<L: OwnedLockable + Default> Default for BoxedLockCollection<L> {
+ fn default() -> Self {
+ Self::new(L::default())
+ }
+}
+
+impl<L: OwnedLockable + Default> From<L> for BoxedLockCollection<L> {
+ fn from(value: L) -> Self {
+ Self::new(value)
+ }
+}
+
+impl<L: OwnedLockable> BoxedLockCollection<L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, LockCollection};
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = LockCollection::new(data);
+ /// ```
+ #[must_use]
+ pub fn new(data: L) -> Self {
+ // safety: owned lockable types cannot contain duplicates
+ unsafe { Self::new_unchecked(data) }
+ }
+}
+
+impl<'a, L: OwnedLockable> BoxedLockCollection<&'a L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, LockCollection};
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = LockCollection::new_ref(&data);
+ /// ```
+ #[must_use]
+ pub fn new_ref(data: &'a L) -> Self {
+ // safety: owned lockable types cannot contain duplicates
+ unsafe { Self::new_unchecked(data) }
+ }
+}
+
+impl<L: Lockable> BoxedLockCollection<L> {
+ /// Creates a new collections of locks.
+ ///
+ /// # Safety
+ ///
+ /// This results in undefined behavior if any locks are presented twice
+ /// within this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, LockCollection};
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // safety: data1 and data2 refer to distinct mutexes
+ /// let data = (&data1, &data2);
+ /// let lock = unsafe { LockCollection::new_unchecked(&data) };
+ /// ```
+ #[must_use]
+ pub unsafe fn new_unchecked(data: L) -> Self {
+ let data = Box::new(data);
+ let mut locks = Vec::new();
+ data.get_ptrs(&mut locks);
+
+ // cast to *const () because fat pointers can't be converted to usize
+ locks.sort_by_key(|lock| std::ptr::from_ref(*lock).cast::<()>() as usize);
+
+ // safety: the box will be dropped after the lock references, so it's
+ // safe to just pretend they're static
+ let locks = std::mem::transmute(locks);
+ Self { data, locks }
+ }
+
+ /// Creates a new collection of locks.
+ ///
+ /// This returns `None` if any locks are found twice in the given
+ /// collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, LockCollection};
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // data1 and data2 refer to distinct mutexes, so this won't panic
+ /// let data = (&data1, &data2);
+ /// let lock = LockCollection::try_new(&data).unwrap();
+ /// ```
+ #[must_use]
+ pub fn try_new(data: L) -> Option<Self> {
+ // safety: we are checking for duplicates before returning
+ unsafe {
+ let this = Self::new_unchecked(data);
+ if contains_duplicates(&this.locks) {
+ return None;
+ }
+ Some(this)
+ }
+ }
+
+ /// Gets the underlying collection, consuming this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let data1 = Mutex::new(42);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // data1 and data2 refer to distinct mutexes, so this won't panic
+ /// let data = (&data1, &data2);
+ /// let lock = LockCollection::try_new(&data).unwrap();
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let guard = lock.into_inner().0.lock(key);
+ /// assert_eq!(*guard, 42);
+ /// ```
+ #[must_use]
+ pub fn into_inner(self) -> Box<L> {
+ self.data
+ }
+
+ /// Locks the collection
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data. When the guard is dropped, the locks in the collection are also
+ /// dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// ```
+ pub fn lock<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::Guard<'g>, Key> {
+ for lock in &self.locks {
+ // safety: we have the thread key
+ unsafe { lock.lock() };
+ }
+
+ LockGuard {
+ // safety: we've already acquired the lock
+ guard: unsafe { self.data.guard() },
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data, and unlocks the data when it is dropped. Otherwise, `None` is
+ /// returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// match lock.try_lock(key) {
+ /// Some(mut guard) => {
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::Guard<'g>, Key>> {
+ let guard = unsafe {
+ if !utils::ordered_try_lock(&self.locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// let key = LockCollection::<(Mutex<i32>, Mutex<&str>)>::unlock(guard);
+ /// ```
+ pub fn unlock<'key, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'_>, Key>) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<L: Sharable> BoxedLockCollection<L> {
+ /// Locks the collection, so that other threads can still read from it
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data immutably. When the guard is dropped, the locks in the collection
+ /// are also dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// assert_eq!(*guard.0, 0);
+ /// assert_eq!(*guard.1, "");
+ /// ```
+ pub fn read<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::ReadGuard<'g>, Key> {
+ for lock in &self.locks {
+ // safety: we have the thread key
+ unsafe { lock.read() };
+ }
+
+ LockGuard {
+ // safety: we've already acquired the lock
+ guard: unsafe { self.data.read_guard() },
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking, in such a way that other threads
+ /// can still read from the collection.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data immutably, and unlocks the data when it is dropped. Otherwise,
+ /// `None` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(5), RwLock::new("6"));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// match lock.try_read(key) {
+ /// Some(mut guard) => {
+ /// assert_eq!(*guard.0, 5);
+ /// assert_eq!(*guard.1, "6");
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::ReadGuard<'g>, Key>> {
+ let guard = unsafe {
+ if !utils::ordered_try_read(&self.locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.read_guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = LockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// let key = LockCollection::<(RwLock<i32>, RwLock<&str>)>::unlock_read(guard);
+ /// ```
+ pub fn unlock_read<'key, Key: Keyable + 'key>(
+ guard: LockGuard<'key, L::ReadGuard<'_>, Key>,
+ ) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<'a, L: 'a> BoxedLockCollection<L>
+where
+ &'a L: IntoIterator,
+{
+ /// Returns an iterator over references to each value in the collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = [Mutex::new(26), Mutex::new(1)];
+ /// let lock = LockCollection::new(data);
+ ///
+ /// let mut iter = lock.iter();
+ /// let mutex = iter.next().unwrap();
+ /// let guard = mutex.lock(key);
+ ///
+ /// assert_eq!(*guard, 26);
+ /// ```
+ #[must_use]
+ pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter {
+ self.into_iter()
+ }
+}
+
+impl<'a, L: 'a> BoxedLockCollection<L>
+where
+ &'a mut L: IntoIterator,
+{
+ /// Returns an iterator over mutable references to each value in the
+ /// collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey, LockCollection};
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = [Mutex::new(26), Mutex::new(1)];
+ /// let mut lock = LockCollection::new(data);
+ ///
+ /// let mut iter = lock.iter_mut();
+ /// let mutex = iter.next().unwrap();
+ ///
+ /// assert_eq!(*mutex.as_mut(), 26);
+ /// ```
+ #[must_use]
+ pub fn iter_mut(&'a mut self) -> <&'a mut L as IntoIterator>::IntoIter {
+ self.into_iter()
+ }
+}
diff --git a/src/collection/collection.rs b/src/collection/collection.rs
deleted file mode 100644
index 22a2d11..0000000
--- a/src/collection/collection.rs
+++ /dev/null
@@ -1,291 +0,0 @@
-use std::marker::PhantomData;
-
-use crate::{key::Keyable, Lockable, OwnedLockable};
-
-use super::{LockCollection, LockGuard};
-
-/// returns `true` if the list contains a duplicate
-#[must_use]
-fn contains_duplicates(l: &mut [usize]) -> bool {
- l.sort_unstable();
- l.windows(2).any(|w| w[0] == w[1])
-}
-
-impl<'a, L: OwnedLockable<'a>> From<L> for LockCollection<L> {
- fn from(value: L) -> Self {
- Self::new(value)
- }
-}
-
-impl<'a, L: Lockable<'a>> AsRef<L> for LockCollection<L> {
- fn as_ref(&self) -> &L {
- &self.data
- }
-}
-
-impl<'a, L: Lockable<'a>> AsMut<L> for LockCollection<L> {
- fn as_mut(&mut self) -> &mut L {
- &mut self.data
- }
-}
-
-impl<'a, L: Lockable<'a>> AsRef<Self> for LockCollection<L> {
- fn as_ref(&self) -> &Self {
- self
- }
-}
-
-impl<'a, L: Lockable<'a>> AsMut<Self> for LockCollection<L> {
- fn as_mut(&mut self) -> &mut Self {
- self
- }
-}
-
-impl<L: IntoIterator> IntoIterator for LockCollection<L> {
- type Item = L::Item;
- type IntoIter = L::IntoIter;
-
- fn into_iter(self) -> Self::IntoIter {
- self.data.into_iter()
- }
-}
-
-impl<'a, L> IntoIterator for &'a LockCollection<L>
-where
- &'a L: IntoIterator,
-{
- type Item = <&'a L as IntoIterator>::Item;
- type IntoIter = <&'a L as IntoIterator>::IntoIter;
-
- fn into_iter(self) -> Self::IntoIter {
- self.data.into_iter()
- }
-}
-
-impl<'a, L> IntoIterator for &'a mut LockCollection<L>
-where
- &'a mut L: IntoIterator,
-{
- type Item = <&'a mut L as IntoIterator>::Item;
- type IntoIter = <&'a mut L as IntoIterator>::IntoIter;
-
- fn into_iter(self) -> Self::IntoIter {
- self.data.into_iter()
- }
-}
-
-impl<'a, L: OwnedLockable<'a>, I: FromIterator<L> + OwnedLockable<'a>> FromIterator<L>
- for LockCollection<I>
-{
- fn from_iter<T: IntoIterator<Item = L>>(iter: T) -> Self {
- let iter: I = iter.into_iter().collect();
- Self::new(iter)
- }
-}
-
-impl<'a, E: OwnedLockable<'a> + Extend<L>, L: OwnedLockable<'a>> Extend<L> for LockCollection<E> {
- fn extend<T: IntoIterator<Item = L>>(&mut self, iter: T) {
- self.data.extend(iter)
- }
-}
-
-impl<'a, L: OwnedLockable<'a>> LockCollection<L> {
- /// Creates a new collection of owned locks.
- ///
- /// Because the locks are owned, there's no need to do any checks for
- /// duplicate values.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex};
- ///
- /// let lock = LockCollection::new((Mutex::new(0), Mutex::new("")));
- /// ```
- #[must_use]
- pub const fn new(data: L) -> Self {
- Self { data }
- }
-
- /// Creates a new collection of owned locks.
- ///
- /// Because the locks are owned, there's no need to do any checks for
- /// duplicate values.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex};
- ///
- /// let data = (Mutex::new(0), Mutex::new(""));
- /// let lock = LockCollection::new_ref(&data);
- /// ```
- #[must_use]
- pub const fn new_ref(data: &L) -> LockCollection<&L> {
- LockCollection { data }
- }
-}
-
-impl<L> LockCollection<L> {
- /// Creates a new collections of locks.
- ///
- /// # Safety
- ///
- /// This results in undefined behavior if any locks are presented twice
- /// within this collection.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex};
- ///
- /// let data1 = Mutex::new(0);
- /// let data2 = Mutex::new("");
- ///
- /// // safety: data1 and data2 refer to distinct mutexes
- /// let lock = unsafe { LockCollection::new_unchecked((&data1, &data2)) };
- /// ```
- #[must_use]
- pub const unsafe fn new_unchecked(data: L) -> Self {
- Self { data }
- }
-}
-
-impl<'a, L: Lockable<'a>> LockCollection<L> {
- /// Creates a new collection of locks.
- ///
- /// This returns `None` if any locks are found twice in the given
- /// collection.
- ///
- /// # Performance
- ///
- /// This does a check at runtime to make sure that the collection contains
- /// no two copies of the same lock. This is an `O(n^2)` operation. Prefer
- /// [`LockCollection::new`] or [`LockCollection::new_ref`] instead.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex};
- ///
- /// let data1 = Mutex::new(0);
- /// let data2 = Mutex::new("");
- ///
- /// // data1 and data2 refer to distinct mutexes, so this won't panic
- /// let lock = LockCollection::try_new((&data1, &data2)).unwrap();
- /// ```
- #[must_use]
- pub fn try_new(data: L) -> Option<Self> {
- let mut ptrs = data.get_ptrs();
- if contains_duplicates(&mut ptrs) {
- return None;
- }
-
- Some(Self { data })
- }
-
- /// Locks the collection
- ///
- /// This function returns a guard that can be used to access the underlying
- /// data. When the guard is dropped, the locks in the collection are also
- /// dropped.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex, ThreadKey};
- ///
- /// let key = ThreadKey::get().unwrap();
- /// let lock = LockCollection::new((Mutex::new(0), Mutex::new("")));
- ///
- /// let mut guard = lock.lock(key);
- /// *guard.0 += 1;
- /// *guard.1 = "1";
- /// ```
- pub fn lock<'key: 'a, Key: Keyable + 'key>(&'a self, key: Key) -> LockGuard<'a, 'key, L, Key> {
- LockGuard {
- // safety: we have the thread's key
- guard: unsafe { self.data.lock() },
- key,
- _phantom: PhantomData,
- }
- }
-
- /// Attempts to lock the without blocking.
- ///
- /// If successful, this method returns a guard that can be used to access
- /// the data, and unlocks the data when it is dropped. Otherwise, `None` is
- /// returned.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex, ThreadKey};
- ///
- /// let key = ThreadKey::get().unwrap();
- /// let lock = LockCollection::new((Mutex::new(0), Mutex::new("")));
- ///
- /// match lock.try_lock(key) {
- /// Some(mut guard) => {
- /// *guard.0 += 1;
- /// *guard.1 = "1";
- /// },
- /// None => unreachable!(),
- /// };
- ///
- /// ```
- pub fn try_lock<'key: 'a, Key: Keyable + 'key>(
- &'a self,
- key: Key,
- ) -> Option<LockGuard<'a, 'key, L, Key>> {
- // safety: we have the thread's key
- unsafe { self.data.try_lock() }.map(|guard| LockGuard {
- guard,
- key,
- _phantom: PhantomData,
- })
- }
-
- /// Unlocks the underlying lockable data type, returning the key that's
- /// associated with it.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{LockCollection, Mutex, ThreadKey};
- ///
- /// let key = ThreadKey::get().unwrap();
- /// let lock = LockCollection::new((Mutex::new(0), Mutex::new("")));
- ///
- /// let mut guard = lock.lock(key);
- /// *guard.0 += 1;
- /// *guard.1 = "1";
- /// let key = LockCollection::unlock(guard);
- /// ```
- #[allow(clippy::missing_const_for_fn)]
- pub fn unlock<'key: 'a, Key: Keyable + 'key>(guard: LockGuard<'a, 'key, L, Key>) -> Key {
- drop(guard.guard);
- guard.key
- }
-}
-
-impl<'a, L: 'a> LockCollection<L>
-where
- &'a L: IntoIterator,
-{
- /// Returns an iterator over references to each value in the collection.
- pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter {
- self.into_iter()
- }
-}
-
-impl<'a, L: 'a> LockCollection<L>
-where
- &'a mut L: IntoIterator,
-{
- /// Returns an iterator over mutable references to each value in the
- /// collection.
- pub fn iter_mut(&'a mut self) -> <&'a mut L as IntoIterator>::IntoIter {
- self.into_iter()
- }
-}
diff --git a/src/collection/guard.rs b/src/collection/guard.rs
index 110a935..8857c5f 100644
--- a/src/collection/guard.rs
+++ b/src/collection/guard.rs
@@ -1,19 +1,44 @@
+use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut};
-use crate::{key::Keyable, Lockable};
+use crate::key::Keyable;
use super::LockGuard;
-impl<'a, 'key: 'a, L: Lockable<'a>, Key: Keyable> Deref for LockGuard<'a, 'key, L, Key> {
- type Target = L::Output;
+impl<'key, Guard: Debug, Key: Keyable> Debug for LockGuard<'key, Guard, Key> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl<'key, Guard: Display, Key: Keyable> Display for LockGuard<'key, Guard, Key> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
+impl<'key, Guard, Key: Keyable> Deref for LockGuard<'key, Guard, Key> {
+ type Target = Guard;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
-impl<'a, 'key: 'a, L: Lockable<'a>, Key: Keyable> DerefMut for LockGuard<'a, 'key, L, Key> {
+impl<'key, Guard, Key: Keyable> DerefMut for LockGuard<'key, Guard, Key> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}
+
+impl<'key, Guard, Key: Keyable> AsRef<Guard> for LockGuard<'key, Guard, Key> {
+ fn as_ref(&self) -> &Guard {
+ &self.guard
+ }
+}
+
+impl<'key, Guard, Key: Keyable> AsMut<Guard> for LockGuard<'key, Guard, Key> {
+ fn as_mut(&mut self) -> &mut Guard {
+ &mut self.guard
+ }
+}
diff --git a/src/collection/owned.rs b/src/collection/owned.rs
new file mode 100644
index 0000000..919c403
--- /dev/null
+++ b/src/collection/owned.rs
@@ -0,0 +1,347 @@
+use std::marker::PhantomData;
+
+use crate::lockable::{Lockable, OwnedLockable, RawLock, Sharable};
+use crate::Keyable;
+
+use super::{utils, LockGuard, OwnedLockCollection};
+
+fn get_locks<L: Lockable>(data: &L) -> Vec<&dyn RawLock> {
+ let mut locks = Vec::new();
+ data.get_ptrs(&mut locks);
+ locks
+}
+
+unsafe impl<L: Lockable> Lockable for OwnedLockCollection<L> {
+ type Guard<'g> = L::Guard<'g> where Self: 'g;
+
+ type ReadGuard<'g> = L::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ self.data.get_ptrs(ptrs)
+ }
+
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ self.data.guard()
+ }
+
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ self.data.read_guard()
+ }
+}
+
+unsafe impl<L: Sharable> Sharable for OwnedLockCollection<L> {}
+
+unsafe impl<L: OwnedLockable> OwnedLockable for OwnedLockCollection<L> {}
+
+impl<L> IntoIterator for OwnedLockCollection<L>
+where
+ L: IntoIterator,
+{
+ type Item = <L as IntoIterator>::Item;
+ type IntoIter = <L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<L: OwnedLockable, I: FromIterator<L> + OwnedLockable> FromIterator<L>
+ for OwnedLockCollection<I>
+{
+ fn from_iter<T: IntoIterator<Item = L>>(iter: T) -> Self {
+ let iter: I = iter.into_iter().collect();
+ Self::new(iter)
+ }
+}
+
+impl<E: OwnedLockable + Extend<L>, L: OwnedLockable> Extend<L> for OwnedLockCollection<E> {
+ fn extend<T: IntoIterator<Item = L>>(&mut self, iter: T) {
+ self.data.extend(iter)
+ }
+}
+
+impl<L: OwnedLockable> AsMut<L> for OwnedLockCollection<L> {
+ fn as_mut(&mut self) -> &mut L {
+ &mut self.data
+ }
+}
+
+impl<L: OwnedLockable + Default> Default for OwnedLockCollection<L> {
+ fn default() -> Self {
+ Self::new(L::default())
+ }
+}
+
+impl<L: OwnedLockable + Default> From<L> for OwnedLockCollection<L> {
+ fn from(value: L) -> Self {
+ Self::new(value)
+ }
+}
+
+impl<L: OwnedLockable> OwnedLockCollection<L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values. The locks also don't need to be sorted by memory
+ /// address because they aren't used anywhere else.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ /// ```
+ #[must_use]
+ pub const fn new(data: L) -> Self {
+ Self { data }
+ }
+
+ /// Gets the underlying collection, consuming this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let data = (Mutex::new(42), Mutex::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let inner = lock.into_inner();
+ /// let guard = inner.0.lock(key);
+ /// assert_eq!(*guard, 42);
+ /// ```
+ #[must_use]
+ pub fn into_inner(self) -> L {
+ self.data
+ }
+
+ /// Locks the collection
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data. When the guard is dropped, the locks in the collection are also
+ /// dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// ```
+ pub fn lock<'g, 'key, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::Guard<'g>, Key> {
+ let locks = get_locks(&self.data);
+ for lock in locks {
+ // safety: we have the thread key, and these locks happen in a
+ // predetermined order
+ unsafe { lock.lock() };
+ }
+
+ // safety: we've locked all of this already
+ let guard = unsafe { self.data.guard() };
+ LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data, and unlocks the data when it is dropped. Otherwise, `None` is
+ /// returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// match lock.try_lock(key) {
+ /// Some(mut guard) => {
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::Guard<'g>, Key>> {
+ let locks = get_locks(&self.data);
+ let guard = unsafe {
+ if !utils::ordered_try_lock(&locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// let key = OwnedLockCollection::<(Mutex<i32>, Mutex<&str>)>::unlock(guard);
+ /// ```
+ #[allow(clippy::missing_const_for_fn)]
+ pub fn unlock<'g, 'key: 'g, Key: Keyable + 'key>(
+ guard: LockGuard<'key, L::Guard<'g>, Key>,
+ ) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<L: Sharable> OwnedLockCollection<L> {
+ /// Locks the collection, so that other threads can still read from it
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data immutably. When the guard is dropped, the locks in the collection
+ /// are also dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// assert_eq!(*guard.0, 0);
+ /// assert_eq!(*guard.1, "");
+ /// ```
+ pub fn read<'g, 'key, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::ReadGuard<'g>, Key> {
+ let locks = get_locks(&self.data);
+ for lock in locks {
+ // safety: we have the thread key, and these locks happen in a
+ // predetermined order
+ unsafe { lock.read() };
+ }
+
+ // safety: we've locked all of this already
+ let guard = unsafe { self.data.read_guard() };
+ LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking, in such a way that other threads
+ /// can still read from the collection.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data immutably, and unlocks the data when it is dropped. Otherwise,
+ /// `None` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(5), RwLock::new("6"));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// match lock.try_read(key) {
+ /// Some(mut guard) => {
+ /// assert_eq!(*guard.0, 5);
+ /// assert_eq!(*guard.1, "6");
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::ReadGuard<'g>, Key>> {
+ let locks = get_locks(&self.data);
+ let guard = unsafe {
+ if !utils::ordered_try_read(&locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.read_guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::OwnedLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = OwnedLockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// let key = OwnedLockCollection::<(RwLock<i32>, RwLock<&str>)>::unlock_read(guard);
+ /// ```
+ #[allow(clippy::missing_const_for_fn)]
+ pub fn unlock_read<'g, 'key: 'g, Key: Keyable + 'key>(
+ guard: LockGuard<'key, L::ReadGuard<'g>, Key>,
+ ) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
diff --git a/src/collection/ref.rs b/src/collection/ref.rs
new file mode 100644
index 0000000..d8c7f2e
--- /dev/null
+++ b/src/collection/ref.rs
@@ -0,0 +1,399 @@
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+use crate::lockable::{Lockable, OwnedLockable, RawLock, Sharable};
+use crate::Keyable;
+
+use super::{utils, LockGuard, RefLockCollection};
+
+#[must_use]
+pub fn get_locks<L: Lockable>(data: &L) -> Vec<&dyn RawLock> {
+ let mut locks = Vec::new();
+ data.get_ptrs(&mut locks);
+ locks.sort_by_key(|lock| std::ptr::from_ref(*lock));
+ locks
+}
+
+/// returns `true` if the sorted list contains a duplicate
+#[must_use]
+fn contains_duplicates(l: &[&dyn RawLock]) -> bool {
+ l.windows(2)
+ .any(|window| std::ptr::eq(window[0], window[1]))
+}
+
+impl<'a, L> AsRef<L> for RefLockCollection<'a, L> {
+ fn as_ref(&self) -> &L {
+ self.data
+ }
+}
+
+impl<'a, L> IntoIterator for &'a RefLockCollection<'a, L>
+where
+ &'a L: IntoIterator,
+{
+ type Item = <&'a L as IntoIterator>::Item;
+ type IntoIter = <&'a L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+unsafe impl<'c, L: Lockable> Lockable for RefLockCollection<'c, L> {
+ type Guard<'g> = L::Guard<'g> where Self: 'g;
+
+ type ReadGuard<'g> = L::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ ptrs.extend_from_slice(&self.locks);
+ }
+
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ self.data.guard()
+ }
+
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ self.data.read_guard()
+ }
+}
+
+unsafe impl<'c, L: Sharable> Sharable for RefLockCollection<'c, L> {}
+
+impl<'a, L: Debug> Debug for RefLockCollection<'a, L> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct(stringify!(RefLockCollection))
+ .field("data", self.data)
+ .finish_non_exhaustive()
+ }
+}
+
+impl<'a, L: OwnedLockable + Default> From<&'a L> for RefLockCollection<'a, L> {
+ fn from(value: &'a L) -> Self {
+ Self::new(value)
+ }
+}
+
+impl<'a, L: OwnedLockable> RefLockCollection<'a, L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ /// ```
+ #[must_use]
+ pub fn new(data: &'a L) -> RefLockCollection<L> {
+ RefLockCollection {
+ locks: get_locks(data),
+ data,
+ }
+ }
+}
+
+impl<'a, L: Lockable> RefLockCollection<'a, L> {
+ /// Creates a new collections of locks.
+ ///
+ /// # Safety
+ ///
+ /// This results in undefined behavior if any locks are presented twice
+ /// within this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // safety: data1 and data2 refer to distinct mutexes
+ /// let data = (&data1, &data2);
+ /// let lock = unsafe { RefLockCollection::new_unchecked(&data) };
+ /// ```
+ #[must_use]
+ pub unsafe fn new_unchecked(data: &'a L) -> Self {
+ Self {
+ data,
+ locks: get_locks(data),
+ }
+ }
+
+ /// Creates a new collection of locks.
+ ///
+ /// This returns `None` if any locks are found twice in the given
+ /// collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // data1 and data2 refer to distinct mutexes, so this won't panic
+ /// let data = (&data1, &data2);
+ /// let lock = RefLockCollection::try_new(&data).unwrap();
+ /// ```
+ #[must_use]
+ pub fn try_new(data: &'a L) -> Option<Self> {
+ let locks = get_locks(data);
+ if contains_duplicates(&locks) {
+ return None;
+ }
+
+ Some(Self { data, locks })
+ }
+
+ /// Locks the collection
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data. When the guard is dropped, the locks in the collection are also
+ /// dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// ```
+ pub fn lock<'key: 'a, Key: Keyable + 'key>(
+ &'a self,
+ key: Key,
+ ) -> LockGuard<'key, L::Guard<'a>, Key> {
+ for lock in &self.locks {
+ // safety: we have the thread key
+ unsafe { lock.lock() };
+ }
+
+ LockGuard {
+ // safety: we've already acquired the lock
+ guard: unsafe { self.data.guard() },
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data, and unlocks the data when it is dropped. Otherwise, `None` is
+ /// returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// match lock.try_lock(key) {
+ /// Some(mut guard) => {
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_lock<'key: 'a, Key: Keyable + 'key>(
+ &'a self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::Guard<'a>, Key>> {
+ let guard = unsafe {
+ if !utils::ordered_try_lock(&self.locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// let key = RefLockCollection::<(Mutex<i32>, Mutex<&str>)>::unlock(guard);
+ /// ```
+ #[allow(clippy::missing_const_for_fn)]
+ pub fn unlock<'key: 'a, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'a>, Key>) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<'a, L: Sharable> RefLockCollection<'a, L> {
+ /// Locks the collection, so that other threads can still read from it
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data immutably. When the guard is dropped, the locks in the collection
+ /// are also dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// assert_eq!(*guard.0, 0);
+ /// assert_eq!(*guard.1, "");
+ /// ```
+ pub fn read<'key: 'a, Key: Keyable + 'key>(
+ &'a self,
+ key: Key,
+ ) -> LockGuard<'key, L::ReadGuard<'a>, Key> {
+ for lock in &self.locks {
+ // safety: we have the thread key
+ unsafe { lock.read() };
+ }
+
+ LockGuard {
+ // safety: we've already acquired the lock
+ guard: unsafe { self.data.read_guard() },
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking, in such a way that other threads
+ /// can still read from the collection.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data immutably, and unlocks the data when it is dropped. Otherwise,
+ /// `None` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(5), RwLock::new("6"));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// match lock.try_read(key) {
+ /// Some(mut guard) => {
+ /// assert_eq!(*guard.0, 5);
+ /// assert_eq!(*guard.1, "6");
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_read<'key: 'a, Key: Keyable + 'key>(
+ &'a self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::ReadGuard<'a>, Key>> {
+ let guard = unsafe {
+ if !utils::ordered_try_read(&self.locks) {
+ return None;
+ }
+
+ // safety: we've acquired the locks
+ self.data.read_guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// let key = RefLockCollection::<(RwLock<i32>, RwLock<&str>)>::unlock_read(guard);
+ /// ```
+ #[allow(clippy::missing_const_for_fn)]
+ pub fn unlock_read<'key: 'a, Key: Keyable + 'key>(
+ guard: LockGuard<'key, L::ReadGuard<'a>, Key>,
+ ) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<'a, L: 'a> RefLockCollection<'a, L>
+where
+ &'a L: IntoIterator,
+{
+ /// Returns an iterator over references to each value in the collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RefLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = [Mutex::new(26), Mutex::new(1)];
+ /// let lock = RefLockCollection::new(&data);
+ ///
+ /// let mut iter = lock.iter();
+ /// let mutex = iter.next().unwrap();
+ /// let guard = mutex.lock(key);
+ ///
+ /// assert_eq!(*guard, 26);
+ /// ```
+ #[must_use]
+ pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter {
+ self.into_iter()
+ }
+}
diff --git a/src/collection/retry.rs b/src/collection/retry.rs
new file mode 100644
index 0000000..2b9b0a0
--- /dev/null
+++ b/src/collection/retry.rs
@@ -0,0 +1,619 @@
+use crate::lockable::{Lockable, OwnedLockable, RawLock, Sharable};
+use crate::Keyable;
+
+use std::collections::HashSet;
+use std::marker::PhantomData;
+
+use super::{LockGuard, RetryingLockCollection};
+
+/// Checks that a collection contains no duplicate references to a lock.
+fn contains_duplicates<L: Lockable>(data: L) -> bool {
+ let mut locks = Vec::new();
+ data.get_ptrs(&mut locks);
+ let locks = locks.into_iter().map(|l| l as *const dyn RawLock);
+
+ let mut locks_set = HashSet::with_capacity(locks.len());
+ for lock in locks {
+ if !locks_set.insert(lock) {
+ return true;
+ }
+ }
+
+ false
+}
+
+unsafe impl<L: Lockable> Lockable for RetryingLockCollection<L> {
+ type Guard<'g> = L::Guard<'g> where Self: 'g;
+
+ type ReadGuard<'g> = L::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ self.data.get_ptrs(ptrs)
+ }
+
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ self.data.guard()
+ }
+
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ self.data.read_guard()
+ }
+}
+
+unsafe impl<L: Sharable> Sharable for RetryingLockCollection<L> {}
+
+unsafe impl<L: OwnedLockable> OwnedLockable for RetryingLockCollection<L> {}
+
+impl<L> IntoIterator for RetryingLockCollection<L>
+where
+ L: IntoIterator,
+{
+ type Item = <L as IntoIterator>::Item;
+ type IntoIter = <L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<'a, L> IntoIterator for &'a RetryingLockCollection<L>
+where
+ &'a L: IntoIterator,
+{
+ type Item = <&'a L as IntoIterator>::Item;
+ type IntoIter = <&'a L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<'a, L> IntoIterator for &'a mut RetryingLockCollection<L>
+where
+ &'a mut L: IntoIterator,
+{
+ type Item = <&'a mut L as IntoIterator>::Item;
+ type IntoIter = <&'a mut L as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.data.into_iter()
+ }
+}
+
+impl<L: OwnedLockable, I: FromIterator<L> + OwnedLockable> FromIterator<L>
+ for RetryingLockCollection<I>
+{
+ fn from_iter<T: IntoIterator<Item = L>>(iter: T) -> Self {
+ let iter: I = iter.into_iter().collect();
+ Self::new(iter)
+ }
+}
+
+impl<E: OwnedLockable + Extend<L>, L: OwnedLockable> Extend<L> for RetryingLockCollection<E> {
+ fn extend<T: IntoIterator<Item = L>>(&mut self, iter: T) {
+ self.data.extend(iter)
+ }
+}
+
+impl<L> AsRef<L> for RetryingLockCollection<L> {
+ fn as_ref(&self) -> &L {
+ &self.data
+ }
+}
+
+impl<L> AsMut<L> for RetryingLockCollection<L> {
+ fn as_mut(&mut self) -> &mut L {
+ &mut self.data
+ }
+}
+
+impl<L: OwnedLockable + Default> Default for RetryingLockCollection<L> {
+ fn default() -> Self {
+ Self::new(L::default())
+ }
+}
+
+impl<L: OwnedLockable> From<L> for RetryingLockCollection<L> {
+ fn from(value: L) -> Self {
+ Self::new(value)
+ }
+}
+
+impl<L: OwnedLockable> RetryingLockCollection<L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values. The locks also don't need to be sorted by memory
+ /// address because they aren't used anywhere else.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ /// ```
+ #[must_use]
+ pub const fn new(data: L) -> Self {
+ Self { data }
+ }
+}
+
+impl<'a, L: OwnedLockable> RetryingLockCollection<&'a L> {
+ /// Creates a new collection of owned locks.
+ ///
+ /// Because the locks are owned, there's no need to do any checks for
+ /// duplicate values.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new_ref(&data);
+ /// ```
+ #[must_use]
+ pub const fn new_ref(data: &'a L) -> Self {
+ Self { data }
+ }
+}
+
+impl<L: Lockable> RetryingLockCollection<L> {
+ /// Creates a new collections of locks.
+ ///
+ /// # Safety
+ ///
+ /// This results in undefined behavior if any locks are presented twice
+ /// within this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // safety: data1 and data2 refer to distinct mutexes
+ /// let data = (&data1, &data2);
+ /// let lock = unsafe { RetryingLockCollection::new_unchecked(&data) };
+ /// ```
+ #[must_use]
+ pub const unsafe fn new_unchecked(data: L) -> Self {
+ Self { data }
+ }
+
+ /// Creates a new collection of locks.
+ ///
+ /// This returns `None` if any locks are found twice in the given
+ /// collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::Mutex;
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let data1 = Mutex::new(0);
+ /// let data2 = Mutex::new("");
+ ///
+ /// // data1 and data2 refer to distinct mutexes, so this won't panic
+ /// let data = (&data1, &data2);
+ /// let lock = RetryingLockCollection::try_new(&data).unwrap();
+ /// ```
+ #[must_use]
+ pub fn try_new(data: L) -> Option<Self> {
+ (!contains_duplicates(&data)).then_some(Self { data })
+ }
+
+ /// Gets the underlying collection, consuming this collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let data = (Mutex::new(42), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let inner = lock.into_inner();
+ /// let guard = inner.0.lock(key);
+ /// assert_eq!(*guard, 42);
+ /// ```
+ #[must_use]
+ pub fn into_inner(self) -> L {
+ self.data
+ }
+
+ /// Locks the collection
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data. When the guard is dropped, the locks in the collection are also
+ /// dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// ```
+ pub fn lock<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::Guard<'g>, Key> {
+ let mut first_index = 0;
+ let mut locks = Vec::new();
+ self.data.get_ptrs(&mut locks);
+
+ if locks.is_empty() {
+ return LockGuard {
+ // safety: there's no data being returned
+ guard: unsafe { self.data.guard() },
+ key,
+ _phantom: PhantomData,
+ };
+ }
+
+ let guard = unsafe {
+ 'outer: loop {
+ // safety: we have the thread key
+ locks[first_index].lock();
+ for (i, lock) in locks.iter().enumerate() {
+ if i == first_index {
+ continue;
+ }
+
+ // safety: we have the thread key
+ if !lock.try_lock() {
+ for lock in locks.iter().take(i) {
+ // safety: we already locked all of these
+ lock.unlock();
+ }
+
+ if first_index >= i {
+ // safety: this is already locked and can't be unlocked
+ // by the previous loop
+ locks[first_index].unlock();
+ }
+
+ first_index = i;
+ continue 'outer;
+ }
+ }
+
+ // safety: we locked all the data
+ break self.data.guard();
+ }
+ };
+
+ LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data, and unlocks the data when it is dropped. Otherwise, `None` is
+ /// returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// match lock.try_lock(key) {
+ /// Some(mut guard) => {
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::Guard<'g>, Key>> {
+ let mut locks = Vec::new();
+ self.data.get_ptrs(&mut locks);
+
+ if locks.is_empty() {
+ return Some(LockGuard {
+ // safety: there's no data being returned
+ guard: unsafe { self.data.guard() },
+ key,
+ _phantom: PhantomData,
+ });
+ }
+
+ let guard = unsafe {
+ for (i, lock) in locks.iter().enumerate() {
+ // safety: we have the thread key
+ if !lock.try_lock() {
+ for lock in locks.iter().take(i) {
+ // safety: we already locked all of these
+ lock.unlock();
+ }
+ return None;
+ }
+ }
+
+ // safety: we locked all the data
+ self.data.guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (Mutex::new(0), Mutex::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut guard = lock.lock(key);
+ /// *guard.0 += 1;
+ /// *guard.1 = "1";
+ /// let key = RetryingLockCollection::<(Mutex<i32>, Mutex<&str>)>::unlock(guard);
+ /// ```
+ pub fn unlock<'key, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'_>, Key>) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<L: Sharable> RetryingLockCollection<L> {
+ /// Locks the collection, so that other threads can still read from it
+ ///
+ /// This function returns a guard that can be used to access the underlying
+ /// data immutably. When the guard is dropped, the locks in the collection
+ /// are also dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// assert_eq!(*guard.0, 0);
+ /// assert_eq!(*guard.1, "");
+ /// ```
+ pub fn read<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> LockGuard<'key, L::ReadGuard<'g>, Key> {
+ let mut first_index = 0;
+ let mut locks = Vec::new();
+ self.data.get_ptrs(&mut locks);
+
+ if locks.is_empty() {
+ return LockGuard {
+ // safety: there's no data being returned
+ guard: unsafe { self.data.read_guard() },
+ key,
+ _phantom: PhantomData,
+ };
+ }
+
+ let guard = unsafe {
+ 'outer: loop {
+ // safety: we have the thread key
+ locks[first_index].read();
+ for (i, lock) in locks.iter().enumerate() {
+ if i == first_index {
+ continue;
+ }
+
+ // safety: we have the thread key
+ if !lock.try_read() {
+ for lock in locks.iter().take(i) {
+ // safety: we already locked all of these
+ lock.unlock_read();
+ }
+
+ if first_index >= i {
+ // safety: this is already locked and can't be unlocked
+ // by the previous loop
+ locks[first_index].unlock_read();
+ }
+
+ first_index = i;
+ continue 'outer;
+ }
+ }
+
+ // safety: we locked all the data
+ break self.data.read_guard();
+ }
+ };
+
+ LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Attempts to lock the without blocking, in such a way that other threads
+ /// can still read from the collection.
+ ///
+ /// If successful, this method returns a guard that can be used to access
+ /// the data immutably, and unlocks the data when it is dropped. Otherwise,
+ /// `None` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(5), RwLock::new("6"));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// match lock.try_read(key) {
+ /// Some(mut guard) => {
+ /// assert_eq!(*guard.0, 5);
+ /// assert_eq!(*guard.1, "6");
+ /// },
+ /// None => unreachable!(),
+ /// };
+ ///
+ /// ```
+ pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>(
+ &'g self,
+ key: Key,
+ ) -> Option<LockGuard<'key, L::ReadGuard<'g>, Key>> {
+ let mut locks = Vec::new();
+ self.data.get_ptrs(&mut locks);
+
+ if locks.is_empty() {
+ return Some(LockGuard {
+ // safety: there's no data being returned
+ guard: unsafe { self.data.read_guard() },
+ key,
+ _phantom: PhantomData,
+ });
+ }
+
+ let guard = unsafe {
+ for (i, lock) in locks.iter().enumerate() {
+ // safety: we have the thread key
+ if !lock.try_read() {
+ for lock in locks.iter().take(i) {
+ // safety: we already locked all of these
+ lock.unlock_read();
+ }
+ return None;
+ }
+ }
+
+ // safety: we locked all the data
+ self.data.read_guard()
+ };
+
+ Some(LockGuard {
+ guard,
+ key,
+ _phantom: PhantomData,
+ })
+ }
+
+ /// Unlocks the underlying lockable data type, returning the key that's
+ /// associated with it.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{RwLock, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = (RwLock::new(0), RwLock::new(""));
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut guard = lock.read(key);
+ /// let key = RetryingLockCollection::<(RwLock<i32>, RwLock<&str>)>::unlock_read(guard);
+ /// ```
+ pub fn unlock_read<'key, Key: Keyable + 'key>(
+ guard: LockGuard<'key, L::ReadGuard<'_>, Key>,
+ ) -> Key {
+ drop(guard.guard);
+ guard.key
+ }
+}
+
+impl<'a, L: 'a> RetryingLockCollection<L>
+where
+ &'a L: IntoIterator,
+{
+ /// Returns an iterator over references to each value in the collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = [Mutex::new(26), Mutex::new(1)];
+ /// let lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut iter = lock.iter();
+ /// let mutex = iter.next().unwrap();
+ /// let guard = mutex.lock(key);
+ ///
+ /// assert_eq!(*guard, 26);
+ /// ```
+ #[must_use]
+ pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter {
+ self.into_iter()
+ }
+}
+
+impl<'a, L: 'a> RetryingLockCollection<L>
+where
+ &'a mut L: IntoIterator,
+{
+ /// Returns an iterator over mutable references to each value in the
+ /// collection.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use happylock::{Mutex, ThreadKey};
+ /// use happylock::collection::RetryingLockCollection;
+ ///
+ /// let key = ThreadKey::get().unwrap();
+ /// let data = [Mutex::new(26), Mutex::new(1)];
+ /// let mut lock = RetryingLockCollection::new(data);
+ ///
+ /// let mut iter = lock.iter_mut();
+ /// let mutex = iter.next().unwrap();
+ ///
+ /// assert_eq!(*mutex.as_mut(), 26);
+ /// ```
+ #[must_use]
+ pub fn iter_mut(&'a mut self) -> <&'a mut L as IntoIterator>::IntoIter {
+ self.into_iter()
+ }
+}
diff --git a/src/collection/utils.rs b/src/collection/utils.rs
new file mode 100644
index 0000000..dc58399
--- /dev/null
+++ b/src/collection/utils.rs
@@ -0,0 +1,44 @@
+use crate::lockable::RawLock;
+
+/// Locks the locks in the order they are given. This causes deadlock if the
+/// locks contain duplicates, or if this is called by multiple threads with the
+/// locks in different orders.
+pub unsafe fn ordered_try_lock(locks: &[&dyn RawLock]) -> bool {
+ unsafe {
+ for (i, lock) in locks.iter().enumerate() {
+ // safety: we have the thread key
+ let success = lock.try_lock();
+
+ if !success {
+ for lock in &locks[0..i] {
+ // safety: this lock was already acquired
+ lock.unlock();
+ }
+ return false;
+ }
+ }
+
+ true
+ }
+}
+
+/// Locks the locks in the order they are given. This causes deadlock f this is
+/// called by multiple threads with the locks in different orders.
+pub unsafe fn ordered_try_read(locks: &[&dyn RawLock]) -> bool {
+ unsafe {
+ for (i, lock) in locks.iter().enumerate() {
+ // safety: we have the thread key
+ let success = lock.try_read();
+
+ if !success {
+ for lock in &locks[0..i] {
+ // safety: this lock was already acquired
+ lock.unlock_read();
+ }
+ return false;
+ }
+ }
+
+ true
+ }
+}
diff --git a/src/key.rs b/src/key.rs
index 1cfa209..4d6504f 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -7,6 +7,8 @@ use thread_local::ThreadLocal;
use sealed::Sealed;
+// Sealed to prevent other key types from being implemented. Otherwise, this
+// would almost instant undefined behavior.
mod sealed {
use super::ThreadKey;
@@ -15,12 +17,15 @@ mod sealed {
impl Sealed for &mut ThreadKey {}
}
+// I am concerned that having multiple crates linked together with different
+// static variables could break my key system. Library code probably shouldn't
+// be creating keys at all.
static KEY: Lazy<ThreadLocal<AtomicLock>> = Lazy::new(ThreadLocal::new);
/// The key for the current thread.
///
/// Only one of these exist per thread. To get the current thread's key, call
-/// [`ThreadKey::get`]. If the `ThreadKey` is dropped, it can be reobtained.
+/// [`ThreadKey::get`]. If the `ThreadKey` is dropped, it can be re-obtained.
pub struct ThreadKey {
phantom: PhantomData<*const ()>, // implement !Send and !Sync
}
@@ -34,6 +39,7 @@ pub struct ThreadKey {
/// values invalid.
pub unsafe trait Keyable: Sealed {}
unsafe impl Keyable for ThreadKey {}
+// the ThreadKey can't be moved while a mutable reference to it exists
unsafe impl Keyable for &mut ThreadKey {}
impl Debug for ThreadKey {
@@ -42,6 +48,7 @@ impl Debug for ThreadKey {
}
}
+// If you lose the thread key, you can get it back by calling ThreadKey::get
impl Drop for ThreadKey {
fn drop(&mut self) {
unsafe { KEY.get().unwrap().force_unlock() }
diff --git a/src/lib.rs b/src/lib.rs
index 4093ac4..643c3e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,7 +14,7 @@
//! # Theory
//!
//! There are four conditions necessary for a deadlock to occur. In order to
-//! prevent deadlocks, we need to prevent one of the following:
+//! prevent deadlocks, we just need to prevent one of the following:
//!
//! 1. mutual exclusion
//! 2. non-preemptive allocation
@@ -22,29 +22,15 @@
//! 4. **partial allocation**
//!
//! This library seeks to solve **partial allocation** by requiring total
-//! allocation. All of the resources a thread needs must be allocated at the
-//! same time. In order to request new resources, the old resources must be
-//! dropped first. Requesting multiple resources at once is atomic. You either
-//! get all of the requested resources or none at all.
+//! allocation. All the resources a thread needs must be allocated at the same
+//! time. In order to request new resources, the old resources must be dropped
+//! first. Requesting multiple resources at once is atomic. You either get all
+//! the requested resources or none at all.
//!
-//! # Performance
-//!
-//! **Avoid [`LockCollection::try_new`].** This constructor will check to make
-//! sure that the collection contains no duplicate locks. This is an O(n^2)
-//! operation, where n is the number of locks in the collection.
-//! [`LockCollection::new`] and [`LockCollection::new_ref`] don't need these
-//! checks because they use [`OwnedLockable`], which is guaranteed to be unique
-//! as long as it is accessible. As a last resort,
-//! [`LockCollection::new_unchecked`] doesn't do this check, but is unsafe to
-//! call.
-//!
-//! **Avoid using distinct lock orders for [`LockCollection`].** The problem is
-//! that this library must iterate through the list of locks, and not complete
-//! until every single one of them is unlocked. This also means that attempting
-//! to lock multiple mutexes gives you a lower chance of ever running. Only one
-//! needs to be locked for the operation to need a reset. This problem can be
-//! prevented by not doing that in your code. Resources should be obtained in
-//! the same order on every thread.
+//! As an optimization, this library also often prevents **circular wait**.
+//! Many collections sort the locks in order of their memory address. As long
+//! as the locks are always acquired in that order, then time doesn't need to
+//! be wasted on releasing locks after a failure and re-acquiring them later.
//!
//! # Examples
//!
@@ -105,21 +91,101 @@
//! println!("{}", *data.0);
//! println!("{}", *data.1);
//! ```
+//!
+//! In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`]
+//! method can be used, improving performance.
+//!
+//! ```rust
+//! use std::thread;
+//! use happylock::{LockCollection, Mutex, ThreadKey};
+//!
+//! const N: usize = 100;
+//!
+//! static DATA: [Mutex<i32>; 2] = [Mutex::new(0), Mutex::new(1)];
+//!
+//! for _ in 0..N {
+//! thread::spawn(move || {
+//! let key = ThreadKey::get().unwrap();
+//!
+//! // a reference to a type that implements `OwnedLockable` will never
+//! // contain duplicates, so no duplicate checking is needed.
+//! let collection = LockCollection::new_ref(&DATA);
+//! let mut guard = collection.lock(key);
+//!
+//! let x = *guard[1];
+//! *guard[1] += *guard[0];
+//! *guard[0] = x;
+//! });
+//! }
+//!
+//! let key = ThreadKey::get().unwrap();
+//! let data = LockCollection::new_ref(&DATA);
+//! let data = data.lock(key);
+//! println!("{}", data[0]);
+//! println!("{}", data[1]);
+//! ```
+//!
+//! # Performance
+//!
+//! **The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any
+//! memory, and it doesn't really exist at run-time. The only cost comes from
+//! calling `ThreadKey::get()`, because the function has to ensure at runtime
+//! that the key hasn't already been taken. Dropping the key will also have a
+//! small cost.
+//!
+//! **Consider [`OwnedLockCollection`].** This will almost always be the
+//! fastest lock collection. It doesn't expose the underlying collection
+//! immutably, which means that it will always be locked in the same order, and
+//! doesn't need any sorting.
+//!
+//! **Avoid [`LockCollection::try_new`].** This constructor will check to make
+//! sure that the collection contains no duplicate locks. In most cases, this
+//! is O(nlogn), where n is the number of locks in the collections but in the
+//! case of [`RetryingLockCollection`], it's close to O(n).
+//! [`LockCollection::new`] and [`LockCollection::new_ref`] don't need these
+//! checks because they use [`OwnedLockable`], which is guaranteed to be unique
+//! as long as it is accessible. As a last resort,
+//! [`LockCollection::new_unchecked`] doesn't do this check, but is unsafe to
+//! call.
+//!
+//! **Know how to use [`RetryingLockCollection`].** This collection doesn't do
+//! any sorting, but uses a wasteful lock algorithm. It can't rely on the order
+//! of the locks to be the same across threads, so if it finds a lock that it
+//! can't acquire without blocking, it'll first release all of the locks it
+//! already acquired to avoid blocking other threads. This is wasteful because
+//! this algorithm may end up re-acquiring the same lock multiple times. To
+//! avoid this, ensure that (1) the first lock in the collection is always the
+//! first lock in any collection it appears in, and (2) the other locks in the
+//! collection are always preceded by that first lock. This will prevent any
+//! wasted time from re-acquiring locks. If you're unsure, [`LockCollection`]
+//! is a sensible default.
+//!
+//! [`OwnedLockable`]: `lockable::OwnedLockable`
+//! [`OwnedLockCollection`]: `collection::OwnedLockCollection`
+//! [`RetryingLockCollection`]: `collection::RetryingLockCollection`
-mod collection;
mod key;
-mod lockable;
+pub mod collection;
+pub mod lockable;
pub mod mutex;
pub mod rwlock;
-pub use collection::LockCollection;
pub use key::{Keyable, ThreadKey};
-pub use lockable::{Lockable, OwnedLockable};
#[cfg(feature = "spin")]
pub use mutex::SpinLock;
+// Personally, I think re-exports look ugly in the rust documentation, so I
+// went with type aliases instead.
+
+/// A collection of locks that can be acquired simultaneously.
+///
+/// This re-exports [`BoxedLockCollection`] as a sensible default.
+///
+/// [`BoxedLockCollection`]: collection::BoxedLockCollection
+pub type LockCollection<L> = collection::BoxedLockCollection<L>;
+
/// A mutual exclusion primitive useful for protecting shared data, which cannot deadlock.
///
/// By default, this uses `parking_lot` as a backend.
diff --git a/src/lockable.rs b/src/lockable.rs
index 0dd9b07..9f44981 100644
--- a/src/lockable.rs
+++ b/src/lockable.rs
@@ -7,46 +7,35 @@ use crate::{
use lock_api::{RawMutex, RawRwLock};
-/// A type that may be locked and unlocked, and is known to be the only valid
-/// instance of the lock.
-///
-/// # Safety
-///
-/// 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.
-pub unsafe trait OwnedLockable<'a>: Lockable<'a> {}
-
-/// A type that may be locked and unlocked
+/// A raw lock type that may be locked and unlocked
///
/// # Safety
///
/// A deadlock must never occur. The `unlock` method must correctly unlock the
/// data. The `get_ptrs` method must be implemented correctly. The `Output`
/// must be unlocked when it is dropped.
-pub unsafe trait Lockable<'a> {
- /// The output of the lock
- type Output;
-
- /// Returns a list of all pointers to locks. This is used to ensure that
- /// the same lock isn't included twice
- #[must_use]
- fn get_ptrs(&self) -> Vec<usize>;
+// Why not use a RawRwLock? Because that would be semantically incorrect, and I
+// don't want an INIT or GuardMarker associated item.
+// Originally, RawLock had a sister trait: RawSharableLock. I removed it
+// because it'd be difficult to implement a separate type that takes a
+// different kind of RawLock. But now the Sharable marker trait is needed to
+// indicate if reads can be used.
+pub unsafe trait RawLock: Send + Sync {
/// Blocks until the lock is acquired
///
/// # Safety
///
- /// It is undefined behavior to:
- /// * Use this without ownership or mutable access to the [`ThreadKey`],
- /// which should last as long as the return value is alive.
- /// * Call this on multiple locks without unlocking first.
+ /// It is undefined behavior to use this without ownership or mutable
+ /// access to the [`ThreadKey`], which should last as long as the return
+ /// value is alive.
///
/// [`ThreadKey`]: `crate::ThreadKey`
- unsafe fn lock(&'a self) -> Self::Output;
+ unsafe fn lock(&self);
/// Attempt to lock without blocking.
///
- /// Returns `Some` if successful, `None` otherwise.
+ /// Returns `true` if successful, `false` otherwise.
///
/// # Safety
///
@@ -55,649 +44,449 @@ pub unsafe trait Lockable<'a> {
/// value is alive.
///
/// [`ThreadKey`]: `crate::ThreadKey`
- unsafe fn try_lock(&'a self) -> Option<Self::Output>;
-}
+ unsafe fn try_lock(&self) -> bool;
-unsafe impl<'a, T: Lockable<'a>> Lockable<'a> for &T {
- type Output = T::Output;
+ /// Releases the lock
+ ///
+ /// # Safety
+ ///
+ /// It is undefined behavior to use this if the lock is not acquired
+ unsafe fn unlock(&self);
- fn get_ptrs(&self) -> Vec<usize> {
- (**self).get_ptrs()
- }
+ /// Blocks until the data the lock protects can be safely read.
+ ///
+ /// Some locks, but not all, will allow multiple readers at once. If
+ /// multiple readers are allowed for a [`Lockable`] type, then the
+ /// [`Sharable`] marker trait should be implemented.
+ ///
+ /// # Safety
+ ///
+ /// It is undefined behavior to use this without ownership or mutable
+ /// access to the [`ThreadKey`], which should last as long as the return
+ /// value is alive.
+ ///
+ /// [`ThreadKey`]: `crate::ThreadKey`
+ unsafe fn read(&self);
- unsafe fn lock(&'a self) -> Self::Output {
- (**self).lock()
- }
+ // Attempt to read without blocking.
+ ///
+ /// Returns `true` if successful, `false` otherwise.
+ ///
+ /// Some locks, but not all, will allow multiple readers at once. If
+ /// multiple readers are allowed for a [`Lockable`] type, then the
+ /// [`Sharable`] marker trait should be implemented.
+ ///
+ /// # Safety
+ ///
+ /// It is undefined behavior to use this without ownership or mutable
+ /// access to the [`ThreadKey`], which should last as long as the return
+ /// value is alive.
+ ///
+ /// [`ThreadKey`]: `crate::ThreadKey`
+ unsafe fn try_read(&self) -> bool;
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- (**self).try_lock()
- }
+ /// Releases the lock after calling `read`.
+ ///
+ /// # Safety
+ ///
+ /// It is undefined behavior to use this if the read lock is not acquired
+ unsafe fn unlock_read(&self);
}
-unsafe impl<'a, T: Lockable<'a>> Lockable<'a> for &mut T {
- type Output = T::Output;
-
- fn get_ptrs(&self) -> Vec<usize> {
- (**self).get_ptrs()
- }
+/// A type that may be locked and unlocked.
+///
+/// This trait is usually implemented on collections of [`RawLock`]s. For
+/// example, a `Vec<Mutex<i32>>`.
+///
+/// # Safety
+///
+/// Acquiring the locks returned by `get_ptrs` must allow for the values
+/// returned by `guard` or `read_guard` to be safely used for exclusive or
+/// shared access, respectively.
+///
+/// Dropping the `Guard` and `ReadGuard` types must unlock those same locks.
+///
+/// The order of the resulting list from `get_ptrs` must be deterministic. As
+/// long as the value is not mutated, the references must always be in the same
+/// order.
+pub unsafe trait Lockable {
+ /// The exclusive guard that does not hold a key
+ type Guard<'g>
+ where
+ Self: 'g;
+
+ /// The shared guard type that does not hold a key
+ type ReadGuard<'g>
+ where
+ Self: 'g;
+
+ /// Yields a list of references to the [`RawLock`]s contained within this
+ /// value.
+ ///
+ /// These reference locks which must be locked before acquiring a guard,
+ /// and unlocked when the guard is dropped. The order of the resulting list
+ /// is deterministic. As long as the value is not mutated, the references
+ /// will always be in the same order.
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>);
- unsafe fn lock(&'a self) -> Self::Output {
- (**self).lock()
- }
+ /// Returns a guard that can be used to access the underlying data mutably.
+ ///
+ /// # Safety
+ ///
+ /// All locks given by calling [`Lockable::get_ptrs`] must be locked
+ /// exclusively before calling this function. The locks must not be
+ /// unlocked until this guard is dropped.
+ #[must_use]
+ unsafe fn guard(&self) -> Self::Guard<'_>;
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- (**self).try_lock()
- }
+ /// Returns a guard that can be used to immutably access the underlying
+ /// data.
+ ///
+ /// # Safety
+ ///
+ /// All locks given by calling [`Lockable::get_ptrs`] must be locked using
+ /// [`RawLock::read`] before calling this function. The locks must not be
+ /// unlocked until this guard is dropped.
+ #[must_use]
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_>;
}
-unsafe impl<'a, T: OwnedLockable<'a>> OwnedLockable<'a> for &mut T {}
+/// A marker trait to indicate that multiple readers can access the lock at a
+/// time.
+///
+/// # Safety
+///
+/// This type must only be implemented if the lock can be safely shared between
+/// multiple readers.
+pub unsafe trait Sharable: Lockable {}
-unsafe impl<'a, T: 'a, R: RawMutex + 'a> Lockable<'a> for Mutex<T, R> {
- type Output = MutexRef<'a, T, R>;
+/// A type that may be locked and unlocked, and is known to be the only valid
+/// instance of the lock.
+///
+/// # Safety
+///
+/// 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.
+pub unsafe trait OwnedLockable: Lockable {}
- fn get_ptrs(&self) -> Vec<usize> {
- vec![self as *const Self as usize]
+unsafe impl<T: Send, R: RawMutex + Send + Sync> RawLock for Mutex<T, R> {
+ unsafe fn lock(&self) {
+ self.raw().lock()
}
- unsafe fn lock(&'a self) -> Self::Output {
- self.lock_no_key()
+ unsafe fn try_lock(&self) -> bool {
+ self.raw().try_lock()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- self.try_lock_no_key()
+ unsafe fn unlock(&self) {
+ self.raw().unlock()
}
-}
-
-unsafe impl<'a, T: 'a, R: RawRwLock + 'a> Lockable<'a> for RwLock<T, R> {
- type Output = RwLockWriteRef<'a, T, R>;
- fn get_ptrs(&self) -> Vec<usize> {
- vec![self as *const Self as usize]
+ // this is the closest thing to a read we can get, but Sharable isn't
+ // implemented for this
+ unsafe fn read(&self) {
+ self.raw().lock()
}
- unsafe fn lock(&'a self) -> Self::Output {
- self.write_no_key()
+ unsafe fn try_read(&self) -> bool {
+ self.raw().try_lock()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- self.try_write_no_key()
+ unsafe fn unlock_read(&self) {
+ self.raw().unlock()
}
}
-unsafe impl<'a, T: 'a, R: RawMutex + 'a> OwnedLockable<'a> for Mutex<T, R> {}
+unsafe impl<T: Send, R: RawRwLock + Send + Sync> RawLock for RwLock<T, R> {
+ unsafe fn lock(&self) {
+ self.raw().lock_exclusive()
+ }
-unsafe impl<'a, T: 'a, R: RawRwLock + 'a> OwnedLockable<'a> for RwLock<T, R> {}
+ unsafe fn try_lock(&self) -> bool {
+ self.raw().try_lock_exclusive()
+ }
-unsafe impl<'a, T: 'a, R: RawRwLock + 'a> Lockable<'a> for ReadLock<'a, T, R> {
- type Output = RwLockReadRef<'a, T, R>;
+ unsafe fn unlock(&self) {
+ self.raw().unlock_exclusive()
+ }
- fn get_ptrs(&self) -> Vec<usize> {
- vec![self.as_ref() as *const RwLock<T, R> as usize]
+ unsafe fn read(&self) {
+ self.raw().lock_shared()
}
- unsafe fn lock(&'a self) -> Self::Output {
- self.lock_no_key()
+ unsafe fn try_read(&self) -> bool {
+ self.raw().try_lock_shared()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- self.try_lock_no_key()
+ unsafe fn unlock_read(&self) {
+ self.raw().unlock_shared()
}
}
-unsafe impl<'a, T: 'a, R: RawRwLock + 'a> Lockable<'a> for WriteLock<'a, T, R> {
- type Output = RwLockWriteRef<'a, T, R>;
+unsafe impl<T: Send, R: RawMutex + Send + Sync> Lockable for Mutex<T, R> {
+ type Guard<'g> = MutexRef<'g, T, R> where Self: 'g;
+ type ReadGuard<'g> = MutexRef<'g, T, R> where Self: 'g;
- fn get_ptrs(&self) -> Vec<usize> {
- vec![self.as_ref() as *const RwLock<T, R> as usize]
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ ptrs.push(self);
}
- unsafe fn lock(&'a self) -> Self::Output {
- self.lock_no_key()
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ MutexRef::new(self)
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- self.try_lock_no_key()
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ MutexRef::new(self)
}
}
-unsafe impl<'a, A: Lockable<'a>> Lockable<'a> for (A,) {
- type Output = (A::Output,);
+unsafe impl<T: Send, R: RawRwLock + Send + Sync> Lockable for RwLock<T, R> {
+ type Guard<'g> = RwLockWriteRef<'g, T, R> where Self: 'g;
+
+ type ReadGuard<'g> = RwLockReadRef<'g, T, R> where Self: 'g;
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(1);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ ptrs.push(self);
}
- unsafe fn lock(&'a self) -> Self::Output {
- (self.0.lock(),)
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ RwLockWriteRef::new(self)
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- self.0.try_lock().map(|a| (a,))
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ RwLockReadRef::new(self)
}
}
-unsafe impl<'a, A: Lockable<'a>, B: Lockable<'a>> Lockable<'a> for (A, B) {
- type Output = (A::Output, B::Output);
+unsafe impl<T: Send, R: RawRwLock + Send + Sync> Sharable for RwLock<T, R> {}
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(2);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs.append(&mut self.1.get_ptrs());
- ptrs
- }
+unsafe impl<T: Send, R: RawMutex + Send + Sync> OwnedLockable for Mutex<T, R> {}
- unsafe fn lock(&'a self) -> Self::Output {
- loop {
- let lock0 = self.0.lock();
- let Some(lock1) = self.1.try_lock() else {
- drop(lock0);
- std::thread::yield_now();
- continue;
- };
+unsafe impl<T: Send, R: RawRwLock + Send + Sync> OwnedLockable for RwLock<T, R> {}
- return (lock0, lock1);
- }
+unsafe impl<'l, T: Send, R: RawRwLock + Send + Sync> Lockable for ReadLock<'l, T, R> {
+ type Guard<'g> = RwLockReadRef<'g, T, R> where Self: 'g;
+
+ type ReadGuard<'g> = RwLockReadRef<'g, T, R> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ ptrs.push(self.as_ref());
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let Some(lock0) = self.0.try_lock() else {
- return None;
- };
- let Some(lock1) = self.1.try_lock() else {
- return None;
- };
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ RwLockReadRef::new(self.as_ref())
+ }
- Some((lock0, lock1))
+ unsafe fn read_guard(&self) -> Self::Guard<'_> {
+ RwLockReadRef::new(self.as_ref())
}
}
-unsafe impl<'a, A: Lockable<'a>, B: Lockable<'a>, C: Lockable<'a>> Lockable<'a> for (A, B, C) {
- type Output = (A::Output, B::Output, C::Output);
-
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(3);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs.append(&mut self.1.get_ptrs());
- ptrs.append(&mut self.2.get_ptrs());
- ptrs
- }
-
- unsafe fn lock(&'a self) -> Self::Output {
- loop {
- let lock0 = self.0.lock();
- let Some(lock1) = self.1.try_lock() else {
- drop(lock0);
- std::thread::yield_now();
- continue;
- };
- let Some(lock2) = self.2.try_lock() else {
- drop(lock0);
- drop(lock1);
- std::thread::yield_now();
- continue;
- };
-
- return (lock0, lock1, lock2);
- }
+unsafe impl<'l, T: Send, R: RawRwLock + Send + Sync> Lockable for WriteLock<'l, T, R> {
+ type Guard<'g> = RwLockWriteRef<'g, T, R> where Self: 'g;
+
+ type ReadGuard<'g> = RwLockWriteRef<'g, T, R> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ ptrs.push(self.as_ref());
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let Some(lock0) = self.0.try_lock() else {
- return None;
- };
- let Some(lock1) = self.1.try_lock() else {
- return None;
- };
- let Some(lock2) = self.2.try_lock() else {
- return None;
- };
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ RwLockWriteRef::new(self.as_ref())
+ }
- Some((lock0, lock1, lock2))
+ unsafe fn read_guard(&self) -> Self::Guard<'_> {
+ RwLockWriteRef::new(self.as_ref())
}
}
-unsafe impl<'a, A: Lockable<'a>, B: Lockable<'a>, C: Lockable<'a>, D: Lockable<'a>> Lockable<'a>
- for (A, B, C, D)
-{
- type Output = (A::Output, B::Output, C::Output, D::Output);
-
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(4);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs.append(&mut self.1.get_ptrs());
- ptrs.append(&mut self.2.get_ptrs());
- ptrs.append(&mut self.3.get_ptrs());
- ptrs
- }
-
- unsafe fn lock(&'a self) -> Self::Output {
- loop {
- let lock0 = self.0.lock();
- let Some(lock1) = self.1.try_lock() else {
- drop(lock0);
- std::thread::yield_now();
- continue;
- };
- let Some(lock2) = self.2.try_lock() else {
- drop(lock0);
- drop(lock1);
- std::thread::yield_now();
- continue;
- };
- let Some(lock3) = self.3.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- std::thread::yield_now();
- continue;
- };
-
- return (lock0, lock1, lock2, lock3);
- }
- }
+// Technically, the exclusive locks can also be shared, but there's currently
+// no way to express that. I don't think I want to ever express that.
+unsafe impl<'l, T: Send, R: RawRwLock + Send + Sync> Sharable for ReadLock<'l, T, R> {}
+
+// Because both ReadLock and WriteLock hold references to RwLocks, they can't
+// implement OwnedLockable
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let Some(lock0) = self.0.try_lock() else {
- return None;
- };
- let Some(lock1) = self.1.try_lock() else {
- return None;
- };
- let Some(lock2) = self.2.try_lock() else {
- return None;
- };
- let Some(lock3) = self.3.try_lock() else {
- return None;
- };
+unsafe impl<T: Lockable> Lockable for &T {
+ type Guard<'g> = T::Guard<'g> where Self: 'g;
- Some((lock0, lock1, lock2, lock3))
+ type ReadGuard<'g> = T::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ (*self).get_ptrs(ptrs);
}
-}
-unsafe impl<'a, A: Lockable<'a>, B: Lockable<'a>, C: Lockable<'a>, D: Lockable<'a>, E: Lockable<'a>>
- Lockable<'a> for (A, B, C, D, E)
-{
- type Output = (A::Output, B::Output, C::Output, D::Output, E::Output);
-
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(5);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs.append(&mut self.1.get_ptrs());
- ptrs.append(&mut self.2.get_ptrs());
- ptrs.append(&mut self.3.get_ptrs());
- ptrs.append(&mut self.4.get_ptrs());
- ptrs
- }
-
- unsafe fn lock(&'a self) -> Self::Output {
- loop {
- let lock0 = self.0.lock();
- let Some(lock1) = self.1.try_lock() else {
- drop(lock0);
- std::thread::yield_now();
- continue;
- };
- let Some(lock2) = self.2.try_lock() else {
- drop(lock0);
- drop(lock1);
- std::thread::yield_now();
- continue;
- };
- let Some(lock3) = self.3.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- std::thread::yield_now();
- continue;
- };
- let Some(lock4) = self.4.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- drop(lock3);
- std::thread::yield_now();
- continue;
- };
-
- return (lock0, lock1, lock2, lock3, lock4);
- }
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ (*self).guard()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let Some(lock0) = self.0.try_lock() else {
- return None;
- };
- let Some(lock1) = self.1.try_lock() else {
- return None;
- };
- let Some(lock2) = self.2.try_lock() else {
- return None;
- };
- let Some(lock3) = self.3.try_lock() else {
- return None;
- };
- let Some(lock4) = self.4.try_lock() else {
- return None;
- };
-
- Some((lock0, lock1, lock2, lock3, lock4))
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ (*self).read_guard()
}
}
-unsafe impl<
- 'a,
- A: Lockable<'a>,
- B: Lockable<'a>,
- C: Lockable<'a>,
- D: Lockable<'a>,
- E: Lockable<'a>,
- F: Lockable<'a>,
- > Lockable<'a> for (A, B, C, D, E, F)
-{
- type Output = (
- A::Output,
- B::Output,
- C::Output,
- D::Output,
- E::Output,
- F::Output,
- );
-
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(6);
- ptrs.append(&mut self.0.get_ptrs());
- ptrs.append(&mut self.1.get_ptrs());
- ptrs.append(&mut self.2.get_ptrs());
- ptrs.append(&mut self.3.get_ptrs());
- ptrs.append(&mut self.4.get_ptrs());
- ptrs.append(&mut self.5.get_ptrs());
- ptrs
- }
-
- unsafe fn lock(&'a self) -> Self::Output {
- loop {
- let lock0 = self.0.lock();
- let Some(lock1) = self.1.try_lock() else {
- drop(lock0);
- std::thread::yield_now();
- continue;
- };
- let Some(lock2) = self.2.try_lock() else {
- drop(lock0);
- drop(lock1);
- std::thread::yield_now();
- continue;
- };
- let Some(lock3) = self.3.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- std::thread::yield_now();
- continue;
- };
- let Some(lock4) = self.4.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- drop(lock3);
- std::thread::yield_now();
- continue;
- };
- let Some(lock5) = self.5.try_lock() else {
- drop(lock0);
- drop(lock1);
- drop(lock2);
- drop(lock3);
- drop(lock4);
- std::thread::yield_now();
- continue;
- };
-
- return (lock0, lock1, lock2, lock3, lock4, lock5);
- }
+unsafe impl<T: Sharable> Sharable for &T {}
+
+unsafe impl<T: Lockable> Lockable for &mut T {
+ type Guard<'g> = T::Guard<'g> where Self: 'g;
+
+ type ReadGuard<'g> = T::ReadGuard<'g> where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ (**self).get_ptrs(ptrs)
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let Some(lock0) = self.0.try_lock() else {
- return None;
- };
- let Some(lock1) = self.1.try_lock() else {
- return None;
- };
- let Some(lock2) = self.2.try_lock() else {
- return None;
- };
- let Some(lock3) = self.3.try_lock() else {
- return None;
- };
- let Some(lock4) = self.4.try_lock() else {
- return None;
- };
- let Some(lock5) = self.5.try_lock() else {
- return None;
- };
-
- Some((lock0, lock1, lock2, lock3, lock4, lock5))
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ (**self).guard()
}
-}
-unsafe impl<'a, A: OwnedLockable<'a>> OwnedLockable<'a> for (A,) {}
-unsafe impl<'a, A: OwnedLockable<'a>, B: OwnedLockable<'a>> OwnedLockable<'a> for (A, B) {}
-unsafe impl<'a, A: OwnedLockable<'a>, B: OwnedLockable<'a>, C: OwnedLockable<'a>> OwnedLockable<'a>
- for (A, B, C)
-{
-}
-unsafe impl<
- 'a,
- A: OwnedLockable<'a>,
- B: OwnedLockable<'a>,
- C: OwnedLockable<'a>,
- D: OwnedLockable<'a>,
- > OwnedLockable<'a> for (A, B, C, D)
-{
-}
-unsafe impl<
- 'a,
- A: OwnedLockable<'a>,
- B: OwnedLockable<'a>,
- C: OwnedLockable<'a>,
- D: OwnedLockable<'a>,
- E: OwnedLockable<'a>,
- > OwnedLockable<'a> for (A, B, C, D, E)
-{
-}
-unsafe impl<
- 'a,
- A: OwnedLockable<'a>,
- B: OwnedLockable<'a>,
- C: OwnedLockable<'a>,
- D: OwnedLockable<'a>,
- E: OwnedLockable<'a>,
- F: OwnedLockable<'a>,
- > OwnedLockable<'a> for (A, B, C, D, E, F)
-{
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ (**self).read_guard()
+ }
}
-unsafe impl<'a, T: Lockable<'a>, const N: usize> Lockable<'a> for [T; N] {
- type Output = [T::Output; N];
+unsafe impl<T: Sharable> Sharable for &mut T {}
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(N);
- for lock in self {
- ptrs.append(&mut lock.get_ptrs());
- }
- ptrs
- }
-
- unsafe fn lock(&'a self) -> Self::Output {
- unsafe fn unlock_partial<'a, T: Lockable<'a>, const N: usize>(
- guards: [MaybeUninit<T::Output>; N],
- upto: usize,
- ) {
- for (i, guard) in guards.into_iter().enumerate() {
- if i == upto {
- break;
- }
- drop(guard.assume_init());
+unsafe impl<T: OwnedLockable> OwnedLockable for &mut T {}
+
+/// Implements `Lockable`, `Sharable`, and `OwnedLockable` for tuples
+/// ex: `tuple_impls!(A B C, 0 1 2);`
+macro_rules! tuple_impls {
+ ($($generic:ident)*, $($value:tt)*) => {
+ unsafe impl<$($generic: Lockable,)*> Lockable for ($($generic,)*) {
+ type Guard<'g> = ($($generic::Guard<'g>,)*) where Self: 'g;
+
+ type ReadGuard<'g> = ($($generic::ReadGuard<'g>,)*) where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ self.0.get_ptrs(ptrs);
}
- }
- 'outer: loop {
- let mut outputs = MaybeUninit::<[MaybeUninit<T::Output>; N]>::uninit().assume_init();
- if N == 0 {
- return outputs.map(|mu| mu.assume_init());
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ // It's weird that this works
+ // I don't think any other way of doing it compiles
+ ($(self.$value.guard(),)*)
}
- outputs[0].write(self[0].lock());
- for i in 1..N {
- match self[i].try_lock() {
- Some(guard) => outputs[i].write(guard),
- None => {
- unlock_partial::<T, N>(outputs, i);
- std::thread::yield_now();
- continue 'outer;
- }
- };
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ ($(self.$value.read_guard(),)*)
}
+ }
+
+ unsafe impl<$($generic: Sharable,)*> Sharable for ($($generic,)*) {}
+
+ unsafe impl<$($generic: OwnedLockable,)*> OwnedLockable for ($($generic,)*) {}
+ };
+}
+
+tuple_impls!(A, 0);
+tuple_impls!(A B, 0 1);
+tuple_impls!(A B C, 0 1 2);
+tuple_impls!(A B C D, 0 1 2 3);
+tuple_impls!(A B C D E, 0 1 2 3 4);
+tuple_impls!(A B C D E F, 0 1 2 3 4 5);
+tuple_impls!(A B C D E F G, 0 1 2 3 4 5 6);
+
+unsafe impl<T: Lockable, const N: usize> Lockable for [T; N] {
+ type Guard<'g> = [T::Guard<'g>; N] where Self: 'g;
- return outputs.map(|mu| mu.assume_init());
+ type ReadGuard<'g> = [T::ReadGuard<'g>; N] where Self: 'g;
+
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ for lock in self {
+ lock.get_ptrs(ptrs);
}
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- unsafe fn unlock_partial<'a, T: Lockable<'a>, const N: usize>(
- guards: [MaybeUninit<T::Output>; N],
- upto: usize,
- ) {
- for (i, guard) in guards.into_iter().enumerate() {
- if i == upto {
- break;
- }
- drop(guard.assume_init());
- }
+ unsafe fn guard<'g>(&'g self) -> Self::Guard<'g> {
+ // The MaybeInit helper functions for arrays aren't stable yet, so
+ // we'll just have to implement it ourselves
+ let mut guards = MaybeUninit::<[MaybeUninit<T::Guard<'g>>; N]>::uninit().assume_init();
+ for i in 0..N {
+ guards[i].write(self[i].guard());
}
- let mut outputs = MaybeUninit::<[MaybeUninit<T::Output>; N]>::uninit().assume_init();
- for i in 1..N {
- match self[i].try_lock() {
- Some(guard) => outputs[i].write(guard),
- None => {
- unlock_partial::<T, N>(outputs, i);
- return None;
- }
- };
+ guards.map(|g| g.assume_init())
+ }
+
+ unsafe fn read_guard<'g>(&'g self) -> Self::ReadGuard<'g> {
+ let mut guards = MaybeUninit::<[MaybeUninit<T::ReadGuard<'g>>; N]>::uninit().assume_init();
+ for i in 0..N {
+ guards[i].write(self[i].read_guard());
}
- Some(outputs.map(|mu| mu.assume_init()))
+ guards.map(|g| g.assume_init())
}
}
-unsafe impl<'a, T: Lockable<'a>> Lockable<'a> for Box<[T]> {
- type Output = Box<[T::Output]>;
+unsafe impl<T: Lockable> Lockable for Box<[T]> {
+ type Guard<'g> = Box<[T::Guard<'g>]> where Self: 'g;
+
+ type ReadGuard<'g> = Box<[T::ReadGuard<'g>]> where Self: 'g;
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(self.len());
- for lock in &**self {
- ptrs.append(&mut lock.get_ptrs());
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
+ for lock in self.iter() {
+ lock.get_ptrs(ptrs);
}
- ptrs
}
- unsafe fn lock(&'a self) -> Self::Output {
- if self.is_empty() {
- return Box::new([]);
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ let mut guards = Vec::new();
+ for lock in self.iter() {
+ guards.push(lock.guard());
}
- 'outer: loop {
- let mut outputs = Vec::with_capacity(self.len());
-
- outputs.push(self[0].lock());
- for lock in self.iter().skip(1) {
- match lock.try_lock() {
- Some(guard) => {
- outputs.push(guard);
- }
- None => {
- drop(outputs);
- std::thread::yield_now();
- continue 'outer;
- }
- };
- }
-
- return outputs.into_boxed_slice();
- }
+ guards.into_boxed_slice()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let mut outputs = Vec::with_capacity(self.len());
- for lock in &**self {
- match lock.try_lock() {
- Some(guard) => {
- outputs.push(guard);
- }
- None => return None,
- };
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ let mut guards = Vec::new();
+ for lock in self.iter() {
+ guards.push(lock.read_guard());
}
- Some(outputs.into_boxed_slice())
+ guards.into_boxed_slice()
}
}
-unsafe impl<'a, T: Lockable<'a>> Lockable<'a> for Vec<T> {
- type Output = Vec<T::Output>;
+unsafe impl<T: Lockable> Lockable for Vec<T> {
+ // There's no reason why I'd ever want to extend a list of lock guards
+ type Guard<'g> = Box<[T::Guard<'g>]> where Self: 'g;
+
+ type ReadGuard<'g> = Box<[T::ReadGuard<'g>]> where Self: 'g;
- fn get_ptrs(&self) -> Vec<usize> {
- let mut ptrs = Vec::with_capacity(self.len());
+ fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) {
for lock in self {
- ptrs.append(&mut lock.get_ptrs());
+ lock.get_ptrs(ptrs);
}
- ptrs
}
- unsafe fn lock(&'a self) -> Self::Output {
- if self.is_empty() {
- return Vec::new();
+ unsafe fn guard(&self) -> Self::Guard<'_> {
+ let mut guards = Vec::new();
+ for lock in self {
+ guards.push(lock.guard());
}
- 'outer: loop {
- let mut outputs = Vec::with_capacity(self.len());
-
- outputs.push(self[0].lock());
- for lock in self.iter().skip(1) {
- match lock.try_lock() {
- Some(guard) => {
- outputs.push(guard);
- }
- None => {
- drop(outputs);
- std::thread::yield_now();
- continue 'outer;
- }
- };
- }
-
- return outputs;
- }
+ guards.into_boxed_slice()
}
- unsafe fn try_lock(&'a self) -> Option<Self::Output> {
- let mut outputs = Vec::with_capacity(self.len());
+ unsafe fn read_guard(&self) -> Self::ReadGuard<'_> {
+ let mut guards = Vec::new();
for lock in self {
- match lock.try_lock() {
- Some(guard) => {
- outputs.push(guard);
- }
- None => return None,
- };
+ guards.push(lock.read_guard());
}
- Some(outputs)
+ guards.into_boxed_slice()
}
}
-unsafe impl<'a, T: OwnedLockable<'a>, const N: usize> OwnedLockable<'a> for [T; N] {}
-unsafe impl<'a, T: OwnedLockable<'a>> OwnedLockable<'a> for Box<[T]> {}
-unsafe impl<'a, T: OwnedLockable<'a>> OwnedLockable<'a> for Vec<T> {}
+// I'd make a generic impl<T: Lockable, I: IntoIterator<Item=T>> Lockable for I
+// but I think that'd require sealing up this trait
+
+unsafe impl<T: Sharable, const N: usize> Sharable for [T; N] {}
+unsafe impl<T: Sharable> Sharable for Box<[T]> {}
+unsafe impl<T: Sharable> Sharable for Vec<T> {}
+
+unsafe impl<T: OwnedLockable, const N: usize> OwnedLockable for [T; N] {}
+unsafe impl<T: OwnedLockable> OwnedLockable for Box<[T]> {}
+unsafe impl<T: OwnedLockable> OwnedLockable for Vec<T> {}
diff --git a/src/mutex.rs b/src/mutex.rs
index cef338e..b30c2b1 100644
--- a/src/mutex.rs
+++ b/src/mutex.rs
@@ -49,32 +49,11 @@ pub struct MutexRef<'a, T: ?Sized + 'a, R: RawMutex>(
///
/// [`lock`]: `Mutex::lock`
/// [`try_lock`]: `Mutex::try_lock`
+
+// This is the most lifetime-intensive thing I've ever written. Can I graduate
+// from borrow checker university now?
pub struct MutexGuard<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable + 'key, R: RawMutex> {
- mutex: MutexRef<'a, T, R>,
+ mutex: MutexRef<'a, T, R>, // this way we don't need to re-implement Drop
thread_key: Key,
_phantom: PhantomData<&'key ()>,
}
-
-struct MutexLockFuture<'a, T: ?Sized + 'a, R: RawMutex> {
- mutex: &'a Mutex<T, R>,
- key: Option<crate::ThreadKey>,
-}
-
-impl<'a, T: ?Sized + 'a, R: RawMutex> std::future::Future for MutexLockFuture<'a, T, R> {
- type Output = MutexGuard<'a, 'a, T, crate::ThreadKey, R>;
-
- fn poll(
- mut self: std::pin::Pin<&mut Self>,
- cx: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Self::Output> {
- match unsafe { self.mutex.try_lock_no_key() } {
- Some(guard) => std::task::Poll::Ready(unsafe {
- MutexGuard::new(guard.0, self.key.take().unwrap())
- }),
- None => {
- cx.waker().wake_by_ref();
- std::task::Poll::Pending
- }
- }
- }
-}
diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs
index c7f25e4..f9324ad 100644
--- a/src/mutex/guard.rs
+++ b/src/mutex/guard.rs
@@ -1,3 +1,4 @@
+use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
@@ -7,6 +8,21 @@ use crate::key::Keyable;
use super::{Mutex, MutexGuard, MutexRef};
+// This makes things slightly easier because now you can use
+// `println!("{guard}")` instead of `println!("{}", *guard)`. I wonder if I
+// should implement some other standard library traits like this too?
+impl<'a, T: Debug + ?Sized + 'a, R: RawMutex> Debug for MutexRef<'a, T, R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl<'a, T: Display + ?Sized + 'a, R: RawMutex> Display for MutexRef<'a, T, R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
impl<'a, T: ?Sized + 'a, R: RawMutex> Drop for MutexRef<'a, T, R> {
fn drop(&mut self) {
// safety: this guard is being destroyed, so the data cannot be
@@ -35,6 +51,49 @@ impl<'a, T: ?Sized + 'a, R: RawMutex> DerefMut for MutexRef<'a, T, R> {
}
}
+impl<'a, T: ?Sized + 'a, R: RawMutex> AsRef<T> for MutexRef<'a, T, R> {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<'a, T: ?Sized + 'a, R: RawMutex> AsMut<T> for MutexRef<'a, T, R> {
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
+impl<'a, T: ?Sized + 'a, R: RawMutex> MutexRef<'a, T, R> {
+ /// Creates a reference to the underlying data of a mutex without
+ /// attempting to lock it or take ownership of the key.
+
+ // This might be useful to export, because it makes it easier to express
+ // the concept of: "Get the data out the mutex but don't lock it or take
+ // the key". But it's also quite dangerous to drop.
+ pub(crate) unsafe fn new(mutex: &'a Mutex<T, R>) -> Self {
+ Self(mutex, PhantomData)
+ }
+}
+
+// it's kinda annoying to re-implement some of this stuff on guards
+// there's nothing i can do about that
+
+impl<'a, 'key, T: Debug + ?Sized + 'a, Key: Keyable + 'key, R: RawMutex> Debug
+ for MutexGuard<'a, 'key, T, Key, R>
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl<'a, 'key, T: Display + ?Sized + 'a, Key: Keyable + 'key, R: RawMutex> Display
+ for MutexGuard<'a, 'key, T, Key, R>
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawMutex> Deref
for MutexGuard<'a, 'key, T, Key, R>
{
@@ -53,6 +112,22 @@ impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawMutex> DerefMut
}
}
+impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawMutex> AsRef<T>
+ for MutexGuard<'a, 'key, T, Key, R>
+{
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawMutex> AsMut<T>
+ for MutexGuard<'a, 'key, T, Key, R>
+{
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawMutex> MutexGuard<'a, 'key, T, Key, R> {
/// Create a guard to the given mutex. Undefined if multiple guards to the
/// same mutex exist at once.
diff --git a/src/mutex/mutex.rs b/src/mutex/mutex.rs
index 917ab78..89dfef9 100644
--- a/src/mutex/mutex.rs
+++ b/src/mutex/mutex.rs
@@ -24,11 +24,22 @@ impl<T, R: RawMutex> Mutex<T, R> {
data: UnsafeCell::new(data),
}
}
-}
-impl<T: ?Sized + Default, R: RawMutex> Default for Mutex<T, R> {
- fn default() -> Self {
- Self::new(T::default())
+ /// Returns the raw underlying mutex.
+ ///
+ /// Note that you will most likely need to import the [`RawMutex`] trait
+ /// from `lock_api` to be able to call functions on the raw mutex.
+ ///
+ /// # Safety
+ ///
+ /// This method is unsafe because it allows unlocking a mutex while still
+ /// holding a reference to a [`MutexGuard`], and locking a mutex without
+ /// holding the [`ThreadKey`].
+ ///
+ /// [`ThreadKey`]: `crate::ThreadKey`
+ #[must_use]
+ pub const unsafe fn raw(&self) -> &R {
+ &self.raw
}
}
@@ -37,6 +48,7 @@ impl<T: ?Sized + Debug, R: RawMutex> Debug for Mutex<T, R> {
// safety: this is just a try lock, and the value is dropped
// immediately after, so there's no risk of blocking ourselves
// or any other threads
+ // when i implement try_clone this code will become less unsafe
if let Some(value) = unsafe { self.try_lock_no_key() } {
f.debug_struct("Mutex").field("data", &&*value).finish()
} else {
@@ -54,12 +66,21 @@ impl<T: ?Sized + Debug, R: RawMutex> Debug for Mutex<T, R> {
}
}
+impl<T: ?Sized + Default, R: RawMutex> Default for Mutex<T, R> {
+ fn default() -> Self {
+ Self::new(T::default())
+ }
+}
+
impl<T, R: RawMutex> From<T> for Mutex<T, R> {
fn from(value: T) -> Self {
Self::new(value)
}
}
+// We don't need a `get_mut` because we don't have mutex poisoning. Hurray!
+// This is safe because you can't have a mutable reference to the lock if it's
+// locked. Being locked requires an immutable reference because of the guard.
impl<T: ?Sized, R> AsMut<T> for Mutex<T, R> {
fn as_mut(&mut self) -> &mut T {
self.get_mut()
@@ -138,15 +159,6 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> {
}
}
- /// Lock without a [`ThreadKey`]. You must exclusively own the
- /// [`ThreadKey`] as long as the [`MutexRef`] is alive. This may cause
- /// deadlock if called multiple times without unlocking first.
- pub(crate) unsafe fn lock_no_key(&self) -> MutexRef<'_, T, R> {
- self.raw.lock();
-
- MutexRef(self, PhantomData)
- }
-
/// Attempts to lock the `Mutex` without blocking.
///
/// # Errors
diff --git a/src/rwlock.rs b/src/rwlock.rs
index 7fb8c7a..8f1ba8f 100644
--- a/src/rwlock.rs
+++ b/src/rwlock.rs
@@ -22,7 +22,7 @@ pub type ParkingRwLock<T> = RwLock<T, parking_lot::RawRwLock>;
/// A reader-writer lock
///
/// This type of lock allows a number of readers or at most one writer at any
-/// point in time. The write portion of thislock typically allows modification
+/// point in time. The write portion of this lock typically allows modification
/// of the underlying data (exclusive access) and the read portion of this lock
/// typically allows for read-only access (shared access).
///
@@ -56,7 +56,8 @@ pub struct RwLock<T: ?Sized, R> {
/// that only read access is needed to the data.
///
/// [`LockCollection`]: `crate::LockCollection`
-pub struct ReadLock<'a, T: ?Sized, R>(&'a RwLock<T, R>);
+#[repr(transparent)]
+pub struct ReadLock<'l, T: ?Sized, R>(&'l RwLock<T, R>);
/// Grants write access to an [`RwLock`]
///
@@ -64,7 +65,8 @@ pub struct ReadLock<'a, T: ?Sized, R>(&'a RwLock<T, R>);
/// that write access is needed to the data.
///
/// [`LockCollection`]: `crate::LockCollection`
-pub struct WriteLock<'a, T: ?Sized, R>(&'a RwLock<T, R>);
+#[repr(transparent)]
+pub struct WriteLock<'l, T: ?Sized, R>(&'l RwLock<T, R>);
/// RAII structure that unlocks the shared read access to a [`RwLock`]
pub struct RwLockReadRef<'a, T: ?Sized, R: RawRwLock>(
diff --git a/src/rwlock/read_guard.rs b/src/rwlock/read_guard.rs
index 532a6e7..1eb8bfc 100644
--- a/src/rwlock/read_guard.rs
+++ b/src/rwlock/read_guard.rs
@@ -1,3 +1,4 @@
+use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use std::ops::Deref;
@@ -7,6 +8,18 @@ use crate::key::Keyable;
use super::{RwLock, RwLockReadGuard, RwLockReadRef};
+impl<'a, T: Debug + ?Sized + 'a, R: RawRwLock> Debug for RwLockReadRef<'a, T, R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl<'a, T: Display + ?Sized + 'a, R: RawRwLock> Display for RwLockReadRef<'a, T, R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
impl<'a, T: ?Sized + 'a, R: RawRwLock> Deref for RwLockReadRef<'a, T, R> {
type Target = T;
@@ -18,6 +31,12 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Deref for RwLockReadRef<'a, T, R> {
}
}
+impl<'a, T: ?Sized + 'a, R: RawRwLock> AsRef<T> for RwLockReadRef<'a, T, R> {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockReadRef<'a, T, R> {
fn drop(&mut self) {
// safety: this guard is being destroyed, so the data cannot be
@@ -26,6 +45,31 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockReadRef<'a, T, R> {
}
}
+impl<'a, T: ?Sized + 'a, R: RawRwLock> RwLockReadRef<'a, T, R> {
+ /// Creates an immutable reference for the underlying data of an [`RwLock`]
+ /// without locking it or taking ownership of the key.
+ #[must_use]
+ pub(crate) unsafe fn new(mutex: &'a RwLock<T, R>) -> Self {
+ Self(mutex, PhantomData)
+ }
+}
+
+impl<'a, 'key, T: Debug + ?Sized + 'a, Key: Keyable + 'key, R: RawRwLock> Debug
+ for RwLockReadGuard<'a, 'key, T, Key, R>
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&**self, f)
+ }
+}
+
+impl<'a, 'key, T: Display + ?Sized + 'a, Key: Keyable + 'key, R: RawRwLock> Display
+ for RwLockReadGuard<'a, 'key, T, Key, R>
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&**self, f)
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> Deref
for RwLockReadGuard<'a, 'key, T, Key, R>
{
@@ -36,6 +80,14 @@ impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> Deref
}
}
+impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> AsRef<T>
+ for RwLockReadGuard<'a, 'key, T, Key, R>
+{
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock>
RwLockReadGuard<'a, 'key, T, Key, R>
{
diff --git a/src/rwlock/read_lock.rs b/src/rwlock/read_lock.rs
index 176fc01..4f2bc86 100644
--- a/src/rwlock/read_lock.rs
+++ b/src/rwlock/read_lock.rs
@@ -6,7 +6,7 @@ use crate::key::Keyable;
use super::{ReadLock, RwLock, RwLockReadGuard, RwLockReadRef};
-impl<'a, T: ?Sized + Debug, R: RawRwLock> Debug for ReadLock<'a, T, R> {
+impl<'l, T: ?Sized + Debug, R: RawRwLock> Debug for ReadLock<'l, T, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// safety: this is just a try lock, and the value is dropped
// immediately after, so there's no risk of blocking ourselves
@@ -28,19 +28,19 @@ impl<'a, T: ?Sized + Debug, R: RawRwLock> Debug for ReadLock<'a, T, R> {
}
}
-impl<'a, T: ?Sized, R> From<&'a RwLock<T, R>> for ReadLock<'a, T, R> {
- fn from(value: &'a RwLock<T, R>) -> Self {
+impl<'l, T, R> From<&'l RwLock<T, R>> for ReadLock<'l, T, R> {
+ fn from(value: &'l RwLock<T, R>) -> Self {
Self::new(value)
}
}
-impl<'a, T: ?Sized, R> AsRef<RwLock<T, R>> for ReadLock<'a, T, R> {
+impl<'l, T: ?Sized, R> AsRef<RwLock<T, R>> for ReadLock<'l, T, R> {
fn as_ref(&self) -> &RwLock<T, R> {
self.0
}
}
-impl<'a, T: ?Sized, R> ReadLock<'a, T, R> {
+impl<'l, T, R> ReadLock<'l, T, R> {
/// Creates a new `ReadLock` which accesses the given [`RwLock`]
///
/// # Examples
@@ -52,12 +52,12 @@ impl<'a, T: ?Sized, R> ReadLock<'a, T, R> {
/// let read_lock = ReadLock::new(&lock);
/// ```
#[must_use]
- pub const fn new(rwlock: &'a RwLock<T, R>) -> Self {
+ pub const fn new(rwlock: &'l RwLock<T, R>) -> Self {
Self(rwlock)
}
}
-impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> {
+impl<'l, T: ?Sized, R: RawRwLock> ReadLock<'l, T, R> {
/// Locks the underlying [`RwLock`] with shared read access, blocking the
/// current thread until it can be acquired.
pub fn lock<'s, 'key: 's, Key: Keyable + 'key>(
@@ -67,12 +67,6 @@ impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> {
self.0.read(key)
}
- /// Creates a shared lock without a key. Locking this without exclusive
- /// access to the key is undefined behavior.
- pub(crate) unsafe fn lock_no_key(&self) -> RwLockReadRef<'_, T, R> {
- self.0.read_no_key()
- }
-
/// Attempts to acquire the underlying [`RwLock`] with shared read access
/// without blocking.
pub fn try_lock<'s, 'key: 's, Key: Keyable + 'key>(
@@ -88,7 +82,7 @@ impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> {
self.0.try_read_no_key()
}
- /// Immediately drops the guard, and consequentlyreleases the shared lock
+ /// Immediately drops the guard, and consequently releases the shared lock
/// on the underlying [`RwLock`].
pub fn unlock<'key, Key: Keyable + 'key>(guard: RwLockReadGuard<'_, 'key, T, Key, R>) -> Key {
RwLock::unlock_read(guard)
diff --git a/src/rwlock/rwlock.rs b/src/rwlock/rwlock.rs
index dc5ab30..5bff5a3 100644
--- a/src/rwlock/rwlock.rs
+++ b/src/rwlock/rwlock.rs
@@ -5,7 +5,7 @@ use lock_api::RawRwLock;
use crate::key::Keyable;
-use super::{RwLock, RwLockReadGuard, RwLockReadRef, RwLockWriteGuard, RwLockWriteRef};
+use super::{RwLock, RwLockReadGuard, RwLockReadRef, RwLockWriteGuard};
impl<T, R: RawRwLock> RwLock<T, R> {
/// Creates a new instance of an `RwLock<T>` which is unlocked.
@@ -24,11 +24,19 @@ impl<T, R: RawRwLock> RwLock<T, R> {
raw: R::INIT,
}
}
-}
-impl<T: ?Sized + Default, R: RawRwLock> Default for RwLock<T, R> {
- fn default() -> Self {
- Self::new(T::default())
+ /// Returns the underlying raw reader-writer lock object.
+ ///
+ /// Note that you will most likely need to import the [`RawRwLock`] trait
+ /// from `lock_api` to be able to call functions on the raw reader-writer
+ /// lock.
+ ///
+ /// # Safety
+ ///
+ /// This method is unsafe because it allows unlocking a mutex while
+ /// still holding a reference to a lock guard.
+ pub const unsafe fn raw(&self) -> &R {
+ &self.raw
}
}
@@ -54,6 +62,12 @@ impl<T: ?Sized + Debug, R: RawRwLock> Debug for RwLock<T, R> {
}
}
+impl<T: ?Sized + Default, R: RawRwLock> Default for RwLock<T, R> {
+ fn default() -> Self {
+ Self::new(T::default())
+ }
+}
+
impl<T, R: RawRwLock> From<T> for RwLock<T, R> {
fn from(value: T) -> Self {
Self::new(value)
@@ -62,7 +76,7 @@ impl<T, R: RawRwLock> From<T> for RwLock<T, R> {
impl<T: ?Sized, R> AsMut<T> for RwLock<T, R> {
fn as_mut(&mut self) -> &mut T {
- self.get_mut()
+ self.data.get_mut()
}
}
@@ -82,32 +96,12 @@ impl<T, R> RwLock<T, R> {
/// }
/// assert_eq!(lock.into_inner(), "modified");
/// ```
+ #[must_use]
pub fn into_inner(self) -> T {
self.data.into_inner()
}
}
-impl<T: ?Sized, R> RwLock<T, R> {
- /// Returns a mutable reference to the underlying data.
- ///
- /// Since this call borrows the `RwLock` mutably, no actual locking needs
- /// to take place. The mutable borrow statically guarantees no locks exist.
- ///
- /// # Examples
- ///
- /// ```
- /// use happylock::{RwLock, ThreadKey};
- ///
- /// let key = ThreadKey::get().unwrap();
- /// let mut lock = RwLock::new(0);
- /// *lock.get_mut() = 10;
- /// assert_eq!(*lock.read(key), 10);
- /// ```
- pub fn get_mut(&mut self) -> &mut T {
- self.data.get_mut()
- }
-}
-
impl<T: ?Sized, R: RawRwLock> RwLock<T, R> {
/// Locks this `RwLock` with shared read access, blocking the current
/// thread until it can be acquired.
@@ -155,15 +149,6 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> {
}
}
- /// Creates a shared lock without a key. Locking this without exclusive
- /// access to the key is undefined behavior.
- pub(crate) unsafe fn read_no_key(&self) -> RwLockReadRef<'_, T, R> {
- self.raw.lock_shared();
-
- // safety: the lock is locked first
- RwLockReadRef(self, PhantomData)
- }
-
/// Attempts to acquire this `RwLock` with shared read access without
/// blocking.
///
@@ -246,19 +231,10 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> {
}
}
- /// Creates an exclusive lock without a key. Locking this without exclusive
- /// access to the key is undefined behavior.
- pub(crate) unsafe fn write_no_key(&self) -> RwLockWriteRef<'_, T, R> {
- self.raw.lock_exclusive();
-
- // safety: the lock is locked first
- RwLockWriteRef(self, PhantomData)
- }
-
/// Attempts to lock this `RwLock` with exclusive write access.
///
/// This function does not block. If the lock could not be acquired at this
- /// time, then `None` is returned. Otherwise an RAII guard is returned
+ /// time, then `None` is returned. Otherwise, an RAII guard is returned
/// which will release the lock when it is dropped.
///
/// This function does not provide any guarantees with respect to the
@@ -290,17 +266,6 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> {
}
}
- /// Attempts to create an exclusive lock without a key. Locking this
- /// without exclusive access to the key is undefined behavior.
- pub(crate) unsafe fn try_write_no_key(&self) -> Option<RwLockWriteRef<'_, T, R>> {
- if self.raw.try_lock_exclusive() {
- // safety: the lock is locked first
- Some(RwLockWriteRef(self, PhantomData))
- } else {
- None
- }
- }
-
/// Unlocks shared access on the `RwLock`. This is undefined behavior is
/// the data is still accessible.
pub(super) unsafe fn force_unlock_read(&self) {
diff --git a/src/rwlock/write_guard.rs b/src/rwlock/write_guard.rs
index 6549822..5b41c99 100644
--- a/src/rwlock/write_guard.rs
+++ b/src/rwlock/write_guard.rs
@@ -27,6 +27,18 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> DerefMut for RwLockWriteRef<'a, T, R> {
}
}
+impl<'a, T: ?Sized + 'a, R: RawRwLock> AsRef<T> for RwLockWriteRef<'a, T, R> {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<'a, T: ?Sized + 'a, R: RawRwLock> AsMut<T> for RwLockWriteRef<'a, T, R> {
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockWriteRef<'a, T, R> {
fn drop(&mut self) {
// safety: this guard is being destroyed, so the data cannot be
@@ -35,6 +47,15 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockWriteRef<'a, T, R> {
}
}
+impl<'a, T: ?Sized + 'a, R: RawRwLock> RwLockWriteRef<'a, T, R> {
+ /// Creates a reference to the underlying data of an [`RwLock`] without
+ /// locking or taking ownership of the key.
+ #[must_use]
+ pub(crate) unsafe fn new(mutex: &'a RwLock<T, R>) -> Self {
+ Self(mutex, PhantomData)
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> Deref
for RwLockWriteGuard<'a, 'key, T, Key, R>
{
@@ -53,6 +74,22 @@ impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> DerefMut
}
}
+impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> AsRef<T>
+ for RwLockWriteGuard<'a, 'key, T, Key, R>
+{
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock> AsMut<T>
+ for RwLockWriteGuard<'a, 'key, T, Key, R>
+{
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
impl<'a, 'key: 'a, T: ?Sized + 'a, Key: Keyable, R: RawRwLock>
RwLockWriteGuard<'a, 'key, T, Key, R>
{
diff --git a/src/rwlock/write_lock.rs b/src/rwlock/write_lock.rs
index d7333ae..15eaacc 100644
--- a/src/rwlock/write_lock.rs
+++ b/src/rwlock/write_lock.rs
@@ -4,14 +4,16 @@ use lock_api::RawRwLock;
use crate::key::Keyable;
-use super::{RwLock, RwLockWriteGuard, RwLockWriteRef, WriteLock};
+use super::{RwLock, RwLockWriteGuard, WriteLock};
-impl<'a, T: ?Sized + Debug, R: RawRwLock> Debug for WriteLock<'a, T, R> {
+impl<'l, T: ?Sized + Debug, R: RawRwLock> Debug for WriteLock<'l, T, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// safety: this is just a try lock, and the value is dropped
// immediately after, so there's no risk of blocking ourselves
// or any other threads
- if let Some(value) = unsafe { self.try_lock_no_key() } {
+ // It makes zero sense to try using an exclusive lock for this, so this
+ // is the only time when WriteLock does a read.
+ if let Some(value) = unsafe { self.0.try_read_no_key() } {
f.debug_struct("WriteLock").field("data", &&*value).finish()
} else {
struct LockedPlaceholder;
@@ -21,26 +23,26 @@ impl<'a, T: ?Sized + Debug, R: RawRwLock> Debug for WriteLock<'a, T, R> {
}
}
- f.debug_struct("ReadLock")
+ f.debug_struct("WriteLock")
.field("data", &LockedPlaceholder)
.finish()
}
}
}
-impl<'a, T: ?Sized, R> From<&'a RwLock<T, R>> for WriteLock<'a, T, R> {
- fn from(value: &'a RwLock<T, R>) -> Self {
+impl<'l, T, R> From<&'l RwLock<T, R>> for WriteLock<'l, T, R> {
+ fn from(value: &'l RwLock<T, R>) -> Self {
Self::new(value)
}
}
-impl<'a, T: ?Sized, R> AsRef<RwLock<T, R>> for WriteLock<'a, T, R> {
+impl<'l, T: ?Sized, R> AsRef<RwLock<T, R>> for WriteLock<'l, T, R> {
fn as_ref(&self) -> &RwLock<T, R> {
self.0
}
}
-impl<'a, T: ?Sized, R> WriteLock<'a, T, R> {
+impl<'l, T, R> WriteLock<'l, T, R> {
/// Creates a new `WriteLock` which accesses the given [`RwLock`]
///
/// # Examples
@@ -52,12 +54,12 @@ impl<'a, T: ?Sized, R> WriteLock<'a, T, R> {
/// let write_lock = WriteLock::new(&lock);
/// ```
#[must_use]
- pub const fn new(rwlock: &'a RwLock<T, R>) -> Self {
+ pub const fn new(rwlock: &'l RwLock<T, R>) -> Self {
Self(rwlock)
}
}
-impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> {
+impl<'l, T: ?Sized, R: RawRwLock> WriteLock<'l, T, R> {
/// Locks the underlying [`RwLock`] with exclusive write access, blocking
/// the current until it can be acquired.
pub fn lock<'s, 'key: 's, Key: Keyable + 'key>(
@@ -67,12 +69,6 @@ impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> {
self.0.write(key)
}
- /// Creates an exclusive lock without a key. Locking this without exclusive
- /// access to the key is undefined behavior.
- pub(crate) unsafe fn lock_no_key(&self) -> RwLockWriteRef<'_, T, R> {
- self.0.write_no_key()
- }
-
/// Attempts to lock the underlying [`RwLock`] with exclusive write access.
pub fn try_lock<'s, 'key: 's, Key: Keyable + 'key>(
&'s self,
@@ -81,11 +77,8 @@ impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> {
self.0.try_write(key)
}
- /// Attempts to create an exclusive lock without a key. Locking this
- /// without exclusive access to the key is undefined behavior.
- pub(crate) unsafe fn try_lock_no_key(&self) -> Option<RwLockWriteRef<'_, T, R>> {
- self.0.try_write_no_key()
- }
+ // There's no `try_lock_no_key`. Instead, `try_read_no_key` is called on
+ // the referenced `RwLock`.
/// Immediately drops the guard, and consequently releases the exclusive
/// lock.