Xutex is a high-performance mutex that seamlessly bridges synchronous and asynchronous Rust code with a single type and unified internal representation. Designed for extremely low-latency lock acquisition under minimal contention, it achieves near-zero overhead on the fast path while remaining runtime-agnostic.
- β‘ Blazing-fast async performance: Up to 50Γ faster than standard sync mutexes in single-threaded async runtimes, and 3β5Γ faster in multi-threaded async runtime under extreme contention.
- π Hybrid API: Use the same lock in both sync and async contexts
- β‘ 8-byte lock state: Single
AtomicPtron 64-bit platforms (guarded data stored separately) - π Zero-allocation fast path: Lock acquisition requires no heap allocation when uncontended
- β»οΈ Smart allocation reuse: Object pooling minimizes allocations under contention
- π― Runtime-agnostic: Works with Tokio, async-std, monoio, or any executor using
std::task::Waker - π Lock-free fast path: Single CAS operation for uncontended acquisition
- π¦ Minimal footprint: Compact state representation with lazy queue allocation
- π‘οΈ No-std compatible: Fully compatible with
no_stdenvironments, relying only oncoreandalloc
[dependencies]
xutex = "0.2"Or via cargo:
# with std
cargo add xutex
# for no-std environments
cargo add xutex --no-default-features#[cfg(feature = "std")]
fn example() {
use xutex::Mutex;
let mutex = Mutex::new(0);
{
let mut guard = mutex.lock();
*guard += 1;
} // automatically unlocked on drop
assert_eq!(*mutex.lock(), 1);
}use xutex::AsyncMutex;
async fn increment(mutex: &AsyncMutex<i32>) {
let mut guard = mutex.lock().await;
*guard += 1;
}Convert seamlessly between sync and async:
#[cfg(feature = "std")]
use xutex::Mutex;
#[cfg(feature = "std")]
async fn example(mutex: &Mutex<i32>) {
let async_ref = mutex.as_async();
let guard = async_ref.lock().await;
}#[cfg(feature = "std")]
fn example(){
use xutex::{Mutex, AsyncMutex};
// Async β Sync
let async_mutex = AsyncMutex::new(5);
let sync_ref: &Mutex<_> = async_mutex.as_sync();
let guard = sync_ref.lock();
drop(guard);
// Block on async mutex from sync context
let guard = async_mutex.lock_sync();
}-
Atomic state machine: Three states encoded in a single pointer:
UNLOCKED(null): Lock is freeLOCKED(sentinel): Lock held, no waitersUPDATING: Queue initialization in progressQUEUE_PTR: Lock held with waiting tasks/threads
-
Lock-free fast path: Uncontended acquisition uses a single
compare_exchange -
Lazy queue allocation: Wait queue created only when contention occurs
-
Pointer tagging: LSB tagging prevents race conditions during queue modifications
-
Stack-allocated waiters:
Signalnodes live on the stack, forming an intrusive linked list -
Optimized memory ordering: Careful use of
Acquire/Releasesemantics -
Adaptive backoff: Exponential backoff reduces cache thrashing under contention
-
Minimal heap allocation: At most one allocation per contended lock via pooled queue reuse, additional waiters require zero allocations
Run benchmarks on your machine:
cargo benchExpected Performance (varies by hardware):
- Uncontended: ~1-3ns per lock/unlock cycle (single CAS operation)
- High contention: 2-3Γ faster than
tokio::sync::Mutexin async contexts - Sync contexts: Performance comparable to
std::sync::Mutexwith minimal overhead from queue pointer checks under high contention; matchesparking_lotperformance in low-contention scenarios
βββββββββββββββββββββββββββββββββββββββββββββββ
β Mutex<T> / AsyncMutex<T> β
β βββββββββββββββββββββββββββββββββββββββββ β
β β MutexInternal<T> β β
β β β’ queue: AtomicPtr<QueueStructure> β β
β β β’ inner: UnsafeCell<T> β β
β βββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
ββ UNLOCKED (null) βββββββββββββββΊ Lock available
β
ββ LOCKED (sentinel) ββββββββββββββΊ Lock held, no waiters
β
ββ Queue pointer ββββββββββββββββββΊ Lock held, waiters queued
β
βΌ
βββββββββββββββββββ
β SignalQueue β
β (linked list) β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Signal ββββββΊβ Signal ββββββΊ ...
β β’ waker β β β’ waker β
β β’ value β β β’ value β
βββββββββββββββββββ βββββββββββββββββββ
Each waiter tracks its state through atomic transitions:
SIGNAL_UNINIT (0): Initial stateSIGNAL_INIT_WAITING (1): Enqueued and waitingSIGNAL_SIGNALED (2): Lock grantedSIGNAL_RETURNED (!0): Guard has been returned
- Public API: 100% safe Rust
- Internal implementation: Carefully controlled
unsafeblocks for:- Queue manipulation (pointer tagging prevents use-after-free)
- Guard creation (guaranteed by state machine)
- Memory ordering (documented and audited)
| Method | Description |
|---|---|
new(data: T) |
Create a new synchronous mutex |
lock() |
Acquire the lock (blocks current thread) |
try_lock() |
Attempt non-blocking acquisition |
lock_async() |
Acquire asynchronously (returns Future) |
as_async() |
View as &AsyncMutex<T> |
to_async() |
Convert to AsyncMutex<T> |
to_async_arc() |
Convert Arc<Mutex<T>> to Arc<AsyncMutex<T>> |
| Method | Description |
|---|---|
new(data: T) |
Create a new asynchronous mutex |
lock() |
Acquire the lock (returns Future) |
try_lock() |
Attempt non-blocking acquisition |
lock_sync() |
Acquire synchronously (blocks current thread) |
as_sync() |
View as &Mutex<T> |
to_sync() |
Convert to Mutex<T> |
to_sync_arc() |
Convert Arc<AsyncMutex<T>> to Arc<Mutex<T>> |
Implements Deref<Target = T> and DerefMut for transparent access to the protected data. Automatically releases the lock on drop.
- High-frequency, low-contention async locks
- Hybrid applications mixing sync and async code
- Performance-critical sections with short critical regions
- Runtime-agnostic async libraries
- Situations requiring zero-allocation fast paths
- Predominantly synchronous workloads: In pure sync environments without async interaction,
std::sync::Mutexmay offer slightly better performance due to lower abstraction overhead - Read-heavy workloads: If your use case involves frequent reads with infrequent writes, consider using
RwLockimplementations (e.g.,std::sync::RwLockortokio::sync::RwLock) that allow multiple concurrent readers - Mutex poison state: Cases where
std::sync::Mutexpoisoning semantics are required
- 8-byte claim: Refers to lock metadata only on 64-bit platforms; guarded data
Tstored separately - No poisoning: Unlike
std::sync::Mutex, panics don't poison the lock - Sync overhead: Slight performance cost vs
std::sync::Mutexin pure-sync scenarios (~1-5%)
Run the test suite:
# Standard tests
cargo test
# With Miri (undefined behavior detection)
cargo +nightly miri test
# Benchmarks
cargo bench- Implement
RwLockvariant with shared/exclusive locking - Explore lock-free linked list implementation for improved wait queue performance
Contributions are welcome! Please:
- Run
cargo +nightly fmtandcargo clippybefore submitting - Add tests for new functionality
- Update documentation as needed
- Verify
cargo miri testpasses - Note: This library is
no-stdcompatible; usecoreandallocinstead ofstd. Ensurecargo testandcargo test --no-default-featuresrun without warnings.
Licensed under the MIT License.
Author: Khashayar Fereidani
Repository: github.com/fereidani/xutex