Skip to main content

ferroui_base/utilities/
disposable_lock.rs

1use crate::reactive::IDisposable;
2use std::cell::Cell;
3use std::rc::Rc;
4use std::sync::{Arc, Condvar, Mutex};
5use std::thread::{self, ThreadId};
6
7/// A lock whose holder gets a disposable that releases it.
8///
9/// The thread that holds the lock may take it again (the lock of the
10/// reference is a monitor); each disposable releases one entry, on the
11/// thread that took it.
12#[derive(Clone, Default)]
13pub struct DisposableLock {
14    monitor: Arc<Monitor>,
15}
16
17#[derive(Default)]
18struct Monitor {
19    state: Mutex<State>,
20    exited: Condvar,
21}
22
23#[derive(Default)]
24struct State {
25    owner: Option<ThreadId>,
26    entries: usize,
27}
28
29impl Monitor {
30    fn enter(&self, wait: bool) -> bool {
31        let current = thread::current().id();
32        let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
33        while state.owner.is_some_and(|owner| owner != current) {
34            if !wait {
35                return false;
36            }
37            state = self.exited.wait(state).unwrap_or_else(|e| e.into_inner());
38        }
39        state.owner = Some(current);
40        state.entries += 1;
41        true
42    }
43
44    fn exit(&self) {
45        let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
46        state.entries -= 1;
47        if state.entries == 0 {
48            state.owner = None;
49            drop(state);
50            self.exited.notify_one();
51        }
52    }
53}
54
55impl DisposableLock {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Tries to take the lock. Returns the disposable that releases it if
61    /// the lock was obtained.
62    pub fn try_lock(&self) -> Option<Rc<dyn IDisposable>> {
63        self.monitor.enter(false).then(|| self.unlock_disposable())
64    }
65
66    /// Enters a waiting lock.
67    pub fn lock(&self) -> Rc<dyn IDisposable> {
68        self.monitor.enter(true);
69        self.unlock_disposable()
70    }
71
72    fn unlock_disposable(&self) -> Rc<dyn IDisposable> {
73        Rc::new(UnlockDisposable { monitor: Cell::new(Some(self.monitor.clone())) })
74    }
75}
76
77/// Releases the entry once, however often it is disposed, and when it is
78/// dropped without having been disposed.
79struct UnlockDisposable {
80    monitor: Cell<Option<Arc<Monitor>>>,
81}
82
83impl IDisposable for UnlockDisposable {
84    fn dispose(&self) {
85        if let Some(monitor) = self.monitor.take() {
86            monitor.exit();
87        }
88    }
89}
90
91impl Drop for UnlockDisposable {
92    fn drop(&mut self) {
93        self.dispose();
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    // Not from upstream, which has no tests of the lock.
100    use super::*;
101
102    #[test]
103    fn the_holder_locks_again_and_another_thread_does_not() {
104        let lock = DisposableLock::new();
105        let first = lock.lock();
106        let second = lock.try_lock().expect("the holder may enter again");
107
108        let other = lock.clone();
109        assert!(thread::spawn(move || other.try_lock().is_none()).join().unwrap());
110
111        second.dispose();
112        second.dispose();
113        let other = lock.clone();
114        assert!(thread::spawn(move || other.try_lock().is_none()).join().unwrap());
115
116        first.dispose();
117        let other = lock.clone();
118        assert!(thread::spawn(move || other.try_lock().is_some()).join().unwrap());
119    }
120
121    #[test]
122    fn a_waiting_lock_is_entered_when_the_holder_releases() {
123        let lock = DisposableLock::new();
124        let held = lock.lock();
125        let other = lock.clone();
126        let waiter = thread::spawn(move || {
127            let entered = other.lock();
128            entered.dispose();
129        });
130        thread::sleep(std::time::Duration::from_millis(20));
131        assert!(!waiter.is_finished());
132        drop(held);
133        waiter.join().unwrap();
134    }
135}