1use std::sync::{Arc, Condvar, Mutex};
2
3#[derive(Clone, Debug)]
5pub struct RuntimePause {
6 state: Arc<(Mutex<RuntimeControlState>, Condvar)>,
7}
8
9#[derive(Debug)]
10struct RuntimeControlState {
11 paused: bool,
12 cancelled: bool,
13}
14
15impl RuntimePause {
16 #[must_use]
18 pub fn running() -> Self {
19 Self { state: (Mutex::new(RuntimeControlState { paused: false, cancelled: false }), Condvar::new()).into() }
20 }
21
22 pub fn set_paused(&self, paused: bool) {
24 let (state, changed) = &*self.state;
25 state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).paused = paused;
26 if !paused {
27 changed.notify_all();
28 }
29 }
30
31 pub fn cancel(&self) {
33 let (state, changed) = &*self.state;
34 state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).cancelled = true;
35 changed.notify_all();
36 }
37
38 pub(crate) fn wait_until_running(&self) -> bool {
39 let (state, changed) = &*self.state;
40 let mut paused = state.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
41 while paused.paused && !paused.cancelled {
42 paused = changed.wait(paused).unwrap_or_else(std::sync::PoisonError::into_inner);
43 }
44 !paused.cancelled
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::RuntimePause;
51 use std::sync::mpsc;
52 use std::thread;
53 use std::time::Duration;
54
55 #[test]
56 fn paused_reader_waits_until_resume() {
57 let pause = RuntimePause::running();
58 pause.set_paused(true);
59 let waiting = pause.clone();
60 let (sender, receiver) = mpsc::channel();
61 let worker = thread::spawn(move || {
62 waiting.wait_until_running();
63 sender.send(()).expect("observer remains connected");
64 });
65
66 assert!(receiver.recv_timeout(Duration::from_millis(20)).is_err());
67 pause.set_paused(false);
68 receiver.recv_timeout(Duration::from_secs(1)).expect("reader resumed");
69 worker.join().expect("pause worker");
70 }
71
72 #[test]
73 fn cancellation_wakes_a_paused_reader() {
74 let pause = RuntimePause::running();
75 pause.set_paused(true);
76 let waiting = pause.clone();
77 let (sender, receiver) = mpsc::channel();
78 let worker = thread::spawn(move || {
79 sender.send(waiting.wait_until_running()).expect("observer remains connected");
80 });
81
82 pause.cancel();
83 assert!(!receiver.recv_timeout(Duration::from_secs(1)).expect("cancelled"));
84 worker.join().expect("pause worker");
85 }
86}