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