surelock
Deadlock-freedom for Rust
Surelock prevents deadlocks by breaking the circular-wait Coffman condition (1971) via two complementary mechanisms:
| Mechanism | Granularity | Acquisition | Enforcement | Description |
|---|---|---|---|---|
LockSet |
Fine | Atomic | By construction | Multiple locks acquired at once, sorted by monotonic LockId |
| Levels | Coarse | Incremental | Compile-time | LockAfter trait bounds on a consumed-and-re-emitted MutexKey |
Every lock call is infallible or doesn't compile. No Result, no Option, no panic on any lock acquisition path.
Quick Start
use ;
let counter: = new;
let mut handle = claim;
handle.scope;
Type Lifecycle
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ Explicit multi-core
│
│ Locksmith
(forge) │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐
│ │ │ std (default)
▼ │ │
│ KeyVoucher │ KeyHandle
(deliver) │ (claim) │
└ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ┘ └ ─ ─ ─ ┼ ─ ─ ─ ┘
│ │
└────────────┬──────────┘
│
▼
MutexKey
(scope)
│
├───▶ MutexGuard(s)
│ (access)
▼
MutexKey
(subscope)
│
├───▶ MutexGuard(s)
│ (access)
▼
...
| Type | Role | Created by |
|---|---|---|
Locksmith |
Factory, issues vouchers (singleton) | Locksmith::new(limit) |
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.lock_with() |
How It Works
LockSet: Fine-Grained, Implicitly Runtime Sorted
When you need multiple locks at the same level, LockSet handles it. Each Mutex gets a unique, monotonic LockId at creation. LockSet pre-sorts by these IDs and acquires in that order, every time. The below is an exaggerated timeline to help explain how these locks work:
Thread A Thread B
│ │
▼ ▼
LockSet::new((&acct_1, &acct_2)) LockSet::new((&acct_2, &acct_1))
│ │
▼ ▼
sorted: [acct_1, acct_2] sorted: [acct_1, acct_2]
│ │
├─ takes acct_1 lock │
│ ├─ waits for acct_1 lock
├─ takes acct_2 lock │
│ │
~~~~~~~~~~~~~~~~~~~~~~~TIME PASSES~~~~~~~~~~~~~~~~~~~~~~~
│ │
├─ release acct_2 │
│ │ (still waiting for lock 1 first)
├─ release acct_1 │
│ ├─ takes acct_1 lock
│ ├─ takes acct_2 lock
│ │
▼ ▼
OK OK (no cycle possible)
Levels: Coarse-Grained, Compile-Time Ordered
Use Level<N> types with semantic aliases:
use Level;
type Config = ;
type Account = ;
type Transaction = ;
The MutexKey tracks which level you've reached, and the compiler rejects backwards acquisition.
Mutex Construction
use Mutex;
// Default level (Level<0>), default backend (StdMutex)
let config: = new;
// Auto-incrementing level via new_higher
let account = new_higher; // Level<1>
let txn = new_higher; // Level<2>
// Siblings share a level
let acct_a = new_higher; // Level<1>
let acct_b = new_higher; // Level<1>
// Multi-parent
let reconciler = new_higher;
// max(Level<1>, Level<2>) + 1 = Level<3>
Method syntax is also available via the [NewHigher] trait -- especially
useful with Arc and custom backends where the backend is inherited
from the parent:
use ;
let config: = new;
let account = config.new_higher; // Level<1>
let txn = account.new_higher; // Level<2>
Specify just the level to use the default backend: Mutex<u32, Level<3>>.
Key Patterns
// Enter a scope
handle.scope;
// Lock (single mutex or LockSet, guards returned)
let = key.lock;
let = key.lock;
// Lock with closure (one-shot, sorts inline)
let = key.lock_with;
// Nested sub-scope
let = key.subscope;
Scope Entry
Two ways to enter a lock scope:
KeyHandle (recommended) -- static nesting prevention via &mut self. Works on all targets including no_std:
use ;
let mut handle = claim;
handle.scope;
// Sequential scopes are fine
handle.scope;
// Nesting is a compile error:
// handle.scope(|key1| {
// handle.scope(|key2| { ... });
// ^^^^ error: already mutably borrowed
// });
lock_scope / try_lock_scope (std-only) -- ambient convenience with runtime nesting check:
use ;
let counter: = new;
lock_scope;
[!WARNING]
lock_scope/try_lock_scopeare only available with thestdfeature. Onno_std, useKeyHandlefor static nesting prevention. See the crate documentation for details.
Backend Agnostic
Mutex<T, Lvl, R> is generic over any RawMutex implementation. Lvl defaults to Level<0> (= Base) and R defaults to StdMutex on std:
[]
# std users (default -- Mutex<u32> just works)
= "0.1"
# lock_api users (parking_lot, spin, etc.)
= { = "0.1", = ["lock-api"] }
= "0.12"
# no_std users
= { = "0.1", = false, = ["lock-api"] }
= { = "0.9", = ["lock_api", "spin_mutex"] }
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
yes | StdMutex default backend, thread_local! scope check |
atomic-u64 |
yes | Use AtomicU64 for LockId (default AtomicU32 otherwise) |
lock-api |
no | Blanket RawMutex impl for any lock_api::RawMutex backend |
escape-hatch |
no | Mutex::unchecked_lock() -- std-like direct lock |
portable-atomic |
no | Use portable-atomic for all atomics |
critical-section |
no | Enable CAS emulation via critical-section (implies portable-atomic) |
cortex-m |
no | Convenience: enables critical-section for Cortex-M targets |
levels-32 |
no | Extend numbered levels from 16 to 32 |
levels-64 |
no | Extend numbered levels from 16 to 64 |
Modules
| Module | Key Types | Description |
|---|---|---|
acquirable |
Acquirable |
Internal trait + impls (single, tuples) |
id |
LockId |
Monotonic global counter |
key |
MutexKey, lock_scope, try_lock_scope |
Scope token, ambient entry points |
key_handle |
KeyHandle |
Per-thread scope capability |
key_voucher |
KeyVoucher |
Transferable token (Send) |
level |
Level<N>, IsLevel, LockAfter, Base |
Numbered levels, ordering traits |
locksmith |
Locksmith |
Factory for KeyVouchers |
mutex |
Mutex<T, Lvl, R>, MutexGuard |
Deadlock-free mutex, RAII guard |
raw_mutex |
RawMutex, StdMutex |
Backend trait, lock_api adapter |
set |
LockSet |
Pre-sorted lock collection |
Prior Art
Surelock builds on ideas from two libraries:
-
happylock(botahamec, CC0-1.0) -- Introduced theLockCollectionpattern: a capability token (ThreadKey) combined with sorted multi-lock acquisition. Surelock borrows this pattern, replacing address-based ordering with a stable monotonicLockIdcounter (addresses are unstable across moves andVecreallocations), dropping thestdrequirement, and removingunsafefrom the public API. -
lock_tree(Google Fuchsia, BSD) -- IntroducedLockAfter<A>traits for compile-time ordering of lock categories, enforced via witness-token consumption. Surelock extends this with same-level multi-lock viaLockSet(whichlock_treecannot do) and makes levels opt-in with aBasedefault.
Neither library alone covers both safe dynamic multi-lock and safe incremental locking. The combination -- with stable IDs and no_std throughout -- is surelock's contribution.
Also informed by:
tracing-mutex-- Runtime deadlock detection via dependency graph tracking.ordered-locks-- Compile-time ordering with a fixed set of 5 levels.
Grounded in Coffman, Elphick, and Shoshani's classic System Deadlocks (1971).
For detailed comparisons, see design/comparison/happylock.md and design/comparison/lock_tree.md.
Design Documents
The design/ directory contains architecture documentation, design rationale, and detailed comparisons with prior art.
License
Licensed under either of
at your option.