chj_rustbin/
checked_mutex.rs

1//! Check whether the thread itself already locks the mutex; only a
2//! *hack* currently, won't be generally reliable, right? Should
3//! probably use cooptex or tracing-mutex instead.
4
5//! To use conditionally in debug mode, import as:
6//!
7//!     #[cfg(debug_assertions)]
8//!     use chj_rustbin::checked_mutex::{CheckedMutex as Mutex, CheckedMutexGuard as MutexGuard};
9//!     #[cfg(not(debug_assertions))]
10//!     use std::sync::{Mutex, MutexGuard};
11//!
12
13use std::{
14    ops::{Deref, DerefMut},
15    sync::{Mutex, MutexGuard},
16    thread::ThreadId,
17};
18
19#[derive(Debug, thiserror::Error)]
20pub enum CheckedMutexError {
21    // #[error("mutex is poisoned: {0}")]
22    // PoisonError(PoisonError<MutexGuard<'m, T>>),
23    #[error("mutex is poisoned")]
24    PoisonError,
25    #[error("mutex is locked by the same thread already")]
26    LockedByOurselves,
27}
28
29#[derive(Debug)]
30pub struct CheckedMutex<T> {
31    locked_by: Mutex<Option<ThreadId>>,
32    mutex: Mutex<T>,
33}
34
35pub struct CheckedMutexGuard<'m, T> {
36    checked_mutex: &'m CheckedMutex<T>,
37    guard: MutexGuard<'m, T>,
38}
39
40impl<'m, T> Deref for CheckedMutexGuard<'m, T> {
41    type Target = T;
42
43    fn deref(&self) -> &Self::Target {
44        self.guard.deref()
45    }
46}
47
48impl<'m, T> DerefMut for CheckedMutexGuard<'m, T> {
49    fn deref_mut(&mut self) -> &mut Self::Target {
50        self.guard.deref_mut()
51    }
52}
53
54impl<'m, T> Drop for CheckedMutexGuard<'m, T> {
55    fn drop(&mut self) {
56        let mut locked_by = self.checked_mutex.locked_by.lock().unwrap();
57        // XX should we check if it's still us in there? Or is it
58        // guaranteed? Does Drop only run for self.guard *after* this
59        // code? If so it should be guaranteed, right?
60        *locked_by = None;
61    }
62}
63
64impl<T> CheckedMutex<T> {
65    pub fn new(value: T) -> Self {
66        Self {
67            locked_by: Mutex::new(None),
68            mutex: Mutex::new(value),
69        }
70    }
71
72    pub fn lock(&self) -> Result<CheckedMutexGuard<'_, T>, CheckedMutexError> {
73        // The idea is to lock `locked_by`, if us then give error, if
74        // not, get lock on `mutex`, when gotten, enter us into
75        // `locked_by` and unlock that field. On dropping
76        // CheckedMutexGuard, delete us if it's still us (can it be
77        // another thread? Actually can't right?)
78        let mut locked_by = self.locked_by.lock().unwrap();
79        let id = std::thread::current().id();
80        if let Some(locking_id) = *locked_by {
81            if locking_id == id {
82                return Err(CheckedMutexError::LockedByOurselves);
83            }
84        }
85        match self.mutex.lock() {
86            Ok(guard) => {
87                // Got the lock, enter ourselves into locked_by
88                *locked_by = Some(id);
89                Ok(CheckedMutexGuard {
90                    checked_mutex: self,
91                    guard,
92                })
93            }
94            Err(_e) => Err(CheckedMutexError::PoisonError),
95        }
96    }
97}