- Rust 92.5%
- Nix 7.5%
| .forgejo | ||
| .woodpecker | Fix thumbv6m tests | |
| design | Improve new_after as a trait NewHigher | |
| nix | ||
| src | Fix doc warnings | |
| tests | Appease clippy and add missing test cases | |
| .gitignore | ||
| _typos.toml | ||
| Cargo.lock | Fix thumbv6m tests | |
| Cargo.toml | Fix thumbv6m tests | |
| deny.toml | ||
| flake.lock | ||
| flake.nix | ||
| HACKING.md | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| NO_STD_GUIDE.md | Fix thumbv6m tests | |
| README.md | Improve new_after as a trait NewHigher | |
| rustfmt.toml | Appease Clippy! | |
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
usesurelock::{key_handle::KeyHandle,mutex::Mutex};letcounter: Mutex<u32>=Mutex::new(0);letmuthandle=KeyHandle::claim();handle.scope(|key|{let(mutguard,_key)=key.lock(&counter);*guard+=1;});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:
usesurelock::level::Level;type Config=Level<0>;type Account=Level<1>;type Transaction=Level<2>;The MutexKey tracks which level you've reached, and the compiler rejects backwards acquisition.
Mutex Construction
usesurelock::mutex::Mutex;// Default level (Level<0>), default backend (StdMutex)
letconfig: Mutex<u32>=Mutex::new(10);// Auto-incrementing level via new_higher
letaccount=Mutex::new_higher(20u32,&config);// Level<1>
lettxn=Mutex::new_higher(30u32,&account);// Level<2>
// Siblings share a level
letacct_a=Mutex::new_higher(1u32,&config);// Level<1>
letacct_b=Mutex::new_higher(2u32,&config);// Level<1>
// Multi-parent
letreconciler=Mutex::new_higher(0u32,(&acct_a,&txn));// 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:
usesurelock::{level::NewHigher,mutex::Mutex};letconfig: Mutex<u32>=Mutex::new(10);letaccount=config.new_higher(20u32);// Level<1>
lettxn=account.new_higher(30u32);// Level<2>
Specify just the level to use the default backend: Mutex<u32, Level<3>>.
Key Patterns
// Enter a scope
handle.scope(|key|{/* ... */});// Lock (single mutex or LockSet, guards returned)
let(guard,key)=key.lock(&mutex);let((ga,gb),key)=key.lock(&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|{/* ... */});Scope Entry
Two ways to enter a lock scope:
KeyHandle (recommended) -- static nesting prevention via &mut self. Works on all targets including no_std:
usesurelock::{key_handle::KeyHandle,mutex::Mutex};letmuthandle=KeyHandle::claim();handle.scope(|key|{letm: Mutex<u32>=Mutex::new(42);let(val,_key)=key.lock_with(&m,|g|*g);assert_eq!(val,42);});// Sequential scopes are fine
handle.scope(|key|{/* fresh key */});// 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:
usesurelock::{key::lock_scope,mutex::Mutex};letcounter: Mutex<u32>=Mutex::new(0);lock_scope(|key|{let(mutguard,_key)=key.lock(&counter);*guard+=1;});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:
[dependencies]
# std users (default -- Mutex<u32> just works)
surelock = "0.1"
# lock_api users (parking_lot, spin, etc.)
surelock = { version = "0.1", features = ["lock-api"] }
parking_lot = "0.12"
# no_std users
surelock = { version = "0.1", default-features = false, features = ["lock-api"] }
spin = { version = "0.9", features = ["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.