ferroui_base/utilities/
disposable_lock.rs1use crate::reactive::IDisposable;
2use std::cell::Cell;
3use std::rc::Rc;
4use std::sync::{Arc, Condvar, Mutex};
5use std::thread::{self, ThreadId};
6
7#[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 pub fn try_lock(&self) -> Option<Rc<dyn IDisposable>> {
63 self.monitor.enter(false).then(|| self.unlock_disposable())
64 }
65
66 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
77struct 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 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}