//! The three pipes of one container: its arguments go in on standard input, its output and errors //! come back, each on its own thread so no pipe can block another. use std::io::{Read, Write}; use std::process::Child; use std::thread::JoinHandle; /// Step 4: the three threads that feed and read one container, so no pipe can block another. pub(crate) struct Io { writer: Option>, stdout: Option, bool)>>, stderr: Option, bool)>>, } impl Io { /// Take `child`'s three pipes (`child.stdin.take()` and so on) and start one thread for each: /// write `input` to standard input and then drop it; `read_capped` standard output with `cap`; /// `read_capped` standard error with `STDERR_KEPT`. A pipe that is `None` gets no thread. pub(crate) fn start(child: &mut Child, input: String, cap: usize) -> Io { let writer = child.stdin.take().map(|mut stdin| { std::thread::spawn(move || { let _ = stdin.write_all(input.as_bytes()); }) }); let stdout = child .stdout .take() .map(|stdout| std::thread::spawn(move || read_capped(stdout, cap))); let stderr = child.stderr.take().map(|stderr| { std::thread::spawn(move || read_capped(stderr, crate::container::STDERR_KEPT)) }); Io { writer, stdout, stderr, } } /// Step 6: join the three threads. A thread that is missing or panicked counts as empty /// (`join().ok()`, `unwrap_or_default()`). Returns the kept standard output, whether there was /// more, and the kept standard error decoded with `from_utf8_lossy`. pub(crate) fn finish(self) -> (Vec, bool, String) { if let Some(handle) = self.writer { let _ = handle.join(); } let (out, truncated) = match self.stdout { Some(handle) => handle.join().ok().unwrap_or_default(), None => (Vec::new(), false), }; let err = match self.stderr { Some(handle) => { let (bytes, _) = handle.join().ok().unwrap_or_default(); String::from_utf8_lossy(&bytes).into_owned() } None => String::new(), }; (out, truncated, err) } } /// Everything `from` gives, keeping the first `cap` bytes; `true` if there was more. Reads on past /// the cap, so the writer is never blocked or broken by a closed pipe. No indexing: `buf.get(..n)`. pub(crate) fn read_capped(from: impl Read, cap: usize) -> (Vec, bool) { let mut from = from; let mut kept: Vec = Vec::new(); let mut truncated = false; let mut chunk = [0u8; 8192]; loop { let n = match from.read(&mut chunk) { Ok(0) => break, Ok(n) => n, Err(_) => break, }; let remaining = cap - kept.len(); if remaining == 0 { truncated = true; } else { let take = remaining.min(n); kept.extend_from_slice(&chunk[..take]); if n > take { truncated = true; } } } (kept, truncated) }