Mutex::lock does not return a guard. It returns a Result<MutexGuard<T>, PoisonError<...>>, and most code papers over that with .unwrap(). That unwrap is not there for the usual "this can't fail" reason: it is there because a mutex can be poisoned.
A mutex becomes poisoned when a thread panics while holding its guard. Rust assumes the panicking thread was halfway through a change and left the data in a state the rest of the program was never meant to see, so every later lock() call returns Err as a warning.
The data itself is untouched and still reachable. PoisonError owns the guard, and into_inner gives it to you:
let guard = mutex
.lock()
.unwrap_or_else(|e| e.into_inner());Whether that is the right call depends on the invariant. For a plain counter, a half-finished increment is not dangerous, so recovering is reasonable. For a data structure with an invariant spanning several fields, it may not be.
Implement ResilientCounter, a counter that never panics on a poisoned lock.
pub struct ResilientCounter {
value: Mutex<i64>,
}newpub fn new(start: i64) -> SelfCreates a counter holding start.
withpub fn with<R>(
&self,
f: impl FnOnce(&mut i64) -> R,
) -> RTakes the lock, calls f with a mutable reference to the value, and returns whatever f returned. If the mutex is poisoned, recover the guard and run f anyway.
let counter = ResilientCounter::new(0);
counter.with(|value| *value += 21);
assert_eq!(counter.get(), 21);getpub fn get(&self) -> i64Returns the current value, poisoned or not.
is_poisonedpub fn is_poisoned(&self) -> boolReports whether a thread has ever panicked while holding the lock. Recovering the guard does not clear the flag, so this stays true afterwards.
with must not hold the lock after it returns. Locking inside with and releasing at the end of with is what makes nesting easy to reason about.unwrap_or_else on a Result gives you the error value to work with: self.value.lock().unwrap_or_else(|e| e.into_inner()).into_inner consumes the PoisonError and returns the MutexGuard that was inside it.MutexGuard<i64> derefs to i64, so f(&mut guard) coerces to the &mut i64 that f expects.get does not need its own locking code. Write it as self.with(|value| *value).Mutex has an is_poisoned method already, so the last one is a single line.Mutex::lock does not return a guard. It returns a Result<MutexGuard<T>, PoisonError<...>>, and most code papers over that with .unwrap(). That unwrap is not there for the usual "this can't fail" reason: it is there because a mutex can be poisoned.
A mutex becomes poisoned when a thread panics while holding its guard. Rust assumes the panicking thread was halfway through a change and left the data in a state the rest of the program was never meant to see, so every later lock() call returns Err as a warning.
The data itself is untouched and still reachable. PoisonError owns the guard, and into_inner gives it to you:
let guard = mutex
.lock()
.unwrap_or_else(|e| e.into_inner());Whether that is the right call depends on the invariant. For a plain counter, a half-finished increment is not dangerous, so recovering is reasonable. For a data structure with an invariant spanning several fields, it may not be.
Implement ResilientCounter, a counter that never panics on a poisoned lock.
pub struct ResilientCounter {
value: Mutex<i64>,
}newpub fn new(start: i64) -> SelfCreates a counter holding start.
withpub fn with<R>(
&self,
f: impl FnOnce(&mut i64) -> R,
) -> RTakes the lock, calls f with a mutable reference to the value, and returns whatever f returned. If the mutex is poisoned, recover the guard and run f anyway.
let counter = ResilientCounter::new(0);
counter.with(|value| *value += 21);
assert_eq!(counter.get(), 21);getpub fn get(&self) -> i64Returns the current value, poisoned or not.
is_poisonedpub fn is_poisoned(&self) -> boolReports whether a thread has ever panicked while holding the lock. Recovering the guard does not clear the flag, so this stays true afterwards.
with must not hold the lock after it returns. Locking inside with and releasing at the end of with is what makes nesting easy to reason about.unwrap_or_else on a Result gives you the error value to work with: self.value.lock().unwrap_or_else(|e| e.into_inner()).into_inner consumes the PoisonError and returns the MutexGuard that was inside it.MutexGuard<i64> derefs to i64, so f(&mut guard) coerces to the &mut i64 that f expects.get does not need its own locking code. Write it as self.with(|value| *value).Mutex has an is_poisoned method already, so the last one is a single line.