Loading page…
Rust walkthroughs
Loading page…
parking_lot::Mutex::new avoid poisoning compared to std::sync::Mutex in panic scenarios?parking_lot::Mutex eliminates mutex poisoning by allowing the lock to remain usable after a panic, whereas std::sync::Mutex marks itself as "poisoned" when a thread panics while holding the lock, forcing callers to handle the poisoned state. The standard library's approach prioritizes safety by assuming that data protected by a mutex might be in an inconsistent state after a panic, requiring explicit recovery. parking_lot takes the position that this safety is often unnecessary—the mutex's integrity is preserved regardless of panic, and the underlying data's consistency is the programmer's responsibility. This design choice simplifies error handling in most applications where panics are fatal anyway, while still being safe for the cases where panics are caught and execution continues.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec
![1, 2, 3]));
// Thread panics while holding the lock
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut vec = data_clone.lock().unwrap();
vec.push(4);
panic!("Thread panicked while holding mutex!");
});
// Wait for thread to finish (it panicked)
handle.join().unwrap_err(); // Join the panicked thread
// Now try to lock the mutex
let result = data.lock();
println!("Lock result: {:?}", result);
// Err(PoisonError { .. })
// The mutex is "poisoned"
// Must handle the poisoned state
let vec = result.unwrap_or_else(|e| e.into_inner()
);
println!("Data: {:?}", *vec);
// Data: [1, 2, 3, 4]
// The push happened before the panic
}When a thread panics while holding a std::sync::Mutex, the mutex becomes "poisoned" to signal potential data corruption.
use std::sync::Mutex;
use std::thread;
fn std_mutex_example() {
let mutex = Mutex::new(vec
![1, 2, 3]);
// Normal operation
{
let mut data = mutex.lock().unwrap();
data.push(4);
}
// Lock released normally, no poisoning
// Panic while holding lock
let result = thread::spawn(|| {
let mut data = mutex.lock().unwrap();
data.push(5);
panic!("Intentional panic");
}).join();
// Thread panicked
assert!(result.is_err());
// Mutex is now poisoned
let lock_result = mutex.lock();
println!("Is poisoned: {}", lock_result.is_err());
// Is poisoned: true
// Access requires explicit handling
match lock_result {
Ok(data) => println!("Got data: {:?}", *data),
Err(poison_err) => {
println!("Mutex poisoned!");
// Can still access data, but must explicitly acknowledge
let data = poison_err.into_inner();
println!("Data (with potential inconsistency): {:?}", *data);
}
}
}std::sync::Mutex forces explicit handling of poisoned state, ensuring programmers acknowledge potential inconsistency.
use parking_lot::Mutex;
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec
![1, 2, 3]));
// Thread panics while holding lock
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut vec = data_clone.lock();
vec.push(4);
panic!("Thread panicked!");
});
// Wait for panic
handle.join().unwrap_err();
// parking_lot Mutex is NOT poisoned
// lock() returns MutexGuard directly, not Result
{
let mut vec = data.lock();
vec.push(5);
println!("Data: {:?}", *vec);
// Data: [1, 2, 3, 4, 5]
}
// No unwrapping needed, no Result type
}parking_lot::Mutex::lock returns MutexGuard directly, not LockResult<MutexGuard>, simplifying the API.
use std::sync::Mutex as StdMutex;
use parking_lot::Mutex as PlMutex;
fn main() {
// std::sync::Mutex returns Result
let std_mutex = StdMutex::new(42);
let std_guard: Result<_, _> = std_mutex.lock();
// Must handle potential poison
let _value = std_guard.unwrap();
// parking_lot::Mutex returns guard directly
let pl_mutex = PlMutex::new(42);
let pl_guard = pl_mutex.lock();
// Just a MutexGuard, no Result
let _value = *pl_guard;
}The return types reflect different design philosophies: std emphasizes explicit error handling, parking_lot emphasizes simplicity.
use parking_lot::Mutex;
struct Data {
value: i32,
name: String,
}
fn demonstrate_no_poisoning() {
let mutex = Mutex::new(Data {
value: 0,
name: String::new(),
});
std::thread::scope(|s| {
s.spawn(|| {
let mut data = mutex.lock();
data.value = 42;
// Panic occurs here
panic!("Before completing update!");
});
});
// parking_lot's reasoning:
// 1. The mutex itself is still valid (lock/unlock works)
// 2. Data MAY be inconsistent (value updated, name not)
// 3. But "poisoning" doesn't prevent inconsistency
// 4. It only signals that inconsistency might exist
// 5. Programmer must still decide what to do
// So parking_lot skips the ceremony:
let data = mutex.lock();
println!("Value: {}", data.value);
// Value: 42 (partial update visible)
}The parking_lot philosophy: poisoning adds complexity without truly solving data consistency—the programmer still decides recovery strategy.
use std::sync::Mutex;
use std::thread;
fn handle_poisoning() {
let mutex = Mutex::new(vec
![1, 2, 3]);
// Simulate panic
thread::spawn(|| {
let mut data = mutex.lock().unwrap();
data.push(4);
panic!("Panic during update");
}).join().unwrap_err();
// Pattern 1: Ignore poisoning, recover data anyway
let data = mutex.lock().unwrap_or_else(|e| e.into_inner());
println!("Recovered: {:?}", *data);
// Pattern 2: Treat poisoning as fatal
let mutex2 = Mutex::new(42);
thread::spawn(|| {
let _data = mutex2.lock().unwrap();
panic!("Another panic");
}).join().unwrap_err();
let data2 = mutex2.lock().expect("Mutex poisoned, aborting");
// This will panic if poisoned
// Pattern 3: Recover and continue
let mutex3 = Mutex::new(String::new());
thread::spawn(|| {
let mut data = mutex3.lock().unwrap();
data.push_str("partial");
panic!("Interrupted write");
}).join().unwrap_err();
// Clear potentially corrupted state
let mut data3 = mutex3.lock().unwrap_or_else(|e| {
let mut guard = e.into_inner();
guard.clear(); // Reset to known state
guard
});
data3.push_str("fresh start");
}std::sync::Mutex poisoning requires explicit recovery strategies, acknowledged through Result types.
use std::sync::{Arc, Mutex};
use std::thread;
// Scenario where poisoning is valuable: critical invariants
struct BankAccount {
balance: i64,
// Invariant: balance should never be negative
}
fn transfer(
from: Arc<Mutex<BankAccount>>,
to: Arc<Mutex<BankAccount>>,
amount: i64,
) -> Result<(), String> {
let mut from_guard = from.lock().map_err(|_| "Source account poisoned")?;
let mut to_guard = to.lock().map_err(|_| "Destination account poisoned")?;
// Invariant check
if from_guard.balance < amount {
return Err("Insufficient funds".to_string());
}
from_guard.balance -= amount;
// If panic happens here, invariant broken:
// - from.balance reduced
// - to.balance NOT increased
// Money "lost"
to_guard.balance += amount;
Ok(())
}
// Poisoning signals that such partial updates might have occurred
// Receiver can decide to:
// 1. Abort the entire operation
// 2. Attempt recovery
// 3. Log and continuePoisoning is valuable when invariants must be preserved and partial updates are dangerous.
use std::sync::Mutex;
use parking_lot::Mutex as PlMutex;
// Scenario where poisoning adds friction: read-only or simple data
struct Cache {
entries: Vec<String>,
}
fn with_std_mutex() {
let cache = Mutex::new(Cache { entries: vec
![] });
// Even simple reads require unwrapping
let data = cache.lock().unwrap();
println!("Entries: {}", data.entries.len());
}
fn with_parking_lot() {
let cache = PlMutex::new(Cache { entries: vec
![] });
// Direct access, no unwrapping
let data = cache.lock();
println!("Entries: {}", data.entries.len());
}
// For caches, metrics, counters, etc.:
// - Panics typically terminate the program anyway
// - Poisoning just adds unwrapping overhead
// - parking_lot's approach is simplerFor simple data without complex invariants, poisoning adds ceremony without practical benefit.
use parking_lot::Mutex;
// Important: parking_lot still guarantees memory safety
fn memory_safety() {
let mutex = Mutex::new(vec
![1, 2, 3]);
// The mutex lock/unlock mechanism is always correct
// Even after a panic:
std::thread::scope(|s| {
s.spawn(|| {
let _guard = mutex.lock();
panic!("Panic while locked!");
});
});
// Mutex is still sound:
// - No use-after-free
// - No data races
// - Lock still works correctly
{
let mut data = mutex.lock();
data.push(4);
// This is safe - the mutex works correctly
}
// What's NOT guaranteed:
// - Logical consistency of the data
// - Completion of multi-step updates
// - Invariant preservation
}parking_lot guarantees memory safety—the mutex mechanism works correctly—but not logical consistency.
use std::sync::Mutex as StdMutex;
use parking_lot::Mutex as PlMutex;
use parking_lot::const_mutex;
fn creation_comparison() {
// std::sync::Mutex::new
let std_mutex = StdMutex::new(vec
![1, 2, 3]);
// Can't be const in older Rust versions
// Requires std::sync::Mutex::new for dynamic values
// parking_lot::Mutex::new
let pl_mutex = PlMutex::new(vec
![1, 2, 3]);
// Also const-capable
// parking_lot provides const_mutex! macro for static initialization
static STATIC_MUTEX: parking_lot::Mutex<i32> = const_mutex!(42);
// Both support const in modern Rust
static STD_STATIC: StdMutex<i32> = StdMutex::new(42);
static PL_STATIC: PlMutex<i32> = PlMutex::new(42);
}Both support static initialization; parking_lot has historical const support advantages.
use std::sync::Mutex as StdMutex;
use parking_lot::Mutex as PlMutex;
use std::time::Instant;
fn benchmark() {
// std::sync::Mutex has poisoning overhead:
// 1. Tracks poisoned state (atomic flag)
// 2. Returns Result, requires checking
// 3. PoisonError construction on access
// parking_lot::Mutex is lighter:
// 1. No poison tracking
// 2. Returns guard directly
// 3. Fewer branches in lock/unlock path
let std_mutex = StdMutex::new(0u64);
let pl_mutex = PlMutex::new(0u64);
let iterations = 1_000_000;
let start = Instant::now();
for _ in 0..iterations {
let _guard = std_mutex.lock().unwrap();
}
let std_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _guard = pl_mutex.lock();
}
let pl_time = start.elapsed();
println!("std: {:?}", std_time);
println!("parking_lot: {:?}", pl_time);
// parking_lot typically shows slightly better performance
// Difference is more pronounced in contended scenarios
}Removing poisoning eliminates branches and atomic operations from the lock path.
use std::sync::Mutex;
use parking_lot::Mutex as PlMutex;
// std::sync::Mutex recovery
fn std_recovery(mutex: &Mutex<Vec<i32>>) {
let mut data = match mutex.lock() {
Ok(guard) => guard,
Err(poison) => {
// Explicit recovery decision
println!("Recovering from poisoned state");
let guard = poison.into_inner();
// Optionally: reset to known state
guard
}
};
data.push(1);
}
// parking_lot Mutex recovery
fn pl_recovery(mutex: &PlMutex<Vec<i32>>) {
// No special handling needed
// But same logical concerns apply
let mut data = mutex.lock();
// If needed, application-level recovery:
// Check invariants explicitly
if data.len() > 1000 {
data.clear(); // Application-defined recovery
}
data.push(1);
}
// Pattern: Application-level recovery
fn app_level_recovery(mutex: &Mutex<MyData>) {
let mut data = mutex.lock().unwrap_or_else(|e| {
let mut guard = e.into_inner();
guard.reset_to_safe_state();
guard
});
// Continue with known-good state
}
struct MyData {
value: i32,
}
impl MyData {
fn reset_to_safe_state(&mut self) {
self.value = 0;
}
}Both approaches can implement recovery; std makes it explicit, parking_lot leaves it to application logic.
use std::sync::Mutex;
use std::panic;
fn catch_unwind_example() {
let mutex = Mutex::new(vec
![1, 2, 3]);
// Catch panic to continue execution
let result = panic::catch_unwind(|| {
let mut data = mutex.lock().unwrap();
data.push(4);
panic!("Inside catch_unwind");
});
assert!(result.is_err());
// Mutex IS poisoned because panic was caught
// and program continues
let lock_result = mutex.lock();
assert!(lock_result.is_err());
// Recovery:
let data = lock_result.unwrap_err().into_inner();
println!("Data after recovery: {:?}", *data);
}
fn parking_lot_no_poison() {
use parking_lot::Mutex as PlMutex;
use std::panic;
let mutex = PlMutex::new(vec
![1, 2, 3]);
let result = panic::catch_unwind(|| {
let mut data = mutex.lock();
data.push(4);
panic!("Inside catch_unwind");
});
assert!(result.is_err());
// No poisoning, direct access
let data = mutex.lock();
println!("Data: {:?}", *data);
// Data: [1, 2, 3, 4]
}With catch_unwind, std::sync::Mutex poisoning matters for continued execution; parking_lot just continues.
// Use std::sync::Mutex when:
// 1. Data has critical invariants that must be preserved
// 2. You want explicit signaling of potential corruption
// 3. You're building libraries where users should decide recovery
// 4. Working with code that expects poisoning semantics
// Use parking_lot::Mutex when:
// 1. Simple data without complex invariants
// 2. Panics terminate the program anyway
// 3. You want cleaner API without unwrapping
// 4. Performance matters (micro-optimization)
// 5. You don't catch panics
// Example: Use std for banking
use std::sync::Mutex;
struct Account {
balance: i64,
}
// Invariant: balance >= 0 (if overdraft not allowed)
// Poisoning signals potential violation
// Example: Use parking_lot for metrics
use parking_lot::Mutex;
struct Metrics {
requests: u64,
errors: u64,
}
// No invariants, partial updates acceptable
// Poisoning would add noise without valueChoose based on whether the explicit poisoning signal provides value for your use case.
Poisoning model comparison:
| Aspect | std::sync::Mutex | parking_lot::Mutex |
|--------|-------------------|---------------------|
| Panic handling | Marks mutex as poisoned | Continues normally |
| Lock return type | LockResult<MutexGuard> | MutexGuard |
| Error handling | Required (unwrap, match) | Not needed |
| Memory safety | Guaranteed | Guaranteed |
| Logical consistency | Signaled via poison | Application's responsibility |
| Performance | Slight overhead | Minimal overhead |
| API complexity | Higher (Result types) | Lower (direct access) |
std::sync::Mutex philosophy:
// A panic while holding the lock might leave data inconsistent
// The mutex signals this via poisoning
// Programmers explicitly acknowledge and handle this
let data = mutex.lock().unwrap_or_else(|e| {
e.into_inner() // "I know data might be inconsistent"
});parking_lot::Mutex philosophy:
// The mutex itself remains valid after panic
// Logical consistency is the programmer's concern regardless
// Poisoning adds ceremony without solving the real problem
let data = mutex.lock(); // Trust the programmerKey insight: Mutex poisoning in std::sync::Mutex represents a philosophical stance that programs should explicitly acknowledge potential data inconsistency after a panic. parking_lot::Mutex takes the position that this acknowledgment is often ceremonial—most programs either don't catch panics (making poisoning irrelevant) or would recover in application-specific ways regardless of the poison flag. Both guarantee memory safety; the difference is whether the mutex signals logical inconsistency or leaves it to application code. For most applications where panics terminate the program, parking_lot's simpler API provides the same safety with less friction.