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);