Mutex Poisoning
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.
Your Task
Implement ResilientCounter, a counter that never panics on a poisoned lock.
pub struct ResilientCounter {
value: Mutex<i64>,
}Implement new
pub fn new(start: i64) -> SelfCreates a counter holding start.
Implement with
pub 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);Implement get
pub fn get(&self) -> i64Returns the current value, poisoned or not.
Implement is_poisoned
pub 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.
Notes
- The lock is only poisoned if the panic happens while the guard is alive. A thread that locks, releases, and then panics leaves the mutex healthy.
withmust not hold the lock after it returns. Locking insidewithand releasing at the end ofwithis what makes nesting easy to reason about.
Hints
Click here to reveal hints
unwrap_or_elseon aResultgives you the error value to work with:self.value.lock().unwrap_or_else(|e| e.into_inner()).into_innerconsumes thePoisonErrorand returns theMutexGuardthat was inside it.- A
MutexGuard<i64>derefs toi64, sof(&mut guard)coerces to the&mut i64thatfexpects. getdoes not need its own locking code. Write it asself.with(|value| *value).Mutexhas anis_poisonedmethod already, so the last one is a single line.
Run to execute your code and see test results here.