Skip to main content

rsynko_process/
hold.rs

1use std::process::{Command, Stdio};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU32, Ordering};
4
5/// Holds one running transfer still, and lets it go again.
6///
7/// A transfer performed by another program cannot be asked to wait: it is a process, and the way
8/// a process is held still is a signal. This carries the identity of the running one, so whoever
9/// is watching can hold it without owning it.
10#[derive(Clone, Debug, Default)]
11pub struct ProcessHold {
12    running: Arc<AtomicU32>,
13}
14
15/// Names the signal holding a running transfer still.
16const HOLD_SIGNAL: &str = "STOP";
17
18/// Names the signal letting a held transfer go again.
19const RELEASE_SIGNAL: &str = "CONT";
20
21/// Names the signal ending a transfer that is no longer wanted.
22const END_SIGNAL: &str = "TERM";
23
24/// Names the program that sends a signal to a process.
25const SIGNAL_PROGRAM: &str = "kill";
26
27impl ProcessHold {
28    /// States which process is running, or that none is.
29    pub(crate) fn running(&self, process: Option<u32>) {
30        self.running
31            .store(process.unwrap_or_default(), Ordering::SeqCst);
32    }
33
34    /// Observes which process is running the transfer, while one is.
35    #[must_use]
36    pub fn process(&self) -> Option<u32> {
37        let running = self.running.load(Ordering::SeqCst);
38        (running != 0).then_some(running)
39    }
40
41    /// Holds the running transfer still, and observes whether one was held.
42    pub fn hold(&self) -> bool {
43        self.signalled(HOLD_SIGNAL)
44    }
45
46    /// Lets a held transfer go again, and observes whether one was let go.
47    pub fn release(&self) -> bool {
48        self.signalled(RELEASE_SIGNAL)
49    }
50
51    /// Ends the running transfer, and observes whether one was ended.
52    ///
53    /// A held transfer is let go first: a process that is not running cannot notice that it was
54    /// asked to stop.
55    pub fn cancel(&self) -> bool {
56        let _released = self.signalled(RELEASE_SIGNAL);
57        self.signalled(END_SIGNAL)
58    }
59
60    /// Sends one signal to the whole of the running transfer.
61    ///
62    /// The transfer is a family: a sender, a receiver, and whatever carries between them. The
63    /// signal names the family rather than its head, so none of it is left running alone.
64    fn signalled(&self, signal: &str) -> bool {
65        let running = self.running.load(Ordering::SeqCst);
66        if running == 0 || !HOLDING_IS_POSSIBLE {
67            return false;
68        }
69        // The family is named by a negative number, which has to be stated as an argument rather
70        // than read as an option of its own.
71        Command::new(SIGNAL_PROGRAM)
72            .args(["-s", signal, "--", &format!("-{running}")])
73            .stdout(Stdio::null())
74            .stderr(Stdio::null())
75            .status()
76            .is_ok_and(|status| status.success())
77    }
78}
79
80/// States whether a transfer can be held still on this machine at all.
81pub const HOLDING_IS_POSSIBLE: bool = cfg!(unix);