Crate lock_order
source ·Expand description
Tools for describing and enforcing lock acquisition order.
Using code defines lock levels with types and then implements traits from
this crate, like relation::LockAfter
to describe how those locks can
be acquired. A separate set of traits in lock
implement locked access
for your type. A complete example:
use std::sync::Mutex;
use lock_order::{impl_lock_after, lock::LockFor, relation::LockAfter, Locked, Unlocked};
#[derive(Default)]
struct HoldsLocks {
a: Mutex<u8>,
b: Mutex<u32>,
}
enum LockA {}
enum LockB {}
impl LockFor<LockA> for HoldsLocks {
type Data = u8;
type Guard<'l> = std::sync::MutexGuard<'l, u8>
where Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.a.lock().unwrap()
}
}
impl LockFor<LockB> for HoldsLocks {
type Data = u32;
type Guard<'l> = std::sync::MutexGuard<'l, u32>
where Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.b.lock().unwrap()
}
}
// LockA is the top of the lock hierarchy.
impl LockAfter<Unlocked> for LockA {}
// LockA can be acquired before LockB.
impl_lock_after!(LockA => LockB);
// Accessing locked state looks like this:
let state = HoldsLocks::default();
// Create a new lock session with the "root" lock level (empty tuple).
let mut locked = Locked::new(&state);
// Access locked state.
let (a, mut locked_a) = locked.lock_and::<LockA>();
let b = locked_a.lock::<LockB>();
The methods on Locked
prevent out-of-order locking according to the
specified lock relationships.
This won’t compile because LockB
does not implement LockBefore<LockA>
:
let state = HoldsLocks::default();
let mut locked = Locked::new(&state);
// Locking B without A is fine, but locking A after B is not.
let (b, mut locked_b) = locked.lock_and::<LockB>();
// compile error: LockB does not implement LockBefore<LockA>
let a = locked_b.lock::<LockA>();
Even if the lock guard goes out of scope, the new Locked
instance returned
by Locked::lock_and will prevent the original one from being used to
access state. This doesn’t work:
let state = HoldsLocks::default();
let mut locked = Locked::new(&state);
let (b, mut locked_b) = locked.lock_and::<LockB>();
drop(b);
let b = locked_b.lock::<LockB>();
// Won't work; `locked` is mutably borrowed by `locked_b`.
let a = locked.lock::<LockA>();
The impl_lock_after
macro provides implementations of LockAfter
for
a pair of locks. The complete lock ordering tree can be spelled out by
calling impl_lock_after
for each parent and child in the hierarchy. One
of the upsides to using impl_lock_after
is that it also prevents
accidental lock ordering inversion. This won’t compile:
enum LockA {}
enum LockB {}
impl_lock_after(LockA => LockB);
impl_lock_after(LockB => LockA);
Modules§
- Traits used to describe lock-ordering relationships.
- Wrappers for using
Locked
features on newtypes.
Macros§
Structs§
- Enforcement mechanism for lock ordering.
- An owned wrapper for
T
that implementsDeref
. - A wrapper for tuples to support implementing
Locked::adopt
. - “Highest” lock level
Type Aliases§
- A convenient alias for a
TupleWrapper
inside anOwnedWrapper
.