From 8d2ba80b2b3332a2adbee5a17b3e0b13340887ed Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 5 Apr 2026 21:01:19 -0700 Subject: [PATCH] acquire -> lock abstraction --- README.md | 23 ++- design/comparison/lock_tree.md | 2 +- src/acquirable.rs | 2 +- src/key.rs | 144 +++++++++++++----- src/key_handle.rs | 5 +- src/lib.rs | 28 ++-- src/mutex.rs | 5 +- src/mutex/guard.rs | 4 +- src/set.rs | 4 +- .../key_reuse_after_consumption.rs | 13 +- .../key_reuse_after_consumption.stderr | 24 +-- tests/compile_fail/wrong_level_order.rs | 4 +- tests/compile_fail/wrong_level_order.stderr | 10 +- tests/integration.rs | 127 +++++++++------ tests/property.rs | 4 +- 15 files changed, 240 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index 5856785..20c0818 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,13 @@ Every lock call is infallible or doesn't compile. No `Result`, no `Option`, no p ## Quick Start ```rust -use surelock::{key_handle::KeyHandle, mutex::Mutex, set::LockSet}; +use surelock::{key_handle::KeyHandle, mutex::Mutex}; let counter: Mutex = Mutex::new(0); let mut handle = KeyHandle::claim(); handle.scope(|key| { - let set = LockSet::new(&counter); - let (mut guard, _key) = key.acquire(&set); + let (mut guard, _key) = key.lock(&counter); *guard += 1; }); ``` @@ -68,7 +67,7 @@ handle.scope(|key| { | `KeyVoucher` | Transferable token (`Send`) | `Locksmith::issue()` | | `KeyHandle` | Per-thread scope creator (`!Send`) | `KeyHandle::claim()` or `KeyVoucher::redeem()` | | `MutexKey` | Per-scope lock token, consumed/re-emitted | `handle.scope(\|key\| ...)` | -| `MutexGuard` | RAII access to protected data | `key.lock()` or `key.acquire()` | +| `MutexGuard` | RAII access to protected data | `key.lock()` or `key.lock_with()` | ## How It Works @@ -144,11 +143,12 @@ Specify just the level to use the default backend: `Mutex>`. // Enter a scope handle.scope(|key| { /* ... */ }); -// Lock (closure-based, one-shot) -let (val, key) = key.lock(&mutex, |guard| *guard); +// Lock (single mutex or LockSet, guards returned) +let (guard, key) = key.lock(&mutex); +let ((ga, gb), key) = key.lock(&set); -// Acquire (LockSet, guards returned) -let (guard, key) = key.acquire(&set); +// Lock with closure (one-shot, sorts inline) +let (val, key) = key.lock_with(&mutex, |guard| *guard); // Nested sub-scope let (result, key) = key.subscope(|inner_key| { /* ... */ }); @@ -167,7 +167,7 @@ let mut handle = KeyHandle::claim(); handle.scope(|key| { let m: Mutex = Mutex::new(42); - let (val, _key) = key.lock(&m, |g| *g); + let (val, _key) = key.lock_with(&m, |g| *g); assert_eq!(val, 42); }); @@ -184,13 +184,12 @@ handle.scope(|key| { /* fresh key */ }); **`lock_scope` / `try_lock_scope`** (`std`-only) -- ambient convenience with runtime nesting check: ```rust -use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +use surelock::{key::lock_scope, mutex::Mutex}; let counter: Mutex = Mutex::new(0); lock_scope(|key| { - let set = LockSet::new(&counter); - let (mut guard, _key) = key.acquire(&set); + let (mut guard, _key) = key.lock(&counter); *guard += 1; }); ``` diff --git a/design/comparison/lock_tree.md b/design/comparison/lock_tree.md index 628df7f..37becad 100644 --- a/design/comparison/lock_tree.md +++ b/design/comparison/lock_tree.md @@ -14,7 +14,7 @@ The key insight: the Rust trait system can enforce a static partial order on loc |--------------------------|--------------------------------|---------------------------------------------------------------------------| | `LockAfter` trait | `LockAfter` trait | Same concept, same name | | `Locked` | `MutexKey<'scope, Lvl>` | Witness consumed and re-emitted (surelock separates key from data access) | -| `lock_and` method | `key.lock()` / `key.acquire()` | Consumes key, returns new key at next level | +| `lock_and` method | `key.lock()` / `key.lock_with()` | Consumes key, returns new key at next level | | `impl_lock_after!` macro | Macro-generated impls | Transitive `LockAfter` impls for `Level` | | Named lock categories | `type Config = Level<0>` | Type aliases for semantic meaning | diff --git a/src/acquirable.rs b/src/acquirable.rs index b44c6e0..364baaf 100644 --- a/src/acquirable.rs +++ b/src/acquirable.rs @@ -80,7 +80,7 @@ pub trait Acquirable<'a> { fn lock_sorted(&'a self, sorted_indices: &[usize]) -> Self::Guard; } -// Used by key.lock(&mutex, |guard| ...) +// Used by key.lock_with(&mutex, |guard| ...) impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> Acquirable<'a> for Mutex { type MinLvl = Lvl; type MaxLvl = Lvl; diff --git a/src/key.rs b/src/key.rs index 7384690..f556247 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,9 +1,9 @@ //! Scope token for ordered lock acquisition. //! //! [`MutexKey`] tracks the current lock level as a type parameter. -//! It is consumed by [`MutexKey::lock`] and re-emitted at the new level. -//! The key is `!Send + !Sync` and branded with an invariant lifetime -//! to prevent escape from the [`lock_scope`] closure. +//! It is consumed by [`MutexKey::lock`] and re-emitted at the new +//! level. The key is `!Send + !Sync` and branded with an invariant +//! lifetime to prevent escape from the [`lock_scope`] closure. use alloc::vec::Vec; use core::marker::PhantomData; @@ -11,12 +11,65 @@ use core::marker::PhantomData; use crate::{ acquirable::Acquirable, level::{IsLevel, LockAfter}, + mutex::{Mutex, guard::MutexGuard}, + raw_mutex::RawMutex, set::LockSet, }; #[cfg(feature = "std")] use crate::level::Bottom; +// ── Lockable trait ────────────────────────────────────────────── + +/// A target that can be locked by [`MutexKey::lock`]. +/// +/// Implemented for single [`Mutex`] references and [`LockSet`]s. +/// Users do not need to implement this trait. +#[diagnostic::on_unimplemented( + 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> { + /// The guard type returned when the lock(s) are held. + type Guard; + + /// The minimum level in this target. + type MinLvl: IsLevel; + + /// The maximum level in this target. The key advances to this + /// level after acquisition. + type MaxLvl: IsLevel; + + /// Acquire the lock(s) and return the guard(s). + 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>; + type MinLvl = Lvl; + type MaxLvl = Lvl; + + fn lock_impl(&'a self) -> Self::Guard { + let raw_guard = self.raw.lock(); + MutexGuard { + data: &self.data, + _raw_guard: raw_guard, + } + } +} + +impl<'a, L: Acquirable<'a>> Lockable<'a> for LockSet { + type Guard = >::Guard; + type MinLvl = >::MinLvl; + type MaxLvl = >::MaxLvl; + + fn lock_impl(&'a self) -> Self::Guard { + self.lock_sorted() + } +} + +// ── MutexKey ──────────────────────────────────────────────────── + /// Scope token for ordered lock acquisition. /// /// Tracks the current lock level as a type parameter `Lvl`. Consumed @@ -52,19 +105,54 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { } } - /// Lock one or more mutexes and pass the guards to a closure. + /// Lock one or more mutexes, returning the guard(s) directly. + /// + /// Accepts a single `&Mutex` or a `&LockSet`. Consumes the key + /// and returns the guard(s) plus a new key at the target's level. + /// + /// For a single mutex this is zero-allocation; for a `LockSet` + /// the sort was already done at construction time. + /// + /// # Examples + /// + /// ```rust + /// use surelock::{key::lock_scope, level::Level, mutex::Mutex, set::LockSet}; + /// + /// let a: Mutex = Mutex::new(1); + /// let b: Mutex> = Mutex::new(2); + /// + /// lock_scope(|key| { + /// // Single mutex + /// let (mut guard, key) = key.lock(&a); + /// *guard += 10; + /// drop(guard); + /// + /// // Pre-sorted set + /// let set = LockSet::new(&b); + /// let (mut guard, _key) = key.lock(&set); + /// *guard += 20; + /// }); + /// ``` + pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) + where + L::MinLvl: LockAfter, + { + let guards = target.lock_impl(); + (guards, MutexKey::new_internal()) + } + + /// Lock one or more mutexes via a closure. /// /// Sorts by [`LockId`](crate::id::LockId) and acquires in one /// call. The guards live inside the closure -- no [`LockSet`] - /// needed. + /// needed. Convenient for one-shot read-and-release patterns. /// /// For hot paths where the same locks are acquired repeatedly, - /// pre-build a [`LockSet`] and use [`acquire`](MutexKey::acquire) + /// pre-build a [`LockSet`] and use [`lock`](MutexKey::lock) /// instead (sort once, lock many). /// /// Consumes the key. Returns the closure's result plus a new key - /// at the lock group's level. Infallible -- if the level - /// relationship isn't declared, the code doesn't compile. + /// at the lock group's level. /// /// # Examples /// @@ -76,12 +164,12 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// /// let mut handle = KeyHandle::claim(); /// handle.scope(|key| { - /// let (sum, _key) = key.lock(&(&a, &b), |(ga, gb)| *ga + *gb); + /// let (sum, _key) = key.lock_with(&(&a, &b), |(ga, gb)| *ga + *gb); /// assert_eq!(sum, 3); /// }); /// ``` #[allow(clippy::indexing_slicing)] - pub fn lock<'a, L, F, Ret>( + pub fn lock_with<'a, L, F, Ret>( self, lockable: &'a L, f: F, @@ -103,32 +191,6 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { (result, MutexKey::new_internal()) } - /// Acquire all locks in a pre-built [`LockSet`], returning the - /// guards directly. - /// - /// Use this for hot paths where the same set is locked - /// repeatedly (sort once, lock many times). For one-off - /// acquisitions, [`lock`](MutexKey::lock) is more convenient. - /// - /// Consumes the key and returns guards plus a new key at the - /// set's level. Infallible -- if the level relationship isn't - /// declared, the code doesn't compile. - pub fn acquire<'set, L>( - self, - set: &'set LockSet, - ) -> ( - >::Guard, - MutexKey<'scope, >::MaxLvl>, - ) - where - L: Acquirable<'set>, - >::MinLvl: LockAfter, - { - let guards = set.lock_sorted(); - - (guards, MutexKey::new_internal()) - } - /// Create a nested scope that inherits the current level. /// /// The inner key starts at the same level as the outer key, so @@ -181,13 +243,12 @@ impl<'scope, Lvl: IsLevel> MutexKey<'scope, Lvl> { /// # Examples /// /// ```rust -/// use surelock::{key::try_lock_scope, mutex::Mutex, set::LockSet}; +/// use surelock::{key::try_lock_scope, mutex::Mutex}; /// /// let counter: Mutex = Mutex::new(0); /// /// let result = try_lock_scope(|key| { -/// let set = LockSet::new(&counter); -/// let (mut guard, _key) = key.acquire(&set); +/// let (mut guard, _key) = key.lock(&counter); /// *guard += 1; /// }); /// @@ -227,13 +288,12 @@ where /// # Examples /// /// ```rust -/// use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +/// use surelock::{key::lock_scope, mutex::Mutex}; /// /// let counter: Mutex = Mutex::new(0); /// /// lock_scope(|key| { -/// let set = LockSet::new(&counter); -/// let (mut guard, _key) = key.acquire(&set); +/// let (mut guard, _key) = key.lock(&counter); /// *guard += 1; /// }); /// ``` diff --git a/src/key_handle.rs b/src/key_handle.rs index dfdb2d5..049a0b5 100644 --- a/src/key_handle.rs +++ b/src/key_handle.rs @@ -77,14 +77,13 @@ mod storage { /// # Examples /// /// ```rust -/// use surelock::{key_handle::KeyHandle, mutex::Mutex, set::LockSet}; +/// use surelock::{key_handle::KeyHandle, mutex::Mutex}; /// /// let mut handle = KeyHandle::claim(); /// /// handle.scope(|key| { /// let m: Mutex = Mutex::new(42); -/// let set = LockSet::new(&m); -/// let (guard, _key) = key.acquire(&set); +/// let (guard, _key) = key.lock(&m); /// assert_eq!(*guard, 42); /// }); /// diff --git a/src/lib.rs b/src/lib.rs index 044ffe0..8672374 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,13 +17,12 @@ //! # Quick Start //! //! ```rust -//! use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +//! use surelock::{key::lock_scope, mutex::Mutex}; //! //! let counter: Mutex = Mutex::new(0); //! //! lock_scope(|key| { -//! let set = LockSet::new(&counter); -//! let (mut guard, _key) = key.acquire(&set); +//! let (mut guard, _key) = key.lock(&counter); //! *guard += 1; //! }); //! ``` @@ -44,7 +43,7 @@ //! //! let mut handle = KeyHandle::claim(); //! handle.scope(|key| { -//! let (val, _key) = key.lock(&counter, |mut guard| { +//! let (val, _key) = key.lock_with(&counter, |mut guard| { //! *guard += 1; //! *guard //! }); @@ -68,7 +67,7 @@ //! //! let mut handle = KeyHandle::claim(); //! handle.scope(|key| { -//! let ((ga, gb), _key) = key.acquire(&set); +//! let ((ga, gb), _key) = key.lock(&set); //! assert_eq!(*ga + *gb, 30); //! }); //! ``` @@ -88,10 +87,10 @@ //! let mut handle = KeyHandle::claim(); //! handle.scope(|key| { //! // Read config first (Level<0>) -//! let (cfg_val, key) = key.lock(&config, |g| *g); +//! let (cfg_val, key) = key.lock_with(&config, |g| *g); //! //! // Then lock account (Level<1>) -//! let ((), _key) = key.lock(&account, |mut acct| { +//! let ((), _key) = key.lock_with(&account, |mut acct| { //! *acct += cfg_val; //! }); //! }); @@ -112,10 +111,10 @@ //! //! let mut handle = KeyHandle::claim(); //! handle.scope(|key| { -//! let (val, key) = key.lock(&config, |g| *g); +//! let (val, key) = key.lock_with(&config, |g| *g); //! //! let (result, _key) = key.subscope(|inner_key| { -//! let (r, _) = inner_key.lock(&account, |mut acct| { +//! let (r, _) = inner_key.lock_with(&account, |mut acct| { //! *acct = val; //! *acct //! }); @@ -163,7 +162,7 @@ //! | [`KeyVoucher`](crate::key_voucher::KeyVoucher) | Transferable token (`Send`) | [`Locksmith::issue`](crate::locksmith::Locksmith::issue) | //! | [`KeyHandle`](crate::key_handle::KeyHandle) | Per-thread scope creator (`!Send`) | [`KeyHandle::claim`](crate::key_handle::KeyHandle::claim) or [`KeyVoucher::redeem`](crate::key_voucher::KeyVoucher::redeem) | //! | [`MutexKey`](crate::key::MutexKey) | Per-scope lock token, consumed/re-emitted | [`handle.scope`](crate::key_handle::KeyHandle::scope) | -//! | [`MutexGuard`](crate::mutex::guard::MutexGuard) | RAII access to protected data | [`key.lock`](crate::key::MutexKey::lock) or [`key.acquire`](crate::key::MutexKey::acquire) | +//! | [`MutexGuard`](crate::mutex::guard::MutexGuard) | RAII access to protected data | [`key.lock`](crate::key::MutexKey::lock) or [`key.lock_with`](crate::key::MutexKey::lock_with) | //! //! ## Key Patterns //! @@ -171,11 +170,12 @@ //! // Enter a scope //! handle.scope(|key| { ... }); //! -//! // Lock (closure-based, one-shot) -//! let (val, key) = key.lock(&mutex, |guard| *guard); +//! // Lock (single mutex or LockSet, guards returned) +//! let (guard, key) = key.lock(&mutex); +//! let ((ga, gb), key) = key.lock(&set); //! -//! // Acquire (LockSet, guards returned) -//! let (guard, key) = key.acquire(&set); +//! // Lock with closure (one-shot, sorts inline) +//! let (val, key) = key.lock_with(&mutex, |guard| *guard); //! //! // Nested sub-scope //! let (result, key) = key.subscope(|inner_key| { ... }); diff --git a/src/mutex.rs b/src/mutex.rs index 44d6cf4..f33a125 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -45,13 +45,12 @@ use guard::MutexGuard; /// # Examples /// /// ```rust -/// use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +/// use surelock::{key::lock_scope, mutex::Mutex}; /// /// let counter: Mutex = Mutex::new(0); /// /// lock_scope(|key| { -/// let set = LockSet::new(&counter); -/// let (mut guard, _key) = key.acquire(&set); +/// let (mut guard, _key) = key.lock(&counter); /// *guard += 1; /// }); /// ``` diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs index 6184ea3..65a524e 100644 --- a/src/mutex/guard.rs +++ b/src/mutex/guard.rs @@ -18,8 +18,8 @@ use crate::raw_mutex::RawMutex; /// The lock is released when the guard is dropped. /// /// Guards are not constructible by users -- they are returned by -/// [`MutexKey::lock`](crate::key::MutexKey::lock) (via -/// [`LockSet`](crate::set::LockSet)) or +/// [`MutexKey::lock`](crate::key::MutexKey::lock), +/// [`MutexKey::lock_with`](crate::key::MutexKey::lock_with), or /// `Mutex::unchecked_lock` /// (if the `escape-hatch` feature is enabled). #[must_use = "if unused, the Mutex will immediately unlock"] diff --git a/src/set.rs b/src/set.rs index ac3f473..5dc2d62 100644 --- a/src/set.rs +++ b/src/set.rs @@ -2,7 +2,7 @@ //! //! A [`LockSet`] wraps an [`Acquirable`] and pre-computes the acquisition //! order (sorted by [`LockId`](crate::id::LockId)). Construct once, lock many times via -//! [`MutexKey::acquire`](crate::key::MutexKey::acquire). +//! [`MutexKey::lock`](crate::key::MutexKey::lock). use alloc::vec::Vec; @@ -13,7 +13,7 @@ use crate::acquirable::Acquirable; /// For tuples of arity 3 and above and for slices, all locks must be /// at the same level. 2-tuples may contain locks at different levels /// (the key advances to the maximum of the two). Construct once, -/// lock many times via [`MutexKey::acquire`](crate::key::MutexKey::acquire). +/// lock many times via [`MutexKey::lock`](crate::key::MutexKey::lock). /// /// # Examples /// diff --git a/tests/compile_fail/key_reuse_after_consumption.rs b/tests/compile_fail/key_reuse_after_consumption.rs index 89cd3ed..dbd9a0a 100644 --- a/tests/compile_fail/key_reuse_after_consumption.rs +++ b/tests/compile_fail/key_reuse_after_consumption.rs @@ -1,21 +1,18 @@ //! Using a MutexKey after it has been consumed by lock() should //! not compile (the key is moved, not copied). -use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +use surelock::{key::lock_scope, mutex::Mutex}; fn main() { let a: Mutex = Mutex::new(1); let b: Mutex = Mutex::new(2); lock_scope(|key| { - let set_a = LockSet::new(&a); - let set_b = LockSet::new(&b); - - // First acquire consumes the key - let (guard_a, _new_key) = key.acquire(&set_a); + // First lock consumes the key + let (guard_a, _new_key) = key.lock(&a); // Trying to use the OLD key again should fail: - // key was moved into the first acquire - let (guard_b, _) = key.acquire(&set_b); + // key was moved into the first lock + let (guard_b, _) = key.lock(&b); }); } diff --git a/tests/compile_fail/key_reuse_after_consumption.stderr b/tests/compile_fail/key_reuse_after_consumption.stderr index dec3b2b..fb8935a 100644 --- a/tests/compile_fail/key_reuse_after_consumption.stderr +++ b/tests/compile_fail/key_reuse_after_consumption.stderr @@ -1,31 +1,31 @@ error[E0382]: use of moved value: `key` - --> tests/compile_fail/key_reuse_after_consumption.rs:19:28 + --> tests/compile_fail/key_reuse_after_consumption.rs:16:28 | 10 | lock_scope(|key| { | --- move occurs because `key` has type `MutexKey<'_, Bottom>`, which does not implement the `Copy` trait +11 | // First lock consumes the key +12 | let (guard_a, _new_key) = key.lock(&a); + | -------- `key` moved due to this method call ... -15 | let (guard_a, _new_key) = key.acquire(&set_a); - | --------------- `key` moved due to this method call -... -19 | let (guard_b, _) = key.acquire(&set_b); +16 | let (guard_b, _) = key.lock(&b); | ^^^ value used here after move | -note: `MutexKey::<'scope, Lvl>::acquire` takes ownership of the receiver `self`, which moves `key` +note: `MutexKey::<'scope, Lvl>::lock` takes ownership of the receiver `self`, which moves `key` --> src/key.rs | - | self, - | ^^^^ + | pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) + | ^^^^ warning: unused variable: `guard_a` - --> tests/compile_fail/key_reuse_after_consumption.rs:15:14 + --> tests/compile_fail/key_reuse_after_consumption.rs:12:14 | -15 | let (guard_a, _new_key) = key.acquire(&set_a); +12 | let (guard_a, _new_key) = key.lock(&a); | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_guard_a` | = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default warning: unused variable: `guard_b` - --> tests/compile_fail/key_reuse_after_consumption.rs:19:14 + --> tests/compile_fail/key_reuse_after_consumption.rs:16:14 | -19 | let (guard_b, _) = key.acquire(&set_b); +16 | let (guard_b, _) = key.lock(&b); | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_guard_b` diff --git a/tests/compile_fail/wrong_level_order.rs b/tests/compile_fail/wrong_level_order.rs index 0262054..bdb7762 100644 --- a/tests/compile_fail/wrong_level_order.rs +++ b/tests/compile_fail/wrong_level_order.rs @@ -12,10 +12,10 @@ fn main() { lock_scope(|key| { // Lock high first (Level<5>) - let (_, key) = key.lock(&high, |g| *g); + let (_, key) = key.lock(&high); // Try to lock low (Level<2>) -- should fail: // Level<2> does not implement LockAfter> - let (_, _key) = key.lock(&low, |g| *g); + let (_, _key) = key.lock(&low); }); } diff --git a/tests/compile_fail/wrong_level_order.stderr b/tests/compile_fail/wrong_level_order.stderr index 769dd3e..2758cb7 100644 --- a/tests/compile_fail/wrong_level_order.stderr +++ b/tests/compile_fail/wrong_level_order.stderr @@ -1,7 +1,7 @@ error[E0277]: `Level<2>` cannot be acquired after `Level<5>` --> tests/compile_fail/wrong_level_order.rs:19:34 | -19 | let (_, _key) = key.lock(&low, |g| *g); +19 | let (_, _key) = key.lock(&low); | ---- ^^^^ the trait `LockAfter>` is not implemented for `Level<2>` | | | required by a bound introduced by this call @@ -24,9 +24,9 @@ help: the following other types implement trait `LockAfter` note: required by a bound in `MutexKey::<'scope, Lvl>::lock` --> src/key.rs | - | pub fn lock<'a, L, F, Ret>( + | pub fn lock<'a, L: Lockable<'a>>(self, target: &'a L) -> (L::Guard, MutexKey<'scope, L::MaxLvl>) | ---- required by a bound in this associated function -... - | >::MinLvl: LockAfter, - | ^^^^^^^^^^^^^^ required by this bound in `MutexKey::<'scope, Lvl>::lock` + | where + | L::MinLvl: LockAfter, + | ^^^^^^^^^^^^^^ required by this bound in `MutexKey::<'scope, Lvl>::lock` = note: this error originates in the macro `generate_level_impls` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/integration.rs b/tests/integration.rs index 8931f5a..ac96ca9 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -12,14 +12,14 @@ use surelock::{ set::LockSet, }; -// -- Convenience .lock() (closure-based, one-shot) -- +// -- Closure-based .lock_with() (one-shot, sorts inline) -- #[test] -fn lock_single() { +fn lock_with_single() { let mut counter: Mutex = Mutex::new(0); lock_scope(|key| { - let ((), _key) = key.lock(&counter, |mut guard| { + let ((), _key) = key.lock_with(&counter, |mut guard| { *guard += 1; }); }); @@ -28,11 +28,11 @@ fn lock_single() { } #[test] -fn lock_returns_value() { +fn lock_with_returns_value() { let counter: Mutex = Mutex::new(42); let value = lock_scope(|key| { - let (v, _key) = key.lock(&counter, |guard| *guard); + let (v, _key) = key.lock_with(&counter, |guard| *guard); v }); @@ -40,12 +40,12 @@ fn lock_returns_value() { } #[test] -fn lock_two_atomic() { +fn lock_with_two_atomic() { let mut a: Mutex = Mutex::new(10); let mut b: Mutex = Mutex::new(20); lock_scope(|key| { - let ((), _key) = key.lock(&(&a, &b), |(mut ga, mut gb)| { + let ((), _key) = key.lock_with(&(&a, &b), |(mut ga, mut gb)| { *ga += 1; *gb += 1; }); @@ -55,15 +55,47 @@ fn lock_two_atomic() { assert_eq!(*b.get_mut(), 21); } -// -- Pre-built LockSet .acquire() (guards returned) -- +// -- Direct .lock() (single mutex, guard returned) -- #[test] -fn acquire_single() { +fn lock_single_mutex() { + let mut counter: Mutex = Mutex::new(0); + + lock_scope(|key| { + let (mut guard, _key) = key.lock(&counter); + *guard += 1; + }); + + assert_eq!(*counter.get_mut(), 1); +} + +#[test] +fn lock_single_then_higher() { + let mut low: Mutex = Mutex::new(10); + let mut high: Mutex> = Mutex::new(20); + + lock_scope(|key| { + let (mut g, key) = key.lock(&low); + *g += 1; + drop(g); + + let (mut g2, _key) = key.lock(&high); + *g2 += 1; + }); + + assert_eq!(*low.get_mut(), 11); + assert_eq!(*high.get_mut(), 21); +} + +// -- Pre-built LockSet .lock() (guards returned) -- + +#[test] +fn lock_set_single() { let mut counter: Mutex = Mutex::new(0); let set = LockSet::new(&counter); lock_scope(|key| { - let (mut guard, _key) = key.acquire(&set); + let (mut guard, _key) = key.lock(&set); *guard += 1; }); @@ -71,13 +103,13 @@ fn acquire_single() { } #[test] -fn acquire_two_atomic() { +fn lock_set_two_atomic() { let mut a: Mutex = Mutex::new(10); let mut b: Mutex = Mutex::new(20); let set = LockSet::new((&a, &b)); lock_scope(|key| { - let ((mut ga, mut gb), _key) = key.acquire(&set); + let ((mut ga, mut gb), _key) = key.lock(&set); *ga += 1; *gb += 1; }); @@ -95,13 +127,13 @@ type LowMutex = Mutex; type HighMutex = Mutex; #[test] -fn cross_level_with_lock() { +fn cross_level_with_lock_with() { let config: LowMutex = Mutex::new(5); let mut account: HighMutex = Mutex::new(100); lock_scope(|key| { - let (bonus, key) = key.lock(&config, |cfg| *cfg); - let ((), _key) = key.lock(&account, |mut acct| { + let (bonus, key) = key.lock_with(&config, |cfg| *cfg); + let ((), _key) = key.lock_with(&account, |mut acct| { *acct += bonus; }); }); @@ -110,16 +142,13 @@ fn cross_level_with_lock() { } #[test] -fn cross_level_with_acquire() { +fn cross_level_with_lock() { let config: LowMutex = Mutex::new(5); let mut account: HighMutex = Mutex::new(100); - let config_set = LockSet::new(&config); - let acct_set = LockSet::new(&account); - lock_scope(|key| { - let (cfg, key) = key.acquire(&config_set); - let (mut acct, _key) = key.acquire(&acct_set); + let (cfg, key) = key.lock(&config); + let (mut acct, _key) = key.lock(&account); *acct += *cfg; }); @@ -131,11 +160,9 @@ fn cross_level_mixed() { let config: LowMutex = Mutex::new(5); let mut account: HighMutex = Mutex::new(100); - let acct_set = LockSet::new(&account); - lock_scope(|key| { - let (bonus, key) = key.lock(&config, |cfg| *cfg); - let (mut acct, _key) = key.acquire(&acct_set); + let (bonus, key) = key.lock_with(&config, |cfg| *cfg); + let (mut acct, _key) = key.lock(&account); *acct += bonus; }); @@ -150,10 +177,10 @@ fn subscope_inherits_level() { let mut account: HighMutex = Mutex::new(0); lock_scope(|key| { - let (val, key) = key.lock(&config, |cfg| *cfg); + let (val, key) = key.lock_with(&config, |cfg| *cfg); let (result, _key) = key.subscope(|inner_key| { - let (r, _inner_key) = inner_key.lock(&account, |mut acct| { + let (r, _inner_key) = inner_key.lock_with(&account, |mut acct| { *acct = val; *acct }); @@ -173,7 +200,7 @@ fn try_lock_scope_returns_some_when_no_scope_active() { let counter: Mutex = Mutex::new(0); let result = try_lock_scope(|key| { - let (v, _key) = key.lock(&counter, |guard| *guard); + let (v, _key) = key.lock_with(&counter, |guard| *guard); v }); @@ -205,7 +232,7 @@ fn key_handle_scope_works() { let mut counter: Mutex = Mutex::new(0); keeper.scope(|key| { - let ((), _key) = key.lock(&counter, |mut guard| { + let ((), _key) = key.lock_with(&counter, |mut guard| { *guard += 1; }); }); @@ -254,8 +281,8 @@ fn key_handle_with_levels() { let mut account: HighMutex = Mutex::new(100); keeper.scope(|key| { - let (bonus, key) = key.lock(&config, |cfg| *cfg); - let ((), _key) = key.lock(&account, |mut acct| { + let (bonus, key) = key.lock_with(&config, |cfg| *cfg); + let ((), _key) = key.lock_with(&account, |mut acct| { *acct += bonus; }); }); @@ -274,7 +301,7 @@ fn locksmith_issue_and_redeem() { let mut counter: Mutex = Mutex::new(0); handle.scope(|key| { - let ((), _key) = key.lock(&counter, |mut guard| { + let ((), _key) = key.lock_with(&counter, |mut guard| { *guard += 1; }); }); @@ -326,7 +353,7 @@ fn locksmith_voucher_to_thread() { let mut handle = voucher.redeem().unwrap(); handle.scope(|key| { let m: Mutex = Mutex::new(99); - let (v, _key) = key.lock(&m, |guard| *guard); + let (v, _key) = key.lock_with(&m, |guard| *guard); v }) }); @@ -366,9 +393,9 @@ fn new_after_linear_chain() { let mut txn = Mutex::new_after(30u32, &account); // Level<2> lock_scope(|key| { - let (cfg_val, key) = key.lock(&config, |g| *g); - let (acct_val, key) = key.lock(&account, |g| *g); - let (tx_val, _key) = key.lock(&txn, |g| *g); + let (cfg_val, key) = key.lock_with(&config, |g| *g); + let (acct_val, key) = key.lock_with(&account, |g| *g); + let (tx_val, _key) = key.lock_with(&txn, |g| *g); assert_eq!(cfg_val + acct_val + tx_val, 60); }); @@ -384,8 +411,8 @@ fn new_after_siblings() { // Siblings can go in a LockSet (same level) let set = LockSet::new((&child_a, &child_b)); lock_scope(|key| { - let (root_val, key) = key.lock(&root, |g| *g); - let ((mut a, mut b), _key) = key.acquire(&set); + let (root_val, key) = key.lock_with(&root, |g| *g); + let ((mut a, mut b), _key) = key.lock(&set); *a += root_val; *b += root_val; }); @@ -402,8 +429,8 @@ fn new_after_skip_levels() { // Skip Level<1>, go directly from Base to Level<2> lock_scope(|key| { - let (r, key) = key.lock(&root, |g| *g); - let (d, _key) = key.lock(&deep, |g| *g); + let (r, key) = key.lock_with(&root, |g| *g); + let (d, _key) = key.lock_with(&deep, |g| *g); assert_eq!(r + d, 4); }); } @@ -419,7 +446,7 @@ fn multi_level_lockset() { let set = LockSet::new((&config, &account)); lock_scope(|key| { - let ((cfg, mut acct), _key) = key.acquire(&set); + let ((cfg, mut acct), _key) = key.lock(&set); *acct += *cfg; }); @@ -435,7 +462,7 @@ fn slice_lockset() { let set = LockSet::new(mutexes.as_slice()); let mut handle = KeyHandle::claim(); handle.scope(|key| { - let (guards, _key) = key.acquire(&set); + let (guards, _key) = key.lock(&set); let sum: u64 = guards.iter().map(|g| **g).sum(); assert_eq!(sum, 10 + 20 + 30 + 40); }); @@ -451,7 +478,7 @@ fn slice_lockset_subset() { let set = LockSet::new(subset); let mut handle = KeyHandle::claim(); handle.scope(|key| { - let (guards, _key) = key.acquire(&set); + let (guards, _key) = key.lock(&set); let sum: u64 = guards.iter().map(|g| **g).sum(); assert_eq!(sum, 2 + 3 + 4); }); @@ -471,8 +498,8 @@ fn mutex_after_same_level_parents() { // Lock both parents atomically (same level), then reconciler let set = LockSet::new((&config, &logger)); lock_scope(|key| { - let ((cfg, log), key) = key.acquire(&set); - let ((), _key) = key.lock(&reconciler, |mut r| { + let ((cfg, log), key) = key.lock(&set); + let ((), _key) = key.lock_with(&reconciler, |mut r| { *r = *cfg + *log; }); }); @@ -492,8 +519,8 @@ fn mutex_after_different_level_parents() { let combined = Mutex::new_after(0u32, (&deep, &shallow)); lock_scope(|key| { - let (r, key) = key.lock(&root, |g| *g); - let (c, _key) = key.lock(&combined, |mut g| { + let (r, key) = key.lock_with(&root, |g| *g); + let (c, _key) = key.lock_with(&combined, |mut g| { *g = r; *g }); @@ -511,8 +538,8 @@ fn type_alias_levels() { let table = Mutex::new_after("users".to_string(), &db); // Level<4> lock_scope(|key| { - let (db_name, key) = key.lock(&db, |g| g.clone()); - let (tbl_name, _key) = key.lock(&table, |g| g.clone()); + let (db_name, key) = key.lock_with(&db, |g| g.clone()); + let (tbl_name, _key) = key.lock_with(&table, |g| g.clone()); assert_eq!(format!("{db_name}.{tbl_name}"), "mydb.users"); }); } @@ -537,7 +564,7 @@ fn concurrent_threads_no_deadlock() { // Every thread acquires in the same order (LockSet sorts by LockId) // regardless of which Arc reference they hold. let set = LockSet::new((&*a, &*b)); - let ((mut ga, mut gb), _key) = key.acquire(&set); + let ((mut ga, mut gb), _key) = key.lock(&set); *ga += i; *gb += i; }); diff --git a/tests/property.rs b/tests/property.rs index 1e42df9..745ab67 100644 --- a/tests/property.rs +++ b/tests/property.rs @@ -37,7 +37,7 @@ fn lockset_sort_invariant_2tuple() { }; lock_scope(|key| { - let ((g1, g2), _key) = key.acquire(&set); + let ((g1, g2), _key) = key.lock(&set); if reverse { assert_eq!(*g1, val_b, "first guard should match b"); assert_eq!(*g2, val_a, "second guard should match a"); @@ -60,7 +60,7 @@ fn lockset_slice_preserves_values() { let set = LockSet::new(mutexes.as_slice()); lock_scope(|key| { - let (guards, _key) = key.acquire(&set); + let (guards, _key) = key.lock(&set); let actual_sum: u64 = guards.iter().map(|g| **g).sum(); assert_eq!( actual_sum, expected_sum, -- knotmirror 0.0.1