rightkit_child_process/
native_capture.rs1use crate::CapturedOutput;
4use std::{
5 fs::File,
6 io,
7 process::{ChildStderr, ChildStdout},
8 sync::{
9 atomic::{AtomicBool, Ordering},
10 Arc, Condvar, Mutex,
11 },
12 thread::{self, JoinHandle},
13 time::Duration,
14};
15
16#[cfg(unix)]
17#[path = "native_capture/unix.rs"]
18mod platform;
19#[cfg(windows)]
20#[path = "native_capture/windows.rs"]
21pub(crate) mod platform;
22
23const CHUNK: usize = 8 * 1024;
24const IDLE_WAIT: Duration = Duration::from_millis(20);
25
26#[derive(Default)]
27struct State {
28 output: CapturedOutput,
29 error: Option<io::Error>,
30 eof: bool,
31 finished: bool,
32}
33
34type Shared = (Mutex<State>, Condvar);
35
36struct Done(Arc<Shared>);
37
38impl Drop for Done {
39 fn drop(&mut self) {
40 self.0 .0.lock().unwrap_or_else(|e| e.into_inner()).finished = true;
41 self.0 .1.notify_all();
42 }
43}
44
45pub struct NativeOutputCapture {
60 shared: Arc<Shared>,
61 stop: Arc<AtomicBool>,
62 handle: Option<JoinHandle<()>>,
63}
64
65impl NativeOutputCapture {
66 pub fn stdout(pipe: ChildStdout, max_bytes: usize) -> io::Result<Self> {
69 Self::start(platform::stdout(pipe), max_bytes)
70 }
71
72 pub fn stderr(pipe: ChildStderr, max_bytes: usize) -> io::Result<Self> {
74 Self::start(platform::stderr(pipe), max_bytes)
75 }
76
77 fn start(pipe: File, max_bytes: usize) -> io::Result<Self> {
78 let prepared = platform::prepare(pipe)?;
79 let shared: Arc<Shared> = Arc::default();
80 let stop = Arc::new(AtomicBool::new(false));
81 let state = Arc::clone(&shared);
82 let cancelled = Arc::clone(&stop);
83 let handle = thread::Builder::new()
84 .name("rightkit-native-output-capture".into())
85 .spawn(move || {
86 let _done = Done(Arc::clone(&state));
87 let mut pipe = platform::reader(prepared);
88 let mut buffer = [0; CHUNK];
89 while !cancelled.load(Ordering::Acquire) {
90 match platform::read(&mut pipe, &mut buffer) {
91 Ok(0) => {
92 state.0.lock().unwrap_or_else(|e| e.into_inner()).eof = true;
93 break;
94 }
95 Ok(count) => state
96 .0
97 .lock()
98 .unwrap_or_else(|e| e.into_inner())
99 .output
100 .push(&buffer[..count], max_bytes),
101 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
102 thread::park_timeout(IDLE_WAIT);
103 }
104 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
105 Err(error) => {
106 state.0.lock().unwrap_or_else(|e| e.into_inner()).error = Some(error);
107 break;
108 }
109 }
110 }
111 if let Err(error) = platform::shutdown(&mut pipe) {
112 let mut state = state.0.lock().unwrap_or_else(|e| e.into_inner());
113 if state.error.is_none() {
114 state.error = Some(error);
115 }
116 }
117 drop(pipe);
119 })?;
120 Ok(Self {
121 shared,
122 stop,
123 handle: Some(handle),
124 })
125 }
126
127 pub fn snapshot(&self) -> CapturedOutput {
129 self.shared
130 .0
131 .lock()
132 .unwrap_or_else(|e| e.into_inner())
133 .output
134 .clone()
135 }
136
137 pub fn is_complete(&self) -> bool {
140 self.shared.0.lock().unwrap_or_else(|e| e.into_inner()).eof
141 }
142
143 pub fn take_error(&self) -> Option<io::Error> {
145 self.shared
146 .0
147 .lock()
148 .unwrap_or_else(|e| e.into_inner())
149 .error
150 .take()
151 }
152
153 pub fn finish_within(&mut self, timeout: Duration) -> CapturedOutput {
157 {
158 let state = self.shared.0.lock().unwrap_or_else(|e| e.into_inner());
159 drop(
160 self.shared
161 .1
162 .wait_timeout_while(state, timeout, |state| !state.finished)
163 .unwrap_or_else(|e| e.into_inner()),
164 );
165 }
166 self.stop_and_join();
167 self.snapshot()
168 }
169
170 pub fn cancel(&mut self) -> CapturedOutput {
172 self.stop_and_join();
173 self.snapshot()
174 }
175
176 fn stop_and_join(&mut self) {
177 self.stop.store(true, Ordering::Release);
178 if let Some(handle) = self.handle.take() {
179 handle.thread().unpark();
180 if handle.join().is_err() {
181 self.shared
182 .0
183 .lock()
184 .unwrap_or_else(|e| e.into_inner())
185 .error = Some(io::Error::other("native pipe reader panicked"));
186 }
187 }
188 }
189}
190
191impl Drop for NativeOutputCapture {
192 fn drop(&mut self) {
193 self.stop_and_join();
194 }
195}