Skip to main content

rsynko_reqwest/
pause.rs

1use std::sync::{Arc, Condvar, Mutex};
2
3/// Controls cooperative suspension of one streaming download.
4#[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    /// Constructs a running transfer control.
17    #[must_use]
18    pub fn running() -> Self {
19        Self { state: (Mutex::new(RuntimeControlState { paused: false, cancelled: false }), Condvar::new()).into() }
20    }
21
22    /// Sets whether retrieval waits before reading its next chunk.
23    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    /// Cancels retrieval and wakes a suspended reader.
32    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}