diff --git a/Cargo.lock b/Cargo.lock index ebdc0ae..a5e8fac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,7 +522,7 @@ dependencies = [ [[package]] name = "surelock" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bolero", "lock_api", diff --git a/Cargo.toml b/Cargo.toml index d5ba32b..d461250 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "surelock" -version = "0.1.0" +version = "0.2.0" description = "Deadlock-free locks for Rust with compile time guarantees, incremental locks, and atomic lock sets." readme = "README.md" diff --git a/README.md b/README.md index ee0a568..14f031b 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,14 @@ use surelock::mutex::Mutex; // Default level (Level<0>), default backend (StdMutex) let config: Mutex = Mutex::new(10); +// `new` is generic over level + backend, and those defaults are only +// applied when the type is named -- not in positions like `Arc::new(...)`, +// which fail with `error[E0283]`. Name the type (the compiler suggests +// the turbofish), or annotate the binding: +use std::sync::Arc; +let shared = Arc::new(Mutex::::new(0)); // turbofish +let owned: Arc> = Arc::new(Mutex::new(0)); // or annotate + // Auto-incrementing level via new_higher let account = Mutex::new_higher(20u32, &config); // Level<1> let txn = Mutex::new_higher(30u32, &account); // Level<2> @@ -263,6 +271,15 @@ let (result, key) = key.subscope(|inner_key| { /* ... */ }); Two ways to enter a lock scope: +> [!IMPORTANT] +> Pick **one** entry mechanism per thread. `KeyHandle` and `lock_scope` / +> `try_lock_scope` share a single per-thread slot (this is what enforces +> one key per thread). A live `KeyHandle` makes a subsequent `lock_scope` +> panic (and `try_lock_scope` return `ClaimError::AlreadyClaimed`), and +> vice versa. Drop the `KeyHandle` before switching to `lock_scope`, or +> just stick to one style. For nesting *within* a scope, use +> `key.subscope(...)`, never a second entry point. + **`KeyHandle`** (recommended) -- static nesting prevention via `&mut self`. Works on all targets including `no_std`: ```rust diff --git a/src/acquirable.rs b/src/acquirable.rs index 3865450..df36583 100644 --- a/src/acquirable.rs +++ b/src/acquirable.rs @@ -5,6 +5,7 @@ pub mod tuples; use alloc::vec::Vec; +use core::marker::PhantomData; use crate::{ id::LockId, @@ -30,7 +31,7 @@ use crate::{ /// readers, exclusive /// [`RwLockWriteGuard`](crate::rw_lock::guard::RwLockWriteGuard) for /// writers. -pub trait MutexRef<'a> { +pub trait MutexRef<'scope, 'a> { /// The data type guarded by the lock. type Data: 'a; @@ -52,105 +53,115 @@ pub trait MutexRef<'a> { fn try_lock_ref(&'a self) -> Option; } -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> for &'a Mutex { +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'scope, 'a> + for &'a Mutex +{ type Data = T; type Lvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { Mutex::id(self) } - fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + fn lock_ref(&'a self) -> MutexGuard<'scope, 'a, R, T> { MutexGuard { data: &self.data, _raw_guard: self.raw.lock(), + _scope: PhantomData, } } - fn try_lock_ref(&'a self) -> Option> { + fn try_lock_ref(&'a self) -> Option> { self.raw.try_lock().map(|raw_guard| MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } #[cfg(target_has_atomic = "ptr")] -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'scope, 'a> for &'a alloc::sync::Arc> { type Data = T; type Lvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { Mutex::id(self) } - fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + fn lock_ref(&'a self) -> MutexGuard<'scope, 'a, R, T> { MutexGuard { data: &self.data, _raw_guard: self.raw.lock(), + _scope: PhantomData, } } - fn try_lock_ref(&'a self) -> Option> { + fn try_lock_ref(&'a self) -> Option> { self.raw.try_lock().map(|raw_guard| MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'scope, 'a> for &'a alloc::rc::Rc> { type Data = T; type Lvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { Mutex::id(self) } - fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + fn lock_ref(&'a self) -> MutexGuard<'scope, 'a, R, T> { MutexGuard { data: &self.data, _raw_guard: self.raw.lock(), + _scope: PhantomData, } } - fn try_lock_ref(&'a self) -> Option> { + fn try_lock_ref(&'a self) -> Option> { self.raw.try_lock().map(|raw_guard| MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'scope, 'a> for &'a alloc::boxed::Box> { type Data = T; type Lvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { Mutex::id(self) } - fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + fn lock_ref(&'a self) -> MutexGuard<'scope, 'a, R, T> { MutexGuard { data: &self.data, _raw_guard: self.raw.lock(), + _scope: PhantomData, } } - fn try_lock_ref(&'a self) -> Option> { + fn try_lock_ref(&'a self) -> Option> { self.raw.try_lock().map(|raw_guard| MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } @@ -195,7 +206,7 @@ impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> message = "`{Self}` cannot be used as a lock group", note = "use `&Mutex` or tuples of mutex references to implement `Acquirable`" )] -pub trait Acquirable<'a> { +pub trait Acquirable<'scope, 'a> { /// The minimum level in this collection. /// /// Used to check that all locks are above the key's current @@ -237,10 +248,12 @@ pub trait Acquirable<'a> { } // Used by key.lock_with(&mutex, |guard| ...) -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for Mutex { +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'scope, 'a> + for Mutex +{ type MinLvl = Lvl; type MaxLvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn collect_ids(&self, out: &mut Vec) { out.push(self.id()); @@ -251,6 +264,7 @@ impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for Mutex Acquirable<'a> for Mutex Acquirable<'a> for &'a Mutex { +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'scope, 'a> + for &'a Mutex +{ type MinLvl = Lvl; type MaxLvl = Lvl; - type Guard = MutexGuard<'a, R, T>; + type Guard = MutexGuard<'scope, 'a, R, T>; fn collect_ids(&self, out: &mut Vec) { out.push(self.id()); @@ -277,6 +294,7 @@ impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for &'a Mutex Acquirable<'a> for &'a Mutex Acquirable<'a> for &'a Mutex> Acquirable<'a> for alloc::sync::Arc { +impl<'scope, 'a, T: Acquirable<'scope, 'a>> Acquirable<'scope, 'a> for alloc::sync::Arc { type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; type Guard = T::Guard; @@ -312,7 +331,7 @@ impl<'a, T: Acquirable<'a>> Acquirable<'a> for alloc::sync::Arc { } } -impl<'a, T: Acquirable<'a>> Acquirable<'a> for alloc::rc::Rc { +impl<'scope, 'a, T: Acquirable<'scope, 'a>> Acquirable<'scope, 'a> for alloc::rc::Rc { type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; type Guard = T::Guard; @@ -330,7 +349,7 @@ impl<'a, T: Acquirable<'a>> Acquirable<'a> for alloc::rc::Rc { } } -impl<'a, T: Acquirable<'a>> Acquirable<'a> for alloc::boxed::Box { +impl<'scope, 'a, T: Acquirable<'scope, 'a>> Acquirable<'scope, 'a> for alloc::boxed::Box { type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; type Guard = T::Guard; @@ -351,10 +370,12 @@ impl<'a, T: Acquirable<'a>> Acquirable<'a> for alloc::boxed::Box { // Slice of mutexes -- same type, same level, dynamic length. // Guards are returned as a Vec. #[allow(clippy::indexing_slicing)] // sorted_indices are valid (produced by LockSet) -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for &'a [Mutex] { +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'scope, 'a> + for &'a [Mutex] +{ type MinLvl = Lvl; type MaxLvl = Lvl; - type Guard = Vec>; + type Guard = Vec>; fn collect_ids(&self, out: &mut Vec) { for m in *self { @@ -370,6 +391,7 @@ impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for &'a [Mutex Acquirable<'a> for &'a [Mutex> = Vec::with_capacity(sorted_indices.len()); + let mut guards: Vec> = + Vec::with_capacity(sorted_indices.len()); for &i in sorted_indices { let raw_guard = self[i].raw.try_lock()?; guards.push(MutexGuard { data: &self[i].data, _raw_guard: raw_guard, + _scope: PhantomData, }); } Some(guards) diff --git a/src/acquirable/tuples.rs b/src/acquirable/tuples.rs index c9d8e85..1956e14 100644 --- a/src/acquirable/tuples.rs +++ b/src/acquirable/tuples.rs @@ -21,10 +21,10 @@ use crate::{ // -- 2-tuple: multi-level, different T / backend per element -- -impl<'a, A, B> Acquirable<'a> for (A, B) +impl<'scope, 'a, A, B> Acquirable<'scope, 'a> for (A, B) where - A: MutexRef<'a>, - B: MutexRef<'a>, + A: MutexRef<'scope, 'a>, + B: MutexRef<'scope, 'a>, A::Lvl: MinLevel + MaxLevel, { type MinLvl = >::Min; @@ -86,11 +86,11 @@ where macro_rules! impl_lockable_tuple_same_level { ($n:literal: $first_idx:tt $first_T:ident $first_G:ident $(, $idx:tt $T:ident $G:ident)+) => { - impl<'a, $first_G, $($G,)+> Acquirable<'a> + impl<'scope, 'a, $first_G, $($G,)+> Acquirable<'scope, 'a> for ($first_G, $($G,)+) where - $first_G: MutexRef<'a>, - $($G: MutexRef<'a, Lvl = $first_G::Lvl>,)+ + $first_G: MutexRef<'scope, 'a>, + $($G: MutexRef<'scope, 'a, Lvl = $first_G::Lvl>,)+ { type MinLvl = $first_G::Lvl; type MaxLvl = $first_G::Lvl; diff --git a/src/key.rs b/src/key.rs index 1070f81..2957aa8 100644 --- a/src/key.rs +++ b/src/key.rs @@ -27,7 +27,7 @@ use crate::level::Bottom; message = "`{Self}` cannot be used as a lock target", note = "use `&Mutex` for a single lock or `&LockSet` for multiple locks" )] -pub trait Lockable<'a> { +pub trait Lockable<'scope, 'a> { /// The guard type returned when the lock(s) are held. type Guard; @@ -42,8 +42,8 @@ pub trait Lockable<'a> { fn lock_impl(&'a self) -> Self::Guard; } -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Lockable<'a> for Mutex { - type Guard = MutexGuard<'a, R, T>; +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Lockable<'scope, 'a> for Mutex { + type Guard = MutexGuard<'scope, 'a, R, T>; type MinLvl = Lvl; type MaxLvl = Lvl; @@ -52,12 +52,13 @@ impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Lockable<'a> for Mutex> Lockable<'a> for alloc::sync::Arc { +impl<'scope, 'a, T: Lockable<'scope, 'a>> Lockable<'scope, 'a> for alloc::sync::Arc { type Guard = T::Guard; type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; @@ -67,7 +68,7 @@ impl<'a, T: Lockable<'a>> Lockable<'a> for alloc::sync::Arc { } } -impl<'a, T: Lockable<'a>> Lockable<'a> for alloc::rc::Rc { +impl<'scope, 'a, T: Lockable<'scope, 'a>> Lockable<'scope, 'a> for alloc::rc::Rc { type Guard = T::Guard; type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; @@ -77,7 +78,7 @@ impl<'a, T: Lockable<'a>> Lockable<'a> for alloc::rc::Rc { } } -impl<'a, T: Lockable<'a>> Lockable<'a> for alloc::boxed::Box { +impl<'scope, 'a, T: Lockable<'scope, 'a>> Lockable<'scope, 'a> for alloc::boxed::Box { type Guard = T::Guard; type MinLvl = T::MinLvl; type MaxLvl = T::MaxLvl; @@ -87,10 +88,10 @@ impl<'a, T: Lockable<'a>> Lockable<'a> for alloc::boxed::Box { } } -impl<'a, L: Acquirable<'a>> Lockable<'a> for LockSet { - type Guard = >::Guard; - type MinLvl = >::MinLvl; - type MaxLvl = >::MaxLvl; +impl<'scope, 'a, L: Acquirable<'scope, 'a>> Lockable<'scope, 'a> for LockSet { + type Guard = >::Guard; + type MinLvl = >::MinLvl; + type MaxLvl = >::MaxLvl; fn lock_impl(&'a self) -> Self::Guard { self.lock_sorted() @@ -114,7 +115,7 @@ impl<'a, L: Acquirable<'a>> Lockable<'a> for LockSet { message = "`{Self}` cannot be acquired without blocking via `try_lock`", note = "`try_lock` targets a single `&Mutex`; multi-lock non-blocking acquisition is not yet supported" )] -pub trait TryLockable<'a> { +pub trait TryLockable<'scope, 'a> { /// The guard type returned when the lock is held. type Guard; @@ -127,20 +128,23 @@ pub trait TryLockable<'a> { fn try_lock_impl(&'a self) -> Option; } -impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> TryLockable<'a> for Mutex { - type Guard = MutexGuard<'a, R, T>; +impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> TryLockable<'scope, 'a> + for Mutex +{ + type Guard = MutexGuard<'scope, 'a, R, T>; type Lvl = Lvl; fn try_lock_impl(&'a self) -> Option { self.raw.try_lock().map(|raw_guard| MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } #[cfg(target_has_atomic = "ptr")] -impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::sync::Arc { +impl<'scope, 'a, T: TryLockable<'scope, 'a>> TryLockable<'scope, 'a> for alloc::sync::Arc { type Guard = T::Guard; type Lvl = T::Lvl; @@ -149,7 +153,7 @@ impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::sync::Arc { } } -impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::rc::Rc { +impl<'scope, 'a, T: TryLockable<'scope, 'a>> TryLockable<'scope, 'a> for alloc::rc::Rc { type Guard = T::Guard; type Lvl = T::Lvl; @@ -158,7 +162,7 @@ impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::rc::Rc { } } -impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::boxed::Box { +impl<'scope, 'a, T: TryLockable<'scope, 'a>> TryLockable<'scope, 'a> for alloc::boxed::Box { type Guard = T::Guard; type Lvl = T::Lvl; @@ -182,7 +186,7 @@ impl<'a, T: TryLockable<'a>> TryLockable<'a> for alloc::boxed::Box { message = "`{Self}` cannot be locked by value", note = "use a `ReadLock(&rw)` / `WriteLock(&rw)` wrapper, or build a `LockSet` for multi-lock acquisition" )] -pub trait LockableByValue<'a> { +pub trait LockableByValue<'scope, 'a> { /// The guard type returned when the lock is held. type Guard; @@ -262,7 +266,10 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// *guard += 20; /// }); /// ``` - pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) + pub fn lock<'a, L: Lockable<'scope, 'a>>( + self, + target: &'a L, + ) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) where L::MinLvl: LockAfter, { @@ -311,7 +318,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// mutex is currently held and acquisition would block. #[allow(clippy::missing_errors_doc)] // documented above #[allow(clippy::result_large_err)] // the key is zero-sized (PhantomData) - pub fn try_lock<'a, L: TryLockable<'a>>( + pub fn try_lock<'a, L: TryLockable<'scope, 'a>>( self, target: &'a L, ) -> Result<(L::Guard, MutexKey<'scope, L::Lvl>), Self> @@ -360,7 +367,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// ``` pub fn lock_owned<'a, L>(self, target: L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) where - L: LockableByValue<'a>, + L: LockableByValue<'scope, 'a>, L::MinLvl: LockAfter, { let guards = target.lock_by_value(); @@ -404,11 +411,11 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { self, lockable: &'a L, f: F, - ) -> (Ret, MutexKey<'scope, >::MaxLvl>) + ) -> (Ret, MutexKey<'scope, >::MaxLvl>) where - L: Acquirable<'a>, - >::MinLvl: LockAfter, - F: FnOnce(>::Guard) -> Ret, + L: Acquirable<'scope, 'a>, + >::MinLvl: LockAfter, + F: FnOnce(>::Guard) -> Ret, { let (indices, duplicate) = build_sorted(lockable); if let Some(d) = duplicate { @@ -467,14 +474,14 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { set: &'a LockSet, ) -> Result< ( - >::Guard, - MutexKey<'scope, >::MaxLvl>, + >::Guard, + MutexKey<'scope, >::MaxLvl>, ), Self, > where - L: Acquirable<'a>, - >::MinLvl: LockAfter, + L: Acquirable<'scope, 'a>, + >::MinLvl: LockAfter, { match set.try_lock_sorted() { Some(guards) => Ok((guards, MutexKey::new_internal())), @@ -524,11 +531,11 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { self, lockable: &'a L, f: F, - ) -> Result<(Ret, MutexKey<'scope, >::MaxLvl>), Self> + ) -> Result<(Ret, MutexKey<'scope, >::MaxLvl>), Self> where - L: Acquirable<'a>, - >::MinLvl: LockAfter, - F: FnOnce(>::Guard) -> Ret, + L: Acquirable<'scope, 'a>, + >::MinLvl: LockAfter, + F: FnOnce(>::Guard) -> Ret, { let (indices, duplicate) = build_sorted(lockable); if let Some(d) = duplicate { @@ -570,7 +577,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// ``` pub fn read<'a, L>(self, rw: L) -> (L::Guard, MutexKey<'scope, L::Lvl>) where - L: crate::rw_lock::ReadLockable<'a>, + L: crate::rw_lock::ReadLockable<'scope, 'a>, L::Lvl: LockAfter, { (rw.read_lock(), MutexKey::new_internal()) @@ -608,7 +615,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { #[allow(clippy::result_large_err)] // the key is zero-sized (PhantomData) pub fn try_read<'a, L>(self, rw: L) -> Result<(L::Guard, MutexKey<'scope, L::Lvl>), Self> where - L: crate::rw_lock::ReadLockable<'a>, + L: crate::rw_lock::ReadLockable<'scope, 'a>, L::Lvl: LockAfter, { match rw.try_read_lock() { @@ -643,7 +650,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// ``` pub fn write<'a, L>(self, rw: L) -> (L::Guard, MutexKey<'scope, L::Lvl>) where - L: crate::rw_lock::WriteLockable<'a>, + L: crate::rw_lock::WriteLockable<'scope, 'a>, L::Lvl: LockAfter, { (rw.write_lock(), MutexKey::new_internal()) @@ -683,7 +690,7 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { #[allow(clippy::result_large_err)] // the key is zero-sized (PhantomData) pub fn try_write<'a, L>(self, rw: L) -> Result<(L::Guard, MutexKey<'scope, L::Lvl>), Self> where - L: crate::rw_lock::WriteLockable<'a>, + L: crate::rw_lock::WriteLockable<'scope, 'a>, L::Lvl: LockAfter, { match rw.try_write_lock() { @@ -789,9 +796,17 @@ where /// /// For nested locking within a scope, use [`MutexKey::subscope`]. /// +/// `lock_scope` and [`KeyHandle`](crate::key_handle::KeyHandle) share a +/// single per-thread slot. If a [`KeyHandle`](crate::key_handle::KeyHandle) +/// is still alive on this thread, `lock_scope` will panic -- drop the +/// handle first, or use [`KeyHandle::scope`](crate::key_handle::KeyHandle::scope) +/// instead of `lock_scope`. Pick one entry mechanism per thread. +/// /// # Panics /// -/// Panics if a scope is already active on the current thread. +/// Panics if a scope is already active on the current thread, or if a +/// [`KeyHandle`](crate::key_handle::KeyHandle) is currently held on this +/// thread. /// /// # Examples /// @@ -811,7 +826,11 @@ pub fn lock_scope(f: F) -> Ret where F: for<'scope> FnOnce(MutexKey<'scope, Bottom>) -> Ret, { - try_lock_scope(f).expect("nested lock_scope -- use key.subscope() or try_lock_scope()") + try_lock_scope(f).expect( + "surelock: cannot enter lock_scope -- this thread already has a KeyHandle or an active \ + scope. Drop the existing KeyHandle, or use key.subscope() for nested locking, or \ + try_lock_scope() to handle this without panicking.", + ) // ^ The expected error is ClaimError::AlreadyClaimed; the message // above is the message the user sees in the panic. } diff --git a/src/key_handle.rs b/src/key_handle.rs index 376decd..af922c9 100644 --- a/src/key_handle.rs +++ b/src/key_handle.rs @@ -154,7 +154,11 @@ impl KeyHandle { #[must_use] #[allow(clippy::expect_used, clippy::new_without_default)] pub fn claim() -> Self { - Self::try_claim().expect("surelock: KeyHandle already claimed on this thread") + Self::try_claim().expect( + "surelock: this thread already has a KeyHandle or an active lock_scope. A KeyHandle \ + and lock_scope share one per-thread slot; hold only one at a time. Drop the existing \ + handle, or use KeyHandle::try_claim() to handle this without panicking.", + ) } /// Provide a [`MutexKey`] to the closure for ordered lock acquisition. diff --git a/src/lib.rs b/src/lib.rs index 6918710..8134efa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,6 +352,16 @@ //! static nesting prevention via `&mut self` (compile error). //! Works on `no_std` without `thread_local!`. //! +//! Pick **one** mechanism per thread. Both draw from a single +//! per-thread slot (the invariant that guarantees one key per thread), +//! so a live [`KeyHandle`](crate::key_handle::KeyHandle) and a +//! [`lock_scope`](crate::key::lock_scope) call cannot coexist: the +//! second one panics (or returns +//! [`ClaimError::AlreadyClaimed`](crate::key_handle::error::ClaimError::AlreadyClaimed) +//! for the `try_*` forms). For nesting, use +//! [`MutexKey::subscope`](crate::key::MutexKey::subscope), never a +//! second entry point. +//! //! On `no_std`, the ambient entry points are not available. //! [`KeyHandle`](key_handle::KeyHandle) is the only entry mechanism, //! providing static nesting prevention via `&mut self`. See diff --git a/src/mutex.rs b/src/mutex.rs index 82d9de3..ce31dcc 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -100,6 +100,27 @@ impl Mutex { /// /// let counter: Mutex = Mutex::new(0); /// ``` + /// + /// # Type inference + /// + /// `Mutex` has defaulted type parameters (`Lvl = Base`, + /// `R = StdMutex`), but those defaults are only applied when the + /// type is *named*, not when it is produced by return-type + /// inference. In positions that don't pin the type -- most commonly + /// `Arc::new(Mutex::new(x))` -- inference fails with + /// `error[E0283]: type annotations needed`. Name the type to fix it, + /// using whichever reads best: + /// + /// ```rust + /// use std::sync::Arc; + /// use surelock::mutex::Mutex; + /// + /// // Turbofish the data type (the compiler suggests this): + /// let a = Arc::new(Mutex::::new(0)); + /// // ...or annotate the binding: + /// let b: Arc> = Arc::new(Mutex::new(0)); + /// # let _ = (a, b); + /// ``` #[must_use] pub fn new(data: T) -> Self { Self { @@ -211,11 +232,16 @@ impl Mutex { /// prevention is the caller's responsibility. /// /// Only available with the `escape-hatch` feature enabled. - pub fn unchecked_lock(&self) -> MutexGuard<'_, R, T> { + /// + /// The returned guard's `'scope` brand is unconstrained (free): the + /// escape hatch deliberately bypasses the scope/key machinery, so + /// this guard is *not* tied to any lock scope. + pub fn unchecked_lock(&self) -> MutexGuard<'_, '_, R, T> { let raw_guard = self.raw.lock(); MutexGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, } } } diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs index e4e91e1..ec95cfe 100644 --- a/src/mutex/guard.rs +++ b/src/mutex/guard.rs @@ -7,6 +7,7 @@ use core::{ cell::UnsafeCell, + marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -22,16 +23,35 @@ use crate::mutex::raw::RawMutex; /// [`MutexKey::lock_with`](crate::key::MutexKey::lock_with), or /// `Mutex::unchecked_lock` /// (if the `escape-hatch` feature is enabled). +/// +/// # Scope brand +/// +/// The `'scope` lifetime is an invariant *brand* (a zero-sized +/// [`PhantomData`]) tying the guard to the +/// [`MutexKey`](crate::key::MutexKey) scope it was acquired in. Like +/// the key itself, a branded guard cannot escape the +/// [`lock_scope`](crate::key::lock_scope) / +/// [`KeyHandle::scope`](crate::key_handle::KeyHandle::scope) closure, +/// because `'scope` is universally quantified (`for<'scope>`) and so +/// cannot appear in the closure's return type. This closes the +/// self-deadlock hole where a guard is smuggled out of one scope and a +/// fresh scope re-locks the same mutex. The brand does *not* constrain +/// the real data borrow `'a`, so guards remain droppable early and +/// locks created inside the scope work normally. #[must_use = "if unused, the Mutex will immediately unlock"] -pub struct MutexGuard<'a, R: RawMutex + 'a, T: ?Sized> { +pub struct MutexGuard<'scope, 'a, R: RawMutex + 'a, T: ?Sized> { // Field order matters: Rust drops fields in declaration order. // `data` is declared first so its borrow is released before // `_raw_guard` drops and releases the lock. pub(crate) data: &'a UnsafeCell, pub(crate) _raw_guard: R::Guard<'a>, + // Invariant brand: prevents the guard from escaping its scope, + // exactly as `MutexKey`'s `'scope` brand does. Does not affect the + // data borrow. + pub(crate) _scope: PhantomData &'scope ()>, } -impl Deref for MutexGuard<'_, R, T> { +impl Deref for MutexGuard<'_, '_, R, T> { type Target = T; fn deref(&self) -> &T { @@ -40,7 +60,7 @@ impl Deref for MutexGuard<'_, R, T> { } } -impl DerefMut for MutexGuard<'_, R, T> { +impl DerefMut for MutexGuard<'_, '_, R, T> { fn deref_mut(&mut self) -> &mut T { // SAFETY: the raw guard guarantees exclusive access. unsafe { &mut *self.data.get() } @@ -49,20 +69,21 @@ impl DerefMut for MutexGuard<'_, R, T> { // No Drop impl needed -- dropping _raw_guard releases the lock. -impl core::fmt::Debug for MutexGuard<'_, R, T> { +impl core::fmt::Debug for MutexGuard<'_, '_, R, T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt(&**self, f) } } // SAFETY: MutexGuard is Send if the raw guard is Send and T is Send. -unsafe impl<'a, R: RawMutex + 'a, T: ?Sized + Send> Send for MutexGuard<'a, R, T> where +// The `'scope` brand is a ZST PhantomData and does not affect Send/Sync. +unsafe impl<'a, R: RawMutex + 'a, T: ?Sized + Send> Send for MutexGuard<'_, 'a, R, T> where R::Guard<'a>: Send { } // SAFETY: MutexGuard is Sync if T is Sync (shared ref to guard = shared ref to data). -unsafe impl<'a, R: RawMutex + 'a, T: ?Sized + Sync> Sync for MutexGuard<'a, R, T> where +unsafe impl<'a, R: RawMutex + 'a, T: ?Sized + Sync> Sync for MutexGuard<'_, 'a, R, T> where R::Guard<'a>: Sync { } diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 969c80d..b70c1ac 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -135,6 +135,23 @@ impl RwLock { /// /// let cache: RwLock = RwLock::new(0); /// ``` + /// + /// # Type inference + /// + /// Like [`Mutex::new`](crate::mutex::Mutex::new), `RwLock` has + /// defaulted type parameters (`Lvl = Base`, `R = StdRwLock`) that are + /// only applied when the type is *named*. In positions that don't + /// pin it -- e.g. `Arc::new(RwLock::new(x))` -- inference fails with + /// `error[E0283]`. Name the type: + /// + /// ```rust + /// use std::sync::Arc; + /// use surelock::rw_lock::RwLock; + /// + /// let a = Arc::new(RwLock::::new(0)); // turbofish + /// let b: Arc> = Arc::new(RwLock::new(0)); // or annotate + /// # let _ = (a, b); + /// ``` #[must_use] pub fn new(data: T) -> Self { Self { @@ -406,7 +423,7 @@ pub struct WriteLock(pub R); message = "`{Self}` cannot be read-locked", note = "pass a `&RwLock` or a reference to an `Arc`/`Rc`/`Box`" )] -pub trait ReadLockable<'a> { +pub trait ReadLockable<'scope, 'a> { /// The shared read guard returned when the lock is held. type Guard; @@ -435,7 +452,7 @@ pub trait ReadLockable<'a> { message = "`{Self}` cannot be write-locked", note = "pass a `&RwLock` or a reference to an `Arc`/`Rc`/`Box`" )] -pub trait WriteLockable<'a> { +pub trait WriteLockable<'scope, 'a> { /// The exclusive write guard returned when the lock is held. type Guard; @@ -475,10 +492,12 @@ use crate::{ macro_rules! impl_read_lock_traits { ($ref:ty) => { - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> MutexRef<'a> for ReadLock<$ref> { + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> MutexRef<'scope, 'a> + for ReadLock<$ref> + { type Data = T; type Lvl = Lvl; - type Guard = RwLockReadGuard<'a, R, T>; + type Guard = RwLockReadGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { self.0.id() @@ -488,6 +507,7 @@ macro_rules! impl_read_lock_traits { RwLockReadGuard { data: &self.0.data, _raw_guard: self.0.raw.read(), + _scope: PhantomData, } } @@ -495,14 +515,17 @@ macro_rules! impl_read_lock_traits { self.0.raw.try_read().map(|raw_guard| RwLockReadGuard { data: &self.0.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Acquirable<'a> for ReadLock<$ref> { + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Acquirable<'scope, 'a> + for ReadLock<$ref> + { type MinLvl = Lvl; type MaxLvl = Lvl; - type Guard = RwLockReadGuard<'a, R, T>; + type Guard = RwLockReadGuard<'scope, 'a, R, T>; fn collect_ids(&self, out: &mut alloc::vec::Vec) { out.push(self.0.id()); @@ -512,6 +535,7 @@ macro_rules! impl_read_lock_traits { RwLockReadGuard { data: &self.0.data, _raw_guard: self.0.raw.read(), + _scope: PhantomData, } } @@ -519,12 +543,15 @@ macro_rules! impl_read_lock_traits { self.0.raw.try_read().map(|raw_guard| RwLockReadGuard { data: &self.0.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Lockable<'a> for ReadLock<$ref> { - type Guard = RwLockReadGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Lockable<'scope, 'a> + for ReadLock<$ref> + { + type Guard = RwLockReadGuard<'scope, 'a, R, T>; type MinLvl = Lvl; type MaxLvl = Lvl; @@ -532,12 +559,15 @@ macro_rules! impl_read_lock_traits { RwLockReadGuard { data: &self.0.data, _raw_guard: self.0.raw.read(), + _scope: PhantomData, } } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> LockableByValue<'a> for ReadLock<$ref> { - type Guard = RwLockReadGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> LockableByValue<'scope, 'a> + for ReadLock<$ref> + { + type Guard = RwLockReadGuard<'scope, 'a, R, T>; type MinLvl = Lvl; type MaxLvl = Lvl; @@ -545,18 +575,20 @@ macro_rules! impl_read_lock_traits { RwLockReadGuard { data: &self.0.data, _raw_guard: self.0.raw.read(), + _scope: PhantomData, } } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> ReadLockable<'a> for $ref { - type Guard = RwLockReadGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> ReadLockable<'scope, 'a> for $ref { + type Guard = RwLockReadGuard<'scope, 'a, R, T>; type Lvl = Lvl; fn read_lock(self) -> Self::Guard { RwLockReadGuard { data: &self.data, _raw_guard: self.raw.read(), + _scope: PhantomData, } } @@ -564,6 +596,7 @@ macro_rules! impl_read_lock_traits { self.raw.try_read().map(|raw_guard| RwLockReadGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } @@ -572,10 +605,12 @@ macro_rules! impl_read_lock_traits { macro_rules! impl_write_lock_traits { ($ref:ty) => { - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> MutexRef<'a> for WriteLock<$ref> { + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> MutexRef<'scope, 'a> + for WriteLock<$ref> + { type Data = T; type Lvl = Lvl; - type Guard = RwLockWriteGuard<'a, R, T>; + type Guard = RwLockWriteGuard<'scope, 'a, R, T>; fn id(&self) -> LockId { self.0.id() @@ -585,6 +620,7 @@ macro_rules! impl_write_lock_traits { RwLockWriteGuard { data: &self.0.data, _raw_guard: self.0.raw.write(), + _scope: PhantomData, } } @@ -592,14 +628,17 @@ macro_rules! impl_write_lock_traits { self.0.raw.try_write().map(|raw_guard| RwLockWriteGuard { data: &self.0.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Acquirable<'a> for WriteLock<$ref> { + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Acquirable<'scope, 'a> + for WriteLock<$ref> + { type MinLvl = Lvl; type MaxLvl = Lvl; - type Guard = RwLockWriteGuard<'a, R, T>; + type Guard = RwLockWriteGuard<'scope, 'a, R, T>; fn collect_ids(&self, out: &mut alloc::vec::Vec) { out.push(self.0.id()); @@ -609,6 +648,7 @@ macro_rules! impl_write_lock_traits { RwLockWriteGuard { data: &self.0.data, _raw_guard: self.0.raw.write(), + _scope: PhantomData, } } @@ -616,12 +656,15 @@ macro_rules! impl_write_lock_traits { self.0.raw.try_write().map(|raw_guard| RwLockWriteGuard { data: &self.0.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Lockable<'a> for WriteLock<$ref> { - type Guard = RwLockWriteGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> Lockable<'scope, 'a> + for WriteLock<$ref> + { + type Guard = RwLockWriteGuard<'scope, 'a, R, T>; type MinLvl = Lvl; type MaxLvl = Lvl; @@ -629,12 +672,15 @@ macro_rules! impl_write_lock_traits { RwLockWriteGuard { data: &self.0.data, _raw_guard: self.0.raw.write(), + _scope: PhantomData, } } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> LockableByValue<'a> for WriteLock<$ref> { - type Guard = RwLockWriteGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> LockableByValue<'scope, 'a> + for WriteLock<$ref> + { + type Guard = RwLockWriteGuard<'scope, 'a, R, T>; type MinLvl = Lvl; type MaxLvl = Lvl; @@ -642,18 +688,22 @@ macro_rules! impl_write_lock_traits { RwLockWriteGuard { data: &self.0.data, _raw_guard: self.0.raw.write(), + _scope: PhantomData, } } } - impl<'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> WriteLockable<'a> for $ref { - type Guard = RwLockWriteGuard<'a, R, T>; + impl<'scope, 'a, T: 'a, Lvl: IsLevel, R: RawRwLock + 'a> WriteLockable<'scope, 'a> + for $ref + { + type Guard = RwLockWriteGuard<'scope, 'a, R, T>; type Lvl = Lvl; fn write_lock(self) -> Self::Guard { RwLockWriteGuard { data: &self.data, _raw_guard: self.raw.write(), + _scope: PhantomData, } } @@ -661,6 +711,7 @@ macro_rules! impl_write_lock_traits { self.raw.try_write().map(|raw_guard| RwLockWriteGuard { data: &self.data, _raw_guard: raw_guard, + _scope: PhantomData, }) } } diff --git a/src/rw_lock/guard.rs b/src/rw_lock/guard.rs index 480329e..e18042d 100644 --- a/src/rw_lock/guard.rs +++ b/src/rw_lock/guard.rs @@ -16,6 +16,7 @@ use core::{ cell::UnsafeCell, + marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -25,16 +26,21 @@ use crate::rw_lock::raw::RawRwLock; /// /// `Deref` only -- read guards do not expose mutable access. The /// shared lock is released when the guard is dropped. +/// +/// The `'scope` lifetime is an invariant scope *brand*; see +/// [`MutexGuard`](crate::mutex::guard::MutexGuard) for details. It +/// prevents the guard from escaping its lock scope. #[must_use = "if unused, the RwLock will immediately unlock"] -pub struct RwLockReadGuard<'a, R: RawRwLock + 'a, T: ?Sized> { +pub struct RwLockReadGuard<'scope, 'a, R: RawRwLock + 'a, T: ?Sized> { // Field order matters: Rust drops fields in declaration order. // `data` is declared first so its borrow is released before // `_raw_guard` drops and releases the lock. pub(crate) data: &'a UnsafeCell, pub(crate) _raw_guard: R::ReadGuard<'a>, + pub(crate) _scope: PhantomData &'scope ()>, } -impl Deref for RwLockReadGuard<'_, R, T> { +impl Deref for RwLockReadGuard<'_, '_, R, T> { type Target = T; fn deref(&self) -> &T { @@ -43,7 +49,9 @@ impl Deref for RwLockReadGuard<'_, R, T> { } } -impl core::fmt::Debug for RwLockReadGuard<'_, R, T> { +impl core::fmt::Debug + for RwLockReadGuard<'_, '_, R, T> +{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt(&**self, f) } @@ -51,12 +59,12 @@ impl core::fmt::Debug for RwLockRead // SAFETY: shared-mode access exposes &T across threads, so T: Sync is // the relevant bound. The raw guard's Send/Sync gates the surface. -unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Send for RwLockReadGuard<'a, R, T> where +unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Send for RwLockReadGuard<'_, 'a, R, T> where R::ReadGuard<'a>: Send { } -unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Sync for RwLockReadGuard<'a, R, T> where +unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Sync for RwLockReadGuard<'_, 'a, R, T> where R::ReadGuard<'a>: Sync { } @@ -65,13 +73,18 @@ unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Sync for RwLockReadGuard<'a /// /// `Deref` + `DerefMut`. The exclusive lock is released when the /// guard is dropped. +/// +/// The `'scope` lifetime is an invariant scope *brand*; see +/// [`MutexGuard`](crate::mutex::guard::MutexGuard) for details. It +/// prevents the guard from escaping its lock scope. #[must_use = "if unused, the RwLock will immediately unlock"] -pub struct RwLockWriteGuard<'a, R: RawRwLock + 'a, T: ?Sized> { +pub struct RwLockWriteGuard<'scope, 'a, R: RawRwLock + 'a, T: ?Sized> { pub(crate) data: &'a UnsafeCell, pub(crate) _raw_guard: R::WriteGuard<'a>, + pub(crate) _scope: PhantomData &'scope ()>, } -impl Deref for RwLockWriteGuard<'_, R, T> { +impl Deref for RwLockWriteGuard<'_, '_, R, T> { type Target = T; fn deref(&self) -> &T { @@ -80,14 +93,16 @@ impl Deref for RwLockWriteGuard<'_, R, T> { } } -impl DerefMut for RwLockWriteGuard<'_, R, T> { +impl DerefMut for RwLockWriteGuard<'_, '_, R, T> { fn deref_mut(&mut self) -> &mut T { // SAFETY: the exclusive raw guard guarantees no other access. unsafe { &mut *self.data.get() } } } -impl core::fmt::Debug for RwLockWriteGuard<'_, R, T> { +impl core::fmt::Debug + for RwLockWriteGuard<'_, '_, R, T> +{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt(&**self, f) } @@ -95,12 +110,12 @@ impl core::fmt::Debug for RwLockWrit // SAFETY: write-mode access is exclusive, mirroring Mutex semantics: // T: Send is sufficient for cross-thread movement of the lock value. -unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Send> Send for RwLockWriteGuard<'a, R, T> where +unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Send> Send for RwLockWriteGuard<'_, 'a, R, T> where R::WriteGuard<'a>: Send { } -unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Sync for RwLockWriteGuard<'a, R, T> where +unsafe impl<'a, R: RawRwLock + 'a, T: ?Sized + Sync> Sync for RwLockWriteGuard<'_, 'a, R, T> where R::WriteGuard<'a>: Sync { } diff --git a/src/set.rs b/src/set.rs index 8e3cfcb..6b4447f 100644 --- a/src/set.rs +++ b/src/set.rs @@ -58,7 +58,9 @@ pub(crate) struct Duplicate { /// Returns the sorted indices and, if two locks share a [`LockId`], /// the [`Duplicate`] naming the offending pair (by original position). #[allow(clippy::indexing_slicing)] // indices are 0..ids.len(), always in bounds -pub(crate) fn build_sorted<'a, L: Acquirable<'a>>(group: &L) -> (Vec, Option) { +pub(crate) fn build_sorted<'scope, 'a, L: Acquirable<'scope, 'a>>( + group: &L, +) -> (Vec, Option) { let mut ids: Vec = Vec::new(); group.collect_ids(&mut ids); @@ -124,9 +126,9 @@ impl LockSet { /// the two offending positions. #[must_use] #[allow(clippy::panic)] // Intentional: a duplicate is a programmer error - pub fn new<'a>(group: L) -> Self + pub fn new<'scope, 'a>(group: L) -> Self where - L: Acquirable<'a>, + L: Acquirable<'scope, 'a>, { let (sorted_indices, duplicate) = build_sorted(&group); @@ -174,9 +176,9 @@ impl LockSet { /// // Same lock twice -- returns an error. /// assert!(LockSet::try_new((&a, &a)).is_err()); /// ``` - pub fn try_new<'a>(group: L) -> Result + pub fn try_new<'scope, 'a>(group: L) -> Result where - L: Acquirable<'a>, + L: Acquirable<'scope, 'a>, { let (sorted_indices, duplicate) = build_sorted(&group); @@ -191,9 +193,9 @@ impl LockSet { } /// Acquire all locks in sorted order, returning guards. - pub(crate) fn lock_sorted<'a>(&'a self) -> >::Guard + pub(crate) fn lock_sorted<'scope, 'a>(&'a self) -> >::Guard where - L: Acquirable<'a>, + L: Acquirable<'scope, 'a>, { self.group.lock_sorted(&self.sorted_indices) } @@ -202,9 +204,11 @@ impl LockSet { /// Returns `Some(guards)` only if every lock was acquired; on the /// first contended lock, returns `None` and releases any locks /// already taken (all-or-nothing). - pub(crate) fn try_lock_sorted<'a>(&'a self) -> Option<>::Guard> + pub(crate) fn try_lock_sorted<'scope, 'a>( + &'a self, + ) -> Option<>::Guard> where - L: Acquirable<'a>, + L: Acquirable<'scope, 'a>, { self.group.try_lock_sorted(&self.sorted_indices) } diff --git a/tests/compile_fail/guard_escape_from_scope.rs b/tests/compile_fail/guard_escape_from_scope.rs new file mode 100644 index 0000000..fbdcbcf --- /dev/null +++ b/tests/compile_fail/guard_escape_from_scope.rs @@ -0,0 +1,22 @@ +//! A `MutexGuard` must not escape its lock scope. +//! +//! Returning a guard from `lock_scope` / `KeyHandle::scope` would let a +//! caller hold the lock past the scope, then enter a *fresh* scope and +//! re-acquire the same non-reentrant mutex -- a self-deadlock, and +//! exactly the failure surelock exists to prevent. The guard carries +//! the same invariant `'scope` brand as the `MutexKey`, so (like the +//! key) it cannot appear in the closure's return type. +//! +//! This is the guard-level companion to `key_escape_from_scope.rs`. + +use surelock::{key::lock_scope, mutex::Mutex}; + +fn main() { + let m: Mutex = Mutex::new(0); + + // Trying to smuggle the guard out of the scope must not compile. + let _escaped = lock_scope(|key| { + let (guard, _key) = key.lock(&m); + guard + }); +} diff --git a/tests/compile_fail/guard_escape_from_scope.stderr b/tests/compile_fail/guard_escape_from_scope.stderr new file mode 100644 index 0000000..fe10619 --- /dev/null +++ b/tests/compile_fail/guard_escape_from_scope.stderr @@ -0,0 +1,14 @@ +error: lifetime may not live long enough + --> tests/compile_fail/guard_escape_from_scope.rs:20:9 + | +18 | let _escaped = lock_scope(|key| { + | ---- return type of closure is surelock::mutex::guard::MutexGuard<'2, '_, StdMutex, u32> + | | + | has type `MutexKey<'1, Bottom>` +19 | let (guard, _key) = key.lock(&m); +20 | guard + | ^^^^^ returning this value requires that `'1` must outlive `'2` + | + = note: requirement occurs because of the type `surelock::mutex::guard::MutexGuard<'_, '_, StdMutex, u32>`, which makes the generic argument `'_` invariant + = note: the struct `surelock::mutex::guard::MutexGuard<'scope, 'a, R, T>` is invariant over the parameter `'scope` + = help: see for more information about variance diff --git a/tests/compile_fail/key_reuse_after_consumption.stderr b/tests/compile_fail/key_reuse_after_consumption.stderr index ae4f79c..621d171 100644 --- a/tests/compile_fail/key_reuse_after_consumption.stderr +++ b/tests/compile_fail/key_reuse_after_consumption.stderr @@ -27,5 +27,5 @@ error[E0382]: use of moved value: `key` note: `MutexKey::<'scope, Lvl>::lock` takes ownership of the receiver `self`, which moves `key` --> src/key.rs | - | pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) - | ^^^^ + | self, + | ^^^^ diff --git a/tests/compile_fail/read_guard_not_send_with_non_sync_payload.stderr b/tests/compile_fail/read_guard_not_send_with_non_sync_payload.stderr index b84f143..0f9518d 100644 --- a/tests/compile_fail/read_guard_not_send_with_non_sync_payload.stderr +++ b/tests/compile_fail/read_guard_not_send_with_non_sync_payload.stderr @@ -8,7 +8,7 @@ error[E0277]: `Cell` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `Cell` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU32` instead - = note: required for `surelock::rw_lock::guard::RwLockReadGuard<'_, StdRwLock, Cell>` to implement `Send` + = note: required for `surelock::rw_lock::guard::RwLockReadGuard<'_, '_, StdRwLock, Cell>` to implement `Send` note: required by a bound in `assert_send` --> tests/compile_fail/read_guard_not_send_with_non_sync_payload.rs:10:19 | @@ -24,7 +24,7 @@ error[E0277]: `std::sync::RwLockReadGuard<'_, ()>` cannot be sent between thread | required by a bound introduced by this call | = help: the trait `Send` is not implemented for `std::sync::RwLockReadGuard<'_, ()>` - = note: required for `surelock::rw_lock::guard::RwLockReadGuard<'_, StdRwLock, Cell>` to implement `Send` + = note: required for `surelock::rw_lock::guard::RwLockReadGuard<'_, '_, StdRwLock, Cell>` to implement `Send` note: required by a bound in `assert_send` --> tests/compile_fail/read_guard_not_send_with_non_sync_payload.rs:10:19 | diff --git a/tests/compile_fail/rwlock_read_guard_no_deref_mut.stderr b/tests/compile_fail/rwlock_read_guard_no_deref_mut.stderr index 58da75a..6635c3d 100644 --- a/tests/compile_fail/rwlock_read_guard_no_deref_mut.stderr +++ b/tests/compile_fail/rwlock_read_guard_no_deref_mut.stderr @@ -8,10 +8,10 @@ warning: variable does not need to be mutable | = note: `#[warn(unused_mut)]` on by default -error[E0594]: cannot assign to data in dereference of `surelock::rw_lock::guard::RwLockReadGuard<'_, StdRwLock, u32>` +error[E0594]: cannot assign to data in dereference of `surelock::rw_lock::guard::RwLockReadGuard<'_, '_, StdRwLock, u32>` --> tests/compile_fail/rwlock_read_guard_no_deref_mut.rs:12:9 | 12 | *g = 42; | ^^^^^^^ cannot assign | - = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `surelock::rw_lock::guard::RwLockReadGuard<'_, StdRwLock, u32>` + = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `surelock::rw_lock::guard::RwLockReadGuard<'_, '_, StdRwLock, u32>` diff --git a/tests/compile_fail/try_lock_rejects_lockset.stderr b/tests/compile_fail/try_lock_rejects_lockset.stderr index e86ab24..f0dbf17 100644 --- a/tests/compile_fail/try_lock_rejects_lockset.stderr +++ b/tests/compile_fail/try_lock_rejects_lockset.stderr @@ -2,12 +2,12 @@ error[E0277]: `LockSet<(&surelock::mutex::Mutex, &surelock::mutex::Mutex tests/compile_fail/try_lock_rejects_lockset.rs:16:30 | 16 | let _ = key.try_lock(&set); - | -------- ^^^^ the trait `TryLockable<'_>` is not implemented for `LockSet<(&surelock::mutex::Mutex, &surelock::mutex::Mutex)>` + | -------- ^^^^ the trait `TryLockable<'_, '_>` is not implemented for `LockSet<(&surelock::mutex::Mutex, &surelock::mutex::Mutex)>` | | | required by a bound introduced by this call | = note: `try_lock` targets a single `&Mutex`; multi-lock non-blocking acquisition is not yet supported - = help: the following other types implement trait `TryLockable<'a>`: + = help: the following other types implement trait `TryLockable<'scope, 'a>`: Arc Box Rc @@ -15,5 +15,5 @@ error[E0277]: `LockSet<(&surelock::mutex::Mutex, &surelock::mutex::Mutex::try_lock` --> src/key.rs | - | pub fn try_lock<'a, L: TryLockable<'a>>( - | ^^^^^^^^^^^^^^^ required by this bound in `MutexKey::<'scope, Lvl>::try_lock` + | pub fn try_lock<'a, L: TryLockable<'scope, 'a>>( + | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MutexKey::<'scope, Lvl>::try_lock` diff --git a/tests/compile_fail/try_lock_wrong_level_order.stderr b/tests/compile_fail/try_lock_wrong_level_order.stderr index b8250c9..7e1ab7f 100644 --- a/tests/compile_fail/try_lock_wrong_level_order.stderr +++ b/tests/compile_fail/try_lock_wrong_level_order.stderr @@ -14,7 +14,7 @@ error[E0277]: `Level<2>` cannot be acquired after `Level<5>` note: required by a bound in `MutexKey::<'scope, Lvl>::try_lock` --> src/key.rs | - | pub fn try_lock<'a, L: TryLockable<'a>>( + | pub fn try_lock<'a, L: TryLockable<'scope, 'a>>( | -------- required by a bound in this associated function ... | L::Lvl: LockAfter, diff --git a/tests/compile_fail/wrong_level_order.stderr b/tests/compile_fail/wrong_level_order.stderr index ccd083c..d3ef577 100644 --- a/tests/compile_fail/wrong_level_order.stderr +++ b/tests/compile_fail/wrong_level_order.stderr @@ -14,8 +14,8 @@ error[E0277]: `Level<2>` cannot be acquired after `Level<5>` note: required by a bound in `MutexKey::<'scope, Lvl>::lock` --> src/key.rs | - | pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) + | pub fn lock<'a, L: Lockable<'scope, 'a>>( | ---- required by a bound in this associated function - | where +... | L::MinLvl: LockAfter, | ^^^^^^^^^^^^^^ required by this bound in `MutexKey::<'scope, Lvl>::lock`