Deloxide
Deloxide scrubs your threads clean by detecting deadlocks in real time—keeping your system smooth, safe, and corrosion-free. 🦀🧼🔒
Install / Use
/learn @Emivvvvv/DeloxideREADME
<img src='images/deloxide_logo_orange.png' height='25'> Deloxide - Cross-Language Deadlock Detector
Deloxide is a cross-language deadlock detection library with visualization support. It tracks mutex and reader-writer lock operations in multi-threaded applications to detect, report, and visualize potential deadlocks in real-time.
Table of Contents
- Features
- Building and Installation
- Quick Start
- Visualization
- Project Architecture
- Lock Order Graph
- Stress Testing
- Comparison with Other Solutions
- Performance & Validation
- Documentation
- License
Features
- Real-time deadlock detection - Detects deadlocks as they happen using a Dual Detection Architecture (WFG + LOG)
- Zero False Positives - Wait-For Graph (WFG) analysis ensures 100% precision for active deadlocks
- Optimistic Fast Path - "Always-on" monitoring with negligible overhead
- Cross-language support - Core implementation in Rust with C bindings
- Stress Testing Framework - Probabilistic scheduling with Component-Based Targeting
- Visual Diagnostics - Serverless, privacy-preserving visualization of thread interactions (see example here)
- Easy integration - Drop-in replacements for
parking_lotprimitives
[!NOTE] Cross-platform support: Rust API works on Windows, macOS, and Linux. The C API is POSIX-first and ships with pthread-based convenience macros for macOS/Linux; on Windows those macros are disabled (see below) but the core C functions are fully usable.
Building and Installation
Rust
Deloxide is available on crates.io. You can add it as a dependency in your Cargo.toml:
[dependencies]
deloxide = "1.0"
With lock order graph:
[dependencies]
deloxide = { version = "1.0", features = ["lock-order-graph"] }
With stress testing:
[dependencies]
deloxide = { version = "1.0", features = ["stress-test"] }
With logging and visualization:
[dependencies]
deloxide = { version = "1.0", features = ["logging-and-visualization"] }
Or install the CLI tool to showcase deadlock logs directly:
cargo install deloxide
deloxide my_deadlock.log # Opens visualization in browser
For development builds:
# Standard build
cargo build --release
# With lock order graph feature
cargo build --release --features lock-order-graph
# With stress testing feature
cargo build --release --features stress-test
# With both features
cargo build --release --features lock-order-graph,stress-test
C
For C programs, you'll need to compile the Rust library and link against it:
# Build the Rust library
cargo build --release
# With lock order graph feature
cargo build --release --features lock-order-graph
# With stress testing feature
cargo build --release --features stress-test
# With both features
cargo build --release --features lock-order-graph,stress-test
# Compile your C program with Deloxide
gcc -Iinclude your_program.c -Ltarget/release -ldeloxide -lpthread -o your_program
A Makefile is included in the repository to simplify building and testing with C programs. It handles building the Rust library and compiling the C test programs automatically.
C API portability notes
-
Thread ID size across FFI
- The C header uses
uintptr_tfor all thread IDs; the Rust side usesusize. This ensures correct sizes on LP64 (Linux/macOS) and LLP64 (Windows).
- The C header uses
-
pthread-based helpers are POSIX-only
- The convenience macros
DEFINE_TRACKED_THREADandCREATE_TRACKED_THREADdepend onpthread.hand are available only on non-Windows platforms. - On Windows, these macros are disabled at compile time. You can still use the full C API by manually registering thread lifecycle events.
- The convenience macros
-
Manual thread registration (Windows or custom runtimes)
- Create your thread using your platform's API.
- In the thread entry, call
deloxide_register_thread_spawn(child_tid, parent_tid)once. On the thread, get IDs fromdeloxide_get_thread_id(). - Before the thread returns, call
deloxide_register_thread_exit(current_tid).
Minimal example sketch (pseudo-C):
// In parent, capture parent thread id uintptr_t parent_tid = deloxide_get_thread_id(); // Create thread with OS API (e.g., _beginthreadex / CreateThread) // In child thread entry: uintptr_t child_tid = deloxide_get_thread_id(); deloxide_register_thread_spawn(child_tid, parent_tid); // ... user work ... deloxide_register_thread_exit(child_tid);
Quick Start
Rust
Deloxide provides drop-in replacements for parking_lot synchronization primitives with added deadlock detection capabilities. These primitives are API-compatible with parking_lot and serve as near drop-in replacements for std::sync (requiring only the removal of .unwrap() calls since poisoning is not supported).
deloxide::thread
A drop-in replacement for std::thread that automatically tracks thread lifecycle events. All std::thread functions and types are available with added deadlock detection:
// All std::thread items are re-exported
pub use std::thread::{
AccessError, JoinHandle, LocalKey, Result, Scope,
ScopedJoinHandle, Thread, ThreadId, available_parallelism,
current, panicking, park, park_timeout, sleep, yield_now,
};
// Custom spawn function with tracking
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where F: FnOnce() -> T + Send + 'static, T: Send + 'static;
// Custom Builder with tracking
pub struct Builder { /* ... */ }
Using tracked threads is identical to using std::thread:
use deloxide::thread;
// Spawn a tracked thread - exactly like std::thread::spawn
let handle = thread::spawn(|| {
println!("Hello from tracked thread!");
42
});
// All std::thread functions work
thread::yield_now();
thread::sleep(Duration::from_millis(100));
let current = thread::current();
// Builder pattern supported
let handle = thread::Builder::new()
.name("worker".to_string())
.stack_size(32 * 1024)
.spawn(|| { /* ... */ })
.unwrap();
// Join works the same way
let result = handle.join().unwrap();
assert_eq!(result, 42);
It automatically registers thread spawn/exit events for deadlock detection, visualization, and debugging purposes.
Deloxide::Mutex
A drop-in replacement for parking_lot::Mutex. It is also a direct alternative to std::sync::Mutex, but without lock poisoning (removing the need for .unwrap() on lock acquisition):
pub struct Mutex<T> {
id: LockId,
inner: ParkingLotMutex<T>,
creator_thread_id: ThreadId,
}
impl<T> Mutex<T> {
pub fn new(data: T) -> Self;
pub fn lock(&self) -> MutexGuard<'_, T>;
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>>;
pub fn into_inner(self) -> T where T: Sized;
pub fn get_mut(&mut self) -> &mut T;
pub fn id(&self) -> LockId;
pub fn creator_thread_id(&self) -> ThreadId;
}
impl<T: Default> Default for Mutex<T> { /* ... */ }
impl<T> From<T> for Mutex<T> { /* ... */ }
All std::sync::Mutex methods are supported (except poisoning-related ones, as parking_lot doesn't use poisoning).
Deloxide::RwLock
A drop-in replacement for parking_lot::RwLock. It is also a direct alternative to std::sync::RwLock, but without lock poisoning:
pub struct RwLock<T> {
id: LockId,
inner: ParkingLotRwLock<T>,
creator_thread_id: ThreadId,
}
impl<T> RwLock<T> {
pub fn new(data: T) -> Self;
pub fn read(&self) -> RwLockReadGuard<'_, T>;
pub fn write(&self) -> RwLockWriteGuard<'_, T>;
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>>;
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>>;
pub fn into_inner(self) -> T where T: Sized;
pub fn get_mut(&mut self) -> &mut T;
pub fn id(&self) -> LockId;
pub fn creator_thread_id(&self) -> ThreadId;
}
impl<T: Default> Default for RwLock<T> { /* ... */ }
impl<T> From<T> for RwLock<T> { /* ... */ }
All std::sync::RwLock methods are supported (except poisoning-related ones).
Deloxide::Condvar
A drop-in replacement for parking_lot::Condvar. It serves as a replacement for std::sync::Condvar but interacts with Deloxide::Mutex.
pub struct Condvar {
id: CondvarId,
inner: ParkingLotCondvar,
}
impl Condvar {
pub fn new() -> Self;
pub fn wait<T>(&self, guard: &mut MutexGuard<'_, T>);
pub fn wait_while<T, F>(&self, guard: &mut MutexGuard<'_, T>, condition: F)
where F: FnMut(&mut T) -> bool;
pub fn wait_timeout<T>(&self, guard: &mut MutexGuard<'_, T>, timeout: Duration) -> bool;
pub fn wait_timeout_while<T, F>(&self, guard: &mut MutexGuard<'_, T>,
timeout: Duration, condition: F) -> bool
where F: FnMut(&mut T) -> bool;
pub fn notify_one(&self);
pub fn notify_all(&self);
pub fn id(&self) -> CondvarId;
}
impl Default for Condvar { /* ... */ }
All std::sync::Condvar methods are supported.
Complete Usage Example
Here's a comprehensive example demonstrating all Deloxide primitives in a single scenario:
use deloxide::{Delox
