pistonite_cu/process/
child.rs1use std::{process::ExitStatus, time::Duration};
2
3use tokio::{process::Child as TokioChild, task::JoinSet};
4
5use crate::{BoxedFuture, Context as _, co};
6
7pub struct Child {
9 pub(crate) name: String,
10 pub(crate) inner: TokioChild,
11 pub(crate) io: ChildIo,
12}
13
14impl Child {
15 pub fn wait_nz(self) -> crate::Result<()> {
24 let status = wait_internal(&self.name, self.inner, self.io)?;
25 if !status.success() {
26 crate::bail!("{} exited with non-zero status", self.name);
27 }
28 Ok(())
29 }
30
31 #[inline(always)]
41 pub fn wait(self) -> crate::Result<ExitStatus> {
42 wait_internal(&self.name, self.inner, self.io)
43 }
44
45 pub async fn co_wait_nz(self) -> crate::Result<()> {
54 let status = co_wait_internal(&self.name, self.inner, self.io).await?;
55 if !status.success() {
56 crate::bail!("{} exited with non-zero status", self.name);
57 }
58 Ok(())
59 }
60
61 #[inline(always)]
70 pub async fn co_wait(self) -> crate::Result<ExitStatus> {
71 co_wait_internal(&self.name, self.inner, self.io).await
72 }
73
74 #[inline(always)]
77 pub fn wait_guard(self) -> ChildWaitGuard {
78 ChildWaitGuard { inner: Some(self) }
79 }
80
81 pub async fn co_wait_timeout(
84 &mut self,
85 timeout: Duration,
86 ) -> crate::Result<Option<ExitStatus>> {
87 let ms = 100;
88 let mut total_ms = 0;
89 loop {
90 match self.inner.try_wait() {
91 Ok(Some(s)) => return Ok(Some(s)),
92 Ok(None) => {}
93 Err(e) => {
94 crate::rethrow!(e, "io error while waiting {}", self.name)
95 }
96 }
97 tokio::time::sleep(Duration::from_millis(ms)).await;
98 total_ms += ms;
99 if Duration::from_millis(total_ms) >= timeout {
100 break;
101 }
102 }
103 Ok(None)
104 }
105
106 pub fn wait_timeout(&mut self, timeout: Duration) -> crate::Result<Option<ExitStatus>> {
107 let ms = 100;
108 let mut total_ms = 0;
109 loop {
110 match self.inner.try_wait() {
111 Ok(Some(s)) => return Ok(Some(s)),
112 Ok(None) => {}
113 Err(e) => {
114 crate::rethrow!(e, "io error while waiting {}", self.name)
115 }
116 }
117 std::thread::sleep(Duration::from_millis(ms));
118 total_ms += ms;
119 if Duration::from_millis(total_ms) >= timeout {
120 break;
121 }
122 }
123 Ok(None)
124 }
125
126 pub async fn co_kill(mut self) -> crate::Result<ExitStatus> {
131 let mut ms = 100;
132 for i in 0..5 {
133 crate::trace!("trying to kill child '{}', attempt {}", self.name, i + 1);
134 crate::check!(
135 self.inner.start_kill(),
136 "failed to send kill signal to child"
137 )?;
138 match self.inner.try_wait() {
139 Ok(Some(s)) => return Ok(s),
140 Ok(None) => {}
141 Err(e) => {
142 crate::rethrow!(e, "io error while killing {}", self.name)
143 }
144 }
145 tokio::time::sleep(Duration::from_millis(ms)).await;
146 ms *= 4;
147 }
148 self.io.co_join(&self.name).await;
149 crate::bail!("failed to kill child '{}' after many attempts", self.name);
150 }
151
152 pub fn kill(mut self) -> crate::Result<ExitStatus> {
158 let mut ms = 100;
159 for i in 0..5 {
160 crate::trace!("trying to kill child '{}', attempt {}", self.name, i + 1);
161 crate::check!(
162 self.inner.start_kill(),
163 "failed to send kill signal to child"
164 )?;
165 match self.inner.try_wait() {
166 Ok(Some(s)) => return Ok(s),
167 Ok(None) => {}
168 Err(e) => {
169 crate::rethrow!(e, "io error while killing {}", self.name)
170 }
171 }
172 std::thread::sleep(Duration::from_millis(ms));
173 ms *= 4;
174 }
175 self.io.join(&self.name);
176 crate::bail!("failed to kill child '{}' after many attempts", self.name);
177 }
178}
179
180fn wait_internal(name: &str, mut child: TokioChild, io: ChildIo) -> crate::Result<ExitStatus> {
181 let wait_task = co::spawn(async move { child.wait().await });
183 io.join(name);
186 crate::check!(wait_task.join()?, "io error while executing {name}")
187}
188
189async fn co_wait_internal(
190 name: &str,
191 mut child: TokioChild,
192 io: ChildIo,
193) -> crate::Result<ExitStatus> {
194 let wait_task = co::spawn(async move { child.wait().await });
196 io.co_join(name).await;
199 crate::check!(
200 wait_task.co_join().await?,
201 "io error while executing {name}"
202 )
203}
204
205pub(crate) struct ChildIo {
207 inner: co::Handle<(bool, crate::Result<()>)>,
209}
210impl ChildIo {
211 pub fn start(
212 stdin: Option<BoxedFuture<crate::Result<()>>>,
213 stdout: Option<BoxedFuture<()>>,
214 stderr: Option<BoxedFuture<()>>,
215 ) -> Self {
216 let combined_future = async move {
217 let mut j = JoinSet::new();
218 if let Some(x) = stdin {
219 j.spawn(x);
220 }
221 if let Some(x) = stdout {
222 j.spawn(async move {
223 x.await;
224 Ok(())
225 });
226 }
227 if let Some(x) = stderr {
228 j.spawn(async move {
229 x.await;
230 Ok(())
231 });
232 }
233
234 let mut panicked = false;
235 let mut stdin_err = Ok(());
236 while let Some(x) = j.join_next().await {
237 match x {
238 Err(_) => panicked = true,
240 Ok(Err(e)) => {
241 stdin_err = Err(e);
244 }
245 Ok(Ok(())) => {}
246 }
247 }
248 (panicked, stdin_err)
249 };
250 let io_task = co::spawn(combined_future);
251 Self { inner: io_task }
252 }
253
254 pub fn join(self, name: &str) {
255 Self::do_join(name, self.inner.join_maybe_aborted())
256 }
257
258 pub async fn co_join(self, name: &str) {
259 Self::do_join(name, self.inner.co_join_maybe_aborted().await)
260 }
261
262 fn do_join(name: &str, result: crate::Result<Option<(bool, crate::Result<()>)>>) {
263 match result {
264 Ok(Some((panicked, stdin_err))) => {
265 if let Err(e) = stdin_err {
266 crate::warn!("failed to write to stdin while executing {name}: {e:?}")
267 }
268 if panicked {
269 crate::warn!("some io tasks panicked while executing {name}");
270 }
271 }
272 Ok(None) => {
273 }
275 Err(_) => {
276 crate::warn!("some io tasks panicked while executing {name}");
277 }
278 }
279 }
280}
281
282pub struct ChildWaitGuard {
287 inner: Option<Child>,
288}
289impl Drop for ChildWaitGuard {
290 fn drop(&mut self) {
291 let Some(child) = self.inner.take() else {
292 return;
293 };
294 match child.wait() {
295 Err(e) => crate::trace!("wait guard: error while waiting for child: {e}"),
296 Ok(x) => crate::trace!("wait guard: child exited with status: {x}"),
297 };
298 }
299}
300impl ChildWaitGuard {
301 pub fn wait_nz(mut self) -> crate::Result<()> {
303 self.inner.take().unwrap().wait_nz()
304 }
305 pub fn wait(mut self) -> crate::Result<ExitStatus> {
307 self.inner.take().unwrap().wait()
308 }
309}