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.store(process.unwrap_or_default(), Ordering::SeqCst);
31    }
32
33    /// Observes which process is running the transfer, while one is.
34    #[must_use]
35    pub fn process(&self) -> Option<u32> {
36        let running = self.running.load(Ordering::SeqCst);
37        (running != 0).then_some(running)
38    }
39
40    /// Holds the running transfer still, and observes whether one was held.
41    pub fn hold(&self) -> bool {
42        self.signalled(HOLD_SIGNAL)
43    }
44
45    /// Lets a held transfer go again, and observes whether one was let go.
46    pub fn release(&self) -> bool {
47        self.signalled(RELEASE_SIGNAL)
48    }
49
50    /// Ends the running transfer, and observes whether one was ended.
51    ///
52    /// A held transfer is let go first: a process that is not running cannot notice that it was
53    /// asked to stop.
54    pub fn cancel(&self) -> bool {
55        let _released = self.signalled(RELEASE_SIGNAL);
56        self.signalled(END_SIGNAL)
57    }
58
59    /// Sends one signal to the whole of the running transfer.
60    ///
61    /// The transfer is a family: a sender, a receiver, and whatever carries between them. The
62    /// signal names the family rather than its head, so none of it is left running alone.
63    fn signalled(&self, signal: &str) -> bool {
64        let running = self.running.load(Ordering::SeqCst);
65        if running == 0 || !HOLDING_IS_POSSIBLE {
66            return false;
67        }
68        // The family is named by a negative number, which has to be stated as an argument rather
69        // than read as an option of its own.
70        Command::new(SIGNAL_PROGRAM)
71            .args(["-s", signal, "--", &format!("-{running}")])
72            .stdout(Stdio::null())
73            .stderr(Stdio::null())
74            .status()
75            .is_ok_and(|status| status.success())
76    }
77}
78
79/// States whether a transfer can be held still on this machine at all.
80pub const HOLDING_IS_POSSIBLE: bool = cfg!(unix);