hojicha_runtime/
safe_mutex.rs

1//! Safe mutex wrapper that handles poison errors gracefully
2//!
3//! This module provides a mutex wrapper that recovers from poison errors
4//! instead of panicking, allowing the TUI to continue running even if
5//! a thread panics while holding a lock.
6
7use log::warn;
8use std::ops::{Deref, DerefMut};
9use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
10
11/// A mutex wrapper that automatically recovers from poison errors
12pub struct SafeMutex<T> {
13    inner: Mutex<T>,
14}
15
16impl<T> SafeMutex<T> {
17    /// Create a new SafeMutex
18    pub fn new(value: T) -> Self {
19        Self {
20            inner: Mutex::new(value),
21        }
22    }
23
24    /// Lock the mutex, recovering from poison if necessary
25    pub fn lock(&self) -> SafeGuard<'_, T> {
26        match self.inner.lock() {
27            Ok(guard) => SafeGuard {
28                guard: Ok(guard),
29                recovered: false,
30            },
31            Err(poisoned) => {
32                warn!("Mutex was poisoned, recovering...");
33                let guard = poisoned.into_inner();
34                SafeGuard {
35                    guard: Ok(guard),
36                    recovered: true,
37                }
38            }
39        }
40    }
41
42    /// Try to lock the mutex without blocking
43    pub fn try_lock(&self) -> Option<SafeGuard<'_, T>> {
44        match self.inner.try_lock() {
45            Ok(guard) => Some(SafeGuard {
46                guard: Ok(guard),
47                recovered: false,
48            }),
49            Err(std::sync::TryLockError::Poisoned(poisoned)) => {
50                warn!("Mutex was poisoned during try_lock, recovering...");
51                let guard = poisoned.into_inner();
52                Some(SafeGuard {
53                    guard: Ok(guard),
54                    recovered: true,
55                })
56            }
57            Err(std::sync::TryLockError::WouldBlock) => None,
58        }
59    }
60}
61
62/// Guard for SafeMutex that tracks if recovery occurred
63pub struct SafeGuard<'a, T> {
64    guard: Result<MutexGuard<'a, T>, PoisonError<MutexGuard<'a, T>>>,
65    recovered: bool,
66}
67
68impl<'a, T> SafeGuard<'a, T> {
69    /// Check if this guard recovered from a poison error
70    pub fn was_recovered(&self) -> bool {
71        self.recovered
72    }
73}
74
75impl<'a, T> Deref for SafeGuard<'a, T> {
76    type Target = T;
77
78    fn deref(&self) -> &Self::Target {
79        match &self.guard {
80            Ok(guard) => guard.deref(),
81            Err(poisoned) => poisoned.get_ref(),
82        }
83    }
84}
85
86impl<'a, T> DerefMut for SafeGuard<'a, T> {
87    fn deref_mut(&mut self) -> &mut Self::Target {
88        match &mut self.guard {
89            Ok(guard) => guard.deref_mut(),
90            Err(poisoned) => poisoned.get_mut(),
91        }
92    }
93}
94
95/// Arc wrapper around SafeMutex for convenience
96pub type SafeArcMutex<T> = Arc<SafeMutex<T>>;
97
98/// Create a new Arc<SafeMutex<T>>
99pub fn safe_arc_mutex<T>(value: T) -> SafeArcMutex<T> {
100    Arc::new(SafeMutex::new(value))
101}