rpi_plugin_sdk/
handles.rs1#![forbid(unsafe_code)]
8
9use crate::StepHandle;
10use std::collections::HashMap;
11use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
12use std::sync::{Arc, Mutex, MutexGuard};
13
14static NEXT_HANDLE: AtomicUsize = AtomicUsize::new(1);
15
16pub struct ToolHandles<T> {
17 entries: Mutex<Option<HashMap<usize, Arc<ToolState<T>>>>>,
18}
19
20pub struct ToolState<T> {
21 state: Mutex<T>,
22 pub cancelled: Arc<AtomicBool>,
23}
24
25impl<T> ToolState<T> {
26 pub fn lock(&self) -> MutexGuard<'_, T> {
28 self.state.lock().unwrap_or_else(|p| p.into_inner())
29 }
30
31 pub fn cancel(&self) {
33 self.cancelled.store(true, Ordering::SeqCst);
34 }
35}
36
37impl<T> Default for ToolHandles<T> {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl<T> ToolHandles<T> {
44 pub const fn new() -> Self {
45 Self {
46 entries: Mutex::new(None),
47 }
48 }
49
50 pub fn insert(&self, state: T) -> StepHandle {
51 self.insert_with_cancel(state, Arc::new(AtomicBool::new(false)))
52 }
53
54 pub fn insert_with_cancel(&self, state: T, cancelled: Arc<AtomicBool>) -> StepHandle {
56 let token = NEXT_HANDLE
57 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |next| {
58 next.checked_add(1)
59 })
60 .expect("tool handle tokens exhausted");
61 let entry = Arc::new(ToolState {
62 state: Mutex::new(state),
63 cancelled,
64 });
65 self.entries
66 .lock()
67 .unwrap_or_else(|p| p.into_inner())
68 .get_or_insert_with(HashMap::new)
69 .insert(token, entry);
70 token as StepHandle
71 }
72
73 pub fn get(&self, handle: StepHandle) -> Option<Arc<ToolState<T>>> {
75 self.entries
76 .lock()
77 .unwrap_or_else(|p| p.into_inner())
78 .as_ref()?
79 .get(&(handle as usize))
80 .cloned()
81 }
82
83 pub fn cancel(&self, handle: StepHandle) {
84 if let Some(entry) = self.get(handle) {
85 entry.cancel();
86 }
87 }
88
89 pub fn remove(&self, handle: StepHandle) -> Option<Arc<ToolState<T>>> {
91 let entry = self
92 .entries
93 .lock()
94 .unwrap_or_else(|p| p.into_inner())
95 .as_mut()?
96 .remove(&(handle as usize));
97 if let Some(entry) = &entry {
98 entry.cancel();
99 }
100 entry
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn invalid_retired_and_foreign_handles_are_rejected() {
110 let handles = ToolHandles::new();
111 let other = ToolHandles::new();
112 let first = handles.insert(1);
113 let second = other.insert(2);
114 assert!(handles.get(std::ptr::null_mut()).is_none());
115 assert!(handles.get(usize::MAX as StepHandle).is_none());
116 assert!(handles.get(second).is_none());
117 assert!(handles.remove(first).is_some());
118 assert!(handles.remove(first).is_none());
119 assert!(handles.get(first).is_none());
120 assert_ne!(handles.insert(3), first);
121 }
122
123 #[test]
124 fn cancellation_does_not_wait_for_the_state_lock() {
125 let handles = ToolHandles::new();
126 let handle = handles.insert(0);
127 let entry = handles.get(handle).unwrap();
128 let _guard = entry.lock();
129 let token = handle as usize;
130 std::thread::scope(|scope| {
131 scope
132 .spawn(|| handles.cancel(token as StepHandle))
133 .join()
134 .unwrap();
135 });
136 assert!(entry.cancelled.load(Ordering::SeqCst));
137 }
138
139 #[test]
140 fn removal_keeps_in_flight_state_alive_and_drops_it_once() {
141 struct CountDrop(Arc<AtomicUsize>);
142 impl Drop for CountDrop {
143 fn drop(&mut self) {
144 self.0.fetch_add(1, Ordering::SeqCst);
145 }
146 }
147 let drops = Arc::new(AtomicUsize::new(0));
148 let handles = ToolHandles::new();
149 let handle = handles.insert(CountDrop(drops.clone()));
150 let entry = handles.get(handle).unwrap();
151 drop(handles.remove(handle));
152 assert!(entry.cancelled.load(Ordering::SeqCst));
153 assert_eq!(drops.load(Ordering::SeqCst), 0);
154 drop(entry);
155 assert_eq!(drops.load(Ordering::SeqCst), 1);
156 assert!(handles.remove(handle).is_none());
157 }
158
159 #[test]
160 fn concurrent_polls_serialize_state_changes() {
161 let handles = ToolHandles::new();
162 let handle = handles.insert(0);
163 let entry = handles.get(handle).unwrap();
164 std::thread::scope(|scope| {
165 for _ in 0..4 {
166 let entry = entry.clone();
167 scope.spawn(move || {
168 for _ in 0..100 {
169 *entry.lock() += 1;
170 }
171 });
172 }
173 });
174 assert_eq!(*entry.lock(), 400);
175 }
176
177 #[test]
178 fn removal_signals_the_workers_shared_cancel_flag() {
179 let handles = ToolHandles::new();
180 let cancelled = Arc::new(AtomicBool::new(false));
181 let handle = handles.insert_with_cancel((), cancelled.clone());
182 drop(handles.remove(handle));
183 assert!(cancelled.load(Ordering::SeqCst));
184 }
185}