oxihuman_core/
async_signal.rs1use std::sync::{Arc, Condvar, Mutex};
11use std::time::Duration;
12
13#[derive(Debug)]
15struct SignalInner {
16 is_set: bool,
17 count: usize,
18}
19
20impl SignalInner {
21 fn new() -> Self {
22 Self {
23 is_set: false,
24 count: 0,
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
35pub struct AsyncSignal {
36 name: String,
37 inner: Arc<(Mutex<SignalInner>, Condvar)>,
38}
39
40#[inline]
46fn lock_inner(pair: &Arc<(Mutex<SignalInner>, Condvar)>) -> std::sync::MutexGuard<'_, SignalInner> {
47 pair.0
48 .lock()
49 .unwrap_or_else(|poisoned| poisoned.into_inner())
50}
51
52pub fn new_async_signal(name: &str) -> AsyncSignal {
58 AsyncSignal {
59 name: name.to_string(),
60 inner: Arc::new((Mutex::new(SignalInner::new()), Condvar::new())),
61 }
62}
63
64pub fn signal_set(sig: &AsyncSignal) {
67 let (mutex, condvar) = sig.inner.as_ref();
68 let mut guard = mutex
69 .lock()
70 .unwrap_or_else(|poisoned| poisoned.into_inner());
71 guard.is_set = true;
72 guard.count += 1;
73 drop(guard);
74 condvar.notify_all();
75}
76
77pub fn signal_reset(sig: &AsyncSignal) {
80 let mut guard = lock_inner(&sig.inner);
81 guard.is_set = false;
82}
83
84pub fn signal_wait(sig: &AsyncSignal) -> bool {
89 let (mutex, condvar) = sig.inner.as_ref();
90 let mut guard = mutex
91 .lock()
92 .unwrap_or_else(|poisoned| poisoned.into_inner());
93 while !guard.is_set {
94 guard = condvar
95 .wait(guard)
96 .unwrap_or_else(|poisoned| poisoned.into_inner());
97 }
98 true
99}
100
101pub fn signal_wait_timeout(sig: &AsyncSignal, timeout_ms: u64) -> bool {
107 let (mutex, condvar) = sig.inner.as_ref();
108 let timeout = Duration::from_millis(timeout_ms);
109 let mut guard = mutex
110 .lock()
111 .unwrap_or_else(|poisoned| poisoned.into_inner());
112 if guard.is_set {
114 return true;
115 }
116 let result = condvar
117 .wait_timeout_while(guard, timeout, |inner| !inner.is_set)
118 .unwrap_or_else(|poisoned| poisoned.into_inner());
119 guard = result.0;
121 guard.is_set
122}
123
124pub fn signal_is_set(sig: &AsyncSignal) -> bool {
126 lock_inner(&sig.inner).is_set
127}
128
129#[deprecated(
131 since = "0.1.3",
132 note = "use signal_is_set for non-blocking poll or signal_wait to block"
133)]
134pub fn signal_wait_stub(sig: &AsyncSignal) -> bool {
135 signal_is_set(sig)
136}
137
138pub fn signal_name_as(sig: &AsyncSignal) -> String {
140 sig.name.clone()
141}
142
143pub fn signal_count_as(sig: &AsyncSignal) -> usize {
145 lock_inner(&sig.inner).count
146}
147
148pub fn signal_to_json(sig: &AsyncSignal) -> String {
150 let guard = lock_inner(&sig.inner);
151 format!(
152 r#"{{"name":"{}","is_set":{},"count":{}}}"#,
153 sig.name, guard.is_set, guard.count
154 )
155}
156
157#[cfg(test)]
162mod tests {
163 use super::*;
164 use std::sync::atomic::{AtomicBool, Ordering};
165 use std::thread;
166 use std::time::Instant;
167
168 #[test]
171 fn test_new_async_signal() {
172 let s = new_async_signal("test");
173 assert_eq!(signal_name_as(&s), "test");
174 assert!(!signal_is_set(&s));
175 }
176
177 #[test]
178 fn test_signal_set_and_check() {
179 let s = new_async_signal("sig");
180 signal_set(&s);
181 assert!(signal_is_set(&s));
182 }
183
184 #[test]
185 fn test_signal_reset() {
186 let s = new_async_signal("sig");
187 signal_set(&s);
188 signal_reset(&s);
189 assert!(!signal_is_set(&s));
190 }
191
192 #[test]
193 #[allow(deprecated)]
194 fn test_signal_wait_stub_deprecated() {
195 let s = new_async_signal("sig");
196 assert!(!signal_wait_stub(&s));
197 signal_set(&s);
198 assert!(signal_wait_stub(&s));
199 }
200
201 #[test]
202 fn test_signal_count() {
203 let s = new_async_signal("sig");
204 signal_set(&s);
205 signal_set(&s);
206 assert_eq!(signal_count_as(&s), 2);
207 }
208
209 #[test]
210 fn test_signal_name() {
211 let s = new_async_signal("my_signal");
212 assert_eq!(signal_name_as(&s), "my_signal");
213 }
214
215 #[test]
216 fn test_signal_to_json() {
217 let s = new_async_signal("j");
218 let json = signal_to_json(&s);
219 assert!(json.contains("\"name\":\"j\""));
220 assert!(json.contains("\"is_set\":false"));
221 }
222
223 #[test]
224 fn test_signal_to_json_after_set() {
225 let s = new_async_signal("j");
226 signal_set(&s);
227 let json = signal_to_json(&s);
228 assert!(json.contains("\"is_set\":true"));
229 assert!(json.contains("\"count\":1"));
230 }
231
232 #[test]
233 fn test_multiple_reset_cycles() {
234 let s = new_async_signal("cycle");
235 signal_set(&s);
236 signal_reset(&s);
237 signal_set(&s);
238 assert!(signal_is_set(&s));
239 assert_eq!(signal_count_as(&s), 2);
240 }
241
242 #[test]
243 fn test_initial_count_zero() {
244 let s = new_async_signal("z");
245 assert_eq!(signal_count_as(&s), 0);
246 }
247
248 #[test]
253 fn test_signal_wait_blocks_until_set() {
254 let sig = new_async_signal("blocking");
255 let sig_thread = sig.clone();
256
257 let setter_fired = Arc::new(AtomicBool::new(false));
266 let setter_fired_thread = Arc::clone(&setter_fired);
267
268 let handle = thread::spawn(move || {
269 thread::sleep(Duration::from_millis(10));
273 setter_fired_thread.store(true, Ordering::Release);
274 signal_set(&sig_thread);
275 });
276
277 let t0 = Instant::now();
278 let result = signal_wait(&sig);
279 let elapsed = t0.elapsed();
280
281 handle.join().expect("thread panicked");
282
283 assert!(result, "signal_wait must return true");
284 assert!(
287 setter_fired.load(Ordering::Acquire),
288 "signal_wait returned before the setter called signal_set"
289 );
290 assert!(
293 elapsed < Duration::from_secs(5),
294 "signal_wait took too long: {elapsed:?}"
295 );
296 }
297
298 #[test]
300 fn test_signal_wait_timeout_fires() {
301 let sig = new_async_signal("timeout_miss");
302 let t0 = Instant::now();
303 let result = signal_wait_timeout(&sig, 50);
304 let elapsed = t0.elapsed();
305
306 assert!(!result, "must return false on timeout");
307 assert!(
309 elapsed >= Duration::from_millis(30),
310 "returned too early: {elapsed:?}"
311 );
312 assert!(
314 elapsed < Duration::from_millis(2_000),
315 "took too long: {elapsed:?}"
316 );
317 }
318
319 #[test]
322 fn test_signal_wait_timeout_succeeds() {
323 let sig = new_async_signal("timeout_hit");
324 let sig_thread = sig.clone();
325
326 let handle = thread::spawn(move || {
327 thread::sleep(Duration::from_millis(20));
328 signal_set(&sig_thread);
329 });
330
331 let result = signal_wait_timeout(&sig, 5_000);
333 handle.join().expect("thread panicked");
334
335 assert!(result, "must return true when signaled before timeout");
336 }
337
338 #[test]
340 fn test_signal_clone_shared() {
341 let original = new_async_signal("shared");
342 let clone = original.clone();
343
344 signal_set(&clone);
346 assert!(signal_is_set(&original), "original must see set via clone");
347 assert_eq!(signal_count_as(&original), 1);
348
349 signal_reset(&original);
351 assert!(!signal_is_set(&clone), "clone must see reset via original");
352
353 signal_set(&original);
355 assert!(signal_is_set(&clone), "clone must see set via original");
356 assert_eq!(signal_count_as(&clone), 2);
357 }
358
359 #[test]
361 fn test_signal_wait_multiple_waiters() {
362 let sig = new_async_signal("multi");
363 let n_threads = 4_usize;
364
365 let handles: Vec<_> = (0..n_threads)
366 .map(|_| {
367 let s = sig.clone();
368 thread::spawn(move || signal_wait(&s))
369 })
370 .collect();
371
372 thread::sleep(Duration::from_millis(20));
374 signal_set(&sig);
375
376 let results: Vec<bool> = handles
377 .into_iter()
378 .map(|h| h.join().expect("thread panicked"))
379 .collect();
380
381 assert!(
382 results.iter().all(|&r| r),
383 "all waiters must return true: {results:?}"
384 );
385 }
386}