harmos_packaging/
process.rs1use std::io::Read;
20use std::process::{Command, ExitStatus, Output};
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex};
23use std::thread::JoinHandle;
24use std::time::Duration;
25
26use process_wrap::std::{ChildWrapper, CommandWrap};
28
29use super::Error;
31
32#[derive(Clone, Default)]
41pub struct BuildCancellation {
42 stopped: Arc<AtomicBool>,
43}
44
45pub(super) struct Process {
46 child: Arc<Mutex<Box<dyn ChildWrapper>>>,
47 finished: Arc<AtomicBool>,
48 monitor: Option<JoinHandle<std::io::Result<()>>>,
49}
50
51impl BuildCancellation {
58 pub fn cancel(&self) {
59 self.stopped.store(true, Ordering::Release);
60 }
61 pub fn is_cancelled(&self) -> bool {
62 self.stopped.load(Ordering::Acquire)
63 }
64 pub(super) fn check(&self) -> Result<(), Error> {
65 if self.is_cancelled() { Err(Error::Cancelled) } else { Ok(()) }
66 }
67}
68
69impl Process {
70 pub(super) fn spawn(command: Command, cancellation: &BuildCancellation) -> Result<Self, Error> {
71 cancellation.check()?;
72 let mut command = CommandWrap::from(command);
73 #[cfg(unix)]
74 command.wrap(process_wrap::std::ProcessGroup::leader());
75 #[cfg(windows)]
76 {
77 let mut flags = process_wrap::std::CreationFlags(Default::default());
79 flags.0.0 = 0x08000000;
80 command.wrap(flags).wrap(process_wrap::std::JobObject);
81 }
82 let child = Arc::new(Mutex::new(command.spawn()?));
83 let retained = child.clone();
84 let finished = Arc::new(AtomicBool::new(false));
85 let completed = finished.clone();
86 let cancellation = cancellation.clone();
87 let monitor = std::thread::spawn(move || {
88 while !completed.load(Ordering::Acquire) {
89 if cancellation.is_cancelled() {
90 return retained.lock().expect("process owner is healthy").start_kill();
91 }
92 std::thread::park_timeout(Duration::from_millis(20));
93 }
94 Ok(())
95 });
96 Ok(Self { child, finished, monitor: Some(monitor) })
97 }
98
99 pub(super) fn stdout(&self) -> std::process::ChildStdout {
100 self.child.lock().expect("process owner is healthy").stdout().take().expect("Cargo stdout was piped")
101 }
102
103 pub(super) fn kill(&self) -> Result<(), Error> {
104 let mut child = self.child.lock().expect("process owner is healthy");
105 if child.try_wait()?.is_none() {
106 child.start_kill()?;
107 }
108 Ok(())
109 }
110
111 pub(super) fn wait(&mut self) -> Result<ExitStatus, Error> {
112 loop {
113 let status = self.child.lock().expect("process owner is healthy").try_wait()?;
114 if let Some(status) = status {
115 self.finished.store(true, Ordering::Release);
116 if let Some(monitor) = self.monitor.take() {
117 monitor.thread().unpark();
118 monitor.join().map_err(|_| std::io::Error::other("process cancellation monitor panicked"))??;
119 }
120 return Ok(status);
121 }
122 std::thread::sleep(Duration::from_millis(20));
123 }
124 }
125
126 pub(super) fn output(mut self) -> Result<Output, Error> {
127 let mut stdout = self.stdout();
128 let mut stderr = self.child.lock().expect("process owner is healthy").stderr().take().expect("Cargo stderr was piped");
129 let stdout = std::thread::spawn(move || {
130 let mut bytes = Vec::new();
131 stdout.read_to_end(&mut bytes).map(|_| bytes)
132 });
133 let stderr = std::thread::spawn(move || {
134 let mut bytes = Vec::new();
135 stderr.read_to_end(&mut bytes).map(|_| bytes)
136 });
137 let status = self.wait();
138 let cleanup = if status.is_err() { self.kill().and_then(|_| self.wait().map(|_| ())) } else { Ok(()) };
139 let stdout = stdout.join().map_err(|_| std::io::Error::other("Cargo stdout reader panicked"));
140 let stderr = stderr.join().map_err(|_| std::io::Error::other("Cargo stderr reader panicked"));
141 cleanup?;
142 Ok(Output { status: status?, stdout: stdout??, stderr: stderr?? })
143 }
144}
145
146impl Drop for Process {
147 fn drop(&mut self) {
148 if let Err(error) = self.kill().and_then(|_| self.wait().map(|_| ())) {
149 tracing::error!(%error, "retained Cargo process cleanup failed");
150 }
151 }
152}
153
154