running_process_platform_internal/
std_child.rs1use std::io::{self, Read};
17use std::time::Duration;
18
19use crate::platform::process::{CaptureCancellation, CaptureStream, UnixSignalKind};
20use crate::{
21 capture_reader_done, exit_code, prepare_capture_reader, unix_signal_process_group, TracedChild,
22 WindowsJobHandle,
23};
24
25const TRACED_WAIT_POLL: Duration = Duration::from_millis(10);
27
28enum StdChildKind {
29 Standard(std::process::Child),
30 ExactTrace(TracedChild),
31}
32
33pub struct PlatformStdChild {
35 kind: StdChildKind,
38 job: Option<WindowsJobHandle>,
39 own_process_group: bool,
40}
41
42pub struct PlatformCaptureReaders {
44 pub stdout: Box<dyn Read + Send>,
45 pub stderr: Box<dyn Read + Send>,
46}
47
48impl PlatformStdChild {
49 pub fn from_std(child: std::process::Child, own_process_group: bool) -> Self {
52 Self::new(StdChildKind::Standard(child), own_process_group)
53 }
54
55 pub fn from_exact_trace(child: TracedChild, own_process_group: bool) -> Self {
57 Self::new(StdChildKind::ExactTrace(child), own_process_group)
58 }
59
60 fn new(kind: StdChildKind, own_process_group: bool) -> Self {
61 Self {
62 kind,
63 job: None,
64 own_process_group,
65 }
66 }
67
68 pub fn id(&self) -> u32 {
70 match &self.kind {
71 StdChildKind::Standard(child) => child.id(),
72 StdChildKind::ExactTrace(child) => child.id(),
73 }
74 }
75
76 pub fn is_exact_trace(&self) -> bool {
78 matches!(self.kind, StdChildKind::ExactTrace(_))
79 }
80
81 pub fn std_child(&self) -> Option<&std::process::Child> {
84 match &self.kind {
85 StdChildKind::Standard(child) => Some(child),
86 StdChildKind::ExactTrace(_) => None,
87 }
88 }
89
90 pub fn attach_job(&mut self, job: WindowsJobHandle) {
92 self.job = Some(job);
93 }
94
95 pub fn try_wait_code(&mut self) -> io::Result<Option<i32>> {
97 match &mut self.kind {
98 StdChildKind::Standard(child) => child.try_wait().map(|status| status.map(exit_code)),
99 StdChildKind::ExactTrace(child) => child.try_wait_code(),
100 }
101 }
102
103 pub fn wait_code(&mut self) -> io::Result<i32> {
110 match &mut self.kind {
111 StdChildKind::Standard(child) => child.wait().map(exit_code),
112 StdChildKind::ExactTrace(child) => loop {
113 if let Some(code) = child.try_wait_code()? {
114 return Ok(code);
115 }
116 std::thread::sleep(TRACED_WAIT_POLL);
117 },
118 }
119 }
120
121 pub fn kill(&mut self) -> io::Result<()> {
123 match &mut self.kind {
124 StdChildKind::Standard(child) => child.kill(),
125 StdChildKind::ExactTrace(child) => child.kill(),
126 }
127 }
128
129 pub fn kill_group_or_child(&mut self) -> io::Result<()> {
132 if self.own_process_group
133 && unix_signal_process_group(self.id() as i32, UnixSignalKind::Kill).is_ok()
134 {
135 return Ok(());
136 }
137 self.kill()
138 }
139
140 pub fn take_stdin(&mut self) -> Option<std::process::ChildStdin> {
141 match &mut self.kind {
142 StdChildKind::Standard(child) => child.stdin.take(),
143 StdChildKind::ExactTrace(child) => child.take_stdin(),
144 }
145 }
146
147 pub fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
148 match &mut self.kind {
149 StdChildKind::Standard(child) => child.stdout.take(),
150 StdChildKind::ExactTrace(child) => child.take_stdout(),
151 }
152 }
153
154 pub fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
155 match &mut self.kind {
156 StdChildKind::Standard(child) => child.stderr.take(),
157 StdChildKind::ExactTrace(child) => child.take_stderr(),
158 }
159 }
160
161 pub fn prepare_capture(
167 &mut self,
168 cancellation: &CaptureCancellation,
169 ) -> io::Result<PlatformCaptureReaders> {
170 let (Some(stdout), Some(stderr)) = (self.take_stdout(), self.take_stderr()) else {
171 return Err(io::Error::new(
172 io::ErrorKind::BrokenPipe,
173 "child stdout and stderr must both be piped for capture",
174 ));
175 };
176 let stdout = prepare_capture_reader(stdout, cancellation, CaptureStream::Stdout)?;
177 let stderr = match prepare_capture_reader(stderr, cancellation, CaptureStream::Stderr) {
178 Ok(stderr) => stderr,
179 Err(error) => {
180 capture_reader_done(cancellation, CaptureStream::Stdout);
181 return Err(error);
182 }
183 };
184 Ok(PlatformCaptureReaders { stdout, stderr })
185 }
186
187 pub fn discard_after_start_error(self) {
190 let Self { kind, job, .. } = self;
191 match kind {
192 StdChildKind::Standard(mut child) => {
193 let _ = child.kill();
194 std::thread::spawn(move || {
197 let _job = job;
198 let _ = child.wait();
199 });
200 }
201 StdChildKind::ExactTrace(mut child) => {
202 let _ = child.kill();
204 }
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::{PlatformStdChild, TRACED_WAIT_POLL};
212 use crate::platform::process::CaptureCancellation;
213 use std::io::Read;
214 use std::process::{Command, Stdio};
215 use std::time::{Duration, Instant};
216
217 const FIXTURE_ENV: &str = "RUNNING_PROCESS_STD_CHILD_FIXTURE";
218
219 #[test]
221 fn std_child_fixture() {
222 let mode = std::env::var_os(FIXTURE_ENV);
223 match mode.as_ref().and_then(|mode| mode.to_str()) {
224 Some("exit7") => std::process::exit(7),
225 Some("sleep") => std::thread::sleep(Duration::from_secs(60)),
226 Some("print") => {
227 println!("std-child-stdout");
228 eprintln!("std-child-stderr");
229 }
230 _ => {}
231 }
232 }
233
234 fn fixture(mode: &str, piped: bool) -> PlatformStdChild {
235 let mut command = Command::new(std::env::current_exe().expect("test executable"));
236 command
237 .args([
238 "--exact",
239 "std_child::tests::std_child_fixture",
240 "--nocapture",
241 ])
242 .env(FIXTURE_ENV, mode)
243 .stdin(Stdio::null());
244 if piped {
245 command.stdout(Stdio::piped()).stderr(Stdio::piped());
246 } else {
247 command.stdout(Stdio::null()).stderr(Stdio::null());
248 }
249 PlatformStdChild::from_std(command.spawn().expect("spawn fixture"), false)
250 }
251
252 fn poll_exit(child: &mut PlatformStdChild, limit: Duration) -> Option<i32> {
253 let deadline = Instant::now() + limit;
254 while Instant::now() < deadline {
255 if let Some(code) = child.try_wait_code().expect("try_wait") {
256 return Some(code);
257 }
258 std::thread::sleep(TRACED_WAIT_POLL);
259 }
260 None
261 }
262
263 #[test]
264 fn exit_is_observed_by_polling() {
265 let mut child = fixture("exit7", false);
266 assert!(child.id() > 0);
267 assert!(!child.is_exact_trace());
268 assert!(child.std_child().is_some());
269 assert_eq!(poll_exit(&mut child, Duration::from_secs(30)), Some(7));
270 assert_eq!(child.try_wait_code().expect("try_wait"), Some(7));
272 assert_eq!(child.wait_code().expect("wait"), 7);
273 }
274
275 #[test]
276 fn kill_ends_a_running_child() {
277 let mut child = fixture("sleep", false);
278 let pid = child.id();
279 assert_eq!(child.try_wait_code().expect("try_wait"), None);
280 child.kill().expect("kill");
281 let code = child.wait_code().expect("reap");
282 assert_ne!(code, 0);
283 assert_eq!(child.id(), pid, "pid is stable across the reap");
284 }
285
286 #[test]
287 fn group_kill_without_own_group_falls_back_to_the_child() {
288 let mut child = fixture("sleep", false);
289 child.kill_group_or_child().expect("kill");
290 assert!(poll_exit(&mut child, Duration::from_secs(30)).is_some());
291 }
292
293 #[test]
294 fn capture_readers_drain_and_register_for_cancellation() {
295 let mut child = fixture("print", true);
296 let cancellation = CaptureCancellation::default();
297 let readers = child
298 .prepare_capture(&cancellation)
299 .expect("prepare capture");
300 let collect = |mut reader: Box<dyn Read + Send>| {
301 std::thread::spawn(move || {
302 let mut text = String::new();
303 reader.read_to_string(&mut text).expect("read capture");
304 text
305 })
306 };
307 let stdout = collect(readers.stdout);
308 let stderr = collect(readers.stderr);
309 assert_eq!(child.wait_code().expect("wait"), 0);
310 assert!(stdout.join().expect("stdout").contains("std-child-stdout"));
311 assert!(stderr.join().expect("stderr").contains("std-child-stderr"));
312 crate::cancel_capture_reader(&cancellation);
314 }
315
316 #[test]
317 fn capture_requires_both_pipes() {
318 let mut child = fixture("sleep", false);
319 let error = child
320 .prepare_capture(&CaptureCancellation::default())
321 .err()
322 .expect("unpiped capture is rejected");
323 assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe);
324 child.discard_after_start_error();
325 }
326}