4
48
Fork
You've already forked surelock
1
Statically prevent deadlocks
  • Rust 92.5%
  • Nix 7.5%
Brooklyn Zelenka 7e7d1f2eb4
All checks were successful
ci/woodpecker/push/spellcheck Pipeline was successful
ci/woodpecker/push/doc Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
Fix doc warnings
2026年04月05日 23:32:14 -07:00
.forgejo
.woodpecker Fix thumbv6m tests 2026年04月05日 16:35:06 -07:00
design Improve new_after as a trait NewHigher 2026年04月05日 21:53:11 -07:00
nix
src Fix doc warnings 2026年04月05日 23:32:14 -07:00
tests Appease clippy and add missing test cases 2026年04月05日 23:27:05 -07:00
.gitignore
_typos.toml
Cargo.lock Fix thumbv6m tests 2026年04月05日 16:35:06 -07:00
Cargo.toml Fix thumbv6m tests 2026年04月05日 16:35:06 -07:00
deny.toml
flake.lock
flake.nix
HACKING.md
LICENSE-APACHE
LICENSE-MIT
NO_STD_GUIDE.md Fix thumbv6m tests 2026年04月05日 16:35:06 -07:00
README.md Improve new_after as a trait NewHigher 2026年04月05日 21:53:11 -07:00
rustfmt.toml Appease Clippy! 2026年04月05日 17:39:32 -07:00

surelock

Deadlock-freedom for Rust

CI License no_std

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_scope are only available with the std feature. On no_std, use KeyHandle for 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 the LockCollection pattern: a capability token (ThreadKey) combined with sorted multi-lock acquisition. Surelock borrows this pattern, replacing address-based ordering with a stable monotonic LockId counter (addresses are unstable across moves and Vec reallocations), dropping the std requirement, and removing unsafe from the public API.

  • lock_tree (Google Fuchsia, BSD) -- Introduced LockAfter<A> traits for compile-time ordering of lock categories, enforced via witness-token consumption. Surelock extends this with same-level multi-lock via LockSet (which lock_tree cannot do) and makes levels opt-in with a Base default.

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.