command_group/tokio/child.rs
1use std::{
2 fmt,
3 io::Result,
4 process::{ExitStatus, Output},
5};
6
7use tokio::{io::AsyncReadExt, process::Child};
8
9#[cfg(unix)]
10pub(self) use unix::ChildImp;
11#[cfg(windows)]
12pub(self) use windows::ChildImp;
13
14#[cfg(unix)]
15use nix::sys::signal::Signal;
16
17#[cfg(windows)]
18use winapi::um::winnt::HANDLE;
19
20#[cfg(unix)]
21mod unix;
22#[cfg(windows)]
23mod windows;
24
25/// Representation of a running or exited child process group (Tokio variant).
26///
27/// This wraps Tokio’s [`Child`] type with methods that work with process groups.
28///
29/// # Examples
30///
31/// ```should_panic
32/// # #[tokio::main]
33/// # async fn main() {
34/// use tokio::process::Command;
35/// use command_group::AsyncCommandGroup;
36///
37/// let mut child = Command::new("/bin/cat")
38/// .arg("file.txt")
39/// .group_spawn()
40/// .expect("failed to execute child");
41///
42/// let ecode = child.wait()
43/// .await
44/// .expect("failed to wait on child");
45///
46/// assert!(ecode.success());
47/// # }
48/// ```
49pub struct AsyncGroupChild {
50 imp: ChildImp,
51 exitstatus: Option<ExitStatus>,
52}
53
54impl fmt::Debug for AsyncGroupChild {
55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56 f.debug_struct("AsyncGroupChild").finish()
57 }
58}
59
60impl AsyncGroupChild {
61 #[cfg(unix)]
62 pub(crate) fn new(inner: Child) -> Self {
63 Self {
64 imp: ChildImp::new(inner),
65 exitstatus: None,
66 }
67 }
68
69 #[cfg(windows)]
70 pub(crate) fn new(inner: Child, j: HANDLE, c: HANDLE) -> Self {
71 Self {
72 imp: ChildImp::new(inner, j, c),
73 exitstatus: None,
74 }
75 }
76
77 /// Returns the stdlib [`Child`] object.
78 ///
79 /// Note that the inner child may not be in the same state as this output child, due to how
80 /// methods like `wait` and `kill` are implemented. It is not recommended to use this method
81 /// _after_ using any of the other methods on this struct.
82 ///
83 /// # Examples
84 ///
85 /// Reading from stdout:
86 ///
87 /// ```no_run
88 /// # #[tokio::main]
89 /// # async fn main() {
90 /// use std::process::Stdio;
91 /// use tokio::{io::AsyncReadExt, process::Command};
92 /// use command_group::AsyncCommandGroup;
93 ///
94 /// let mut child = Command::new("ls").stdout(Stdio::piped()).group_spawn().expect("ls command didn't start");
95 /// let mut output = String::new();
96 /// if let Some(mut out) = child.inner().stdout.take() {
97 /// out.read_to_string(&mut output).await.expect("failed to read from child");
98 /// }
99 /// println!("output: {}", output);
100 /// # }
101 /// ```
102 pub fn inner(&mut self) -> &mut Child {
103 self.imp.inner()
104 }
105
106 /// Consumes itself and returns the stdlib [`Child`] object.
107 ///
108 /// Note that the inner child may not be in the same state as this output child, due to how
109 /// methods like `wait` and `kill` are implemented. It is not recommended to use this method
110 /// _after_ using any of the other methods on this struct.
111 ///
112 #[cfg_attr(
113 windows,
114 doc = "On Windows, this unnavoidably leaves a handle unclosed. Prefer [`inner()`](Self::inner)."
115 )]
116 ///
117 /// # Examples
118 ///
119 /// Writing to input:
120 ///
121 /// ```no_run
122 /// # #[tokio::main]
123 /// # async fn main() {
124 /// use std::process::Stdio;
125 /// use tokio::{io::AsyncWriteExt, process::Command};
126 /// use command_group::AsyncCommandGroup;
127 ///
128 /// let mut child = Command::new("cat").stdin(Stdio::piped()).group_spawn().expect("cat command didn't start");
129 /// if let Some(mut din) = child.into_inner().stdin.take() {
130 /// din.write_all(b"Woohoo!").await.expect("failed to write");
131 /// }
132 /// # }
133 /// ```
134 pub fn into_inner(self) -> Child {
135 self.imp.into_inner()
136 }
137
138 /// Forces the child process group to exit.
139 ///
140 /// If the group has already exited, an [`InvalidInput`] error is returned.
141 ///
142 /// This is equivalent to sending a SIGKILL on Unix platforms.
143 ///
144 /// See [the Tokio documentation](Child::kill) for more.
145 ///
146 /// # Examples
147 ///
148 /// Basic usage:
149 ///
150 /// ```no_run
151 /// # #[tokio::main]
152 /// # async fn main() {
153 /// use tokio::process::Command;
154 /// use command_group::AsyncCommandGroup;
155 ///
156 /// let mut command = Command::new("yes");
157 /// if let Ok(mut child) = command.group_spawn() {
158 /// child.kill().await.expect("command wasn't running");
159 /// } else {
160 /// println!("yes command didn't start");
161 /// }
162 /// # }
163 /// ```
164 ///
165 /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
166 pub async fn kill(&mut self) -> Result<()> {
167 self.start_kill()?;
168 self.wait().await?;
169 Ok(())
170 }
171
172 /// Attempts to force the child to exit, but does not wait for the request to take effect.
173 ///
174 /// This is equivalent to sending a SIGKILL on Unix platforms.
175 ///
176 /// Note that on Unix platforms it is possible for a zombie process to remain after a kill is
177 /// sent; to avoid this, the caller should ensure that either `child.wait().await` or
178 /// `child.try_wait()` is invoked successfully.
179 ///
180 /// See [the Tokio documentation](Child::start_kill) for more.
181 ///
182 /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
183 pub fn start_kill(&mut self) -> Result<()> {
184 self.imp.start_kill()
185 }
186
187 /// Returns the OS-assigned process group identifier.
188 ///
189 /// Like Tokio, this returns `None` if the child process group has alread exited, to avoid
190 /// holding onto an expired (and possibly reused) PGID.
191 ///
192 /// See [the Tokio documentation](Child::id) for more.
193 ///
194 /// # Examples
195 ///
196 /// Basic usage:
197 ///
198 /// ```no_run
199 /// # #[tokio::main]
200 /// # async fn main() {
201 /// use tokio::process::Command;
202 /// use command_group::AsyncCommandGroup;
203 ///
204 /// let mut command = Command::new("ls");
205 /// if let Ok(child) = command.group_spawn() {
206 /// if let Some(pgid) = child.id() {
207 /// println!("Child group's ID is {}", pgid);
208 /// } else {
209 /// println!("Child group is gone");
210 /// }
211 /// } else {
212 /// println!("ls command didn't start");
213 /// }
214 /// # }
215 /// ```
216 pub fn id(&self) -> Option<u32> {
217 self.imp.id()
218 }
219
220 /// Waits for the child group to exit completely, returning the status that the process leader
221 /// exited with.
222 ///
223 /// See [the Tokio documentation](Child::wait) for more.
224 ///
225 /// The current implementation spawns a blocking task on the Tokio thread pool; contributions
226 /// are welcome for a better version.
227 ///
228 /// An important consideration on Unix platforms is that there is no way to cancel the `wait`
229 /// syscall. _Cancelling this future_ will **not** cancel that underlying `wait` call. That has
230 /// consequences: a `wait`ed process that exits will have its resources cleaned up by the kernel.
231 /// If the application is no longer listening for that `wait` returning, it will not know that
232 /// the process has been cleaned up, and will try to wait on it again. That in turn may fail, or
233 /// could even attach to a recycled PID which would then point to a completely different process.
234 ///
235 /// # Examples
236 ///
237 /// Basic usage:
238 ///
239 /// ```no_run
240 /// # #[tokio::main]
241 /// # async fn main() {
242 /// use tokio::process::Command;
243 /// use command_group::AsyncCommandGroup;
244 ///
245 /// let mut command = Command::new("ls");
246 /// if let Ok(mut child) = command.group_spawn() {
247 /// child.wait().await.expect("command wasn't running");
248 /// println!("Child has finished its execution!");
249 /// } else {
250 /// println!("ls command didn't start");
251 /// }
252 /// # }
253 /// ```
254 pub async fn wait(&mut self) -> Result<ExitStatus> {
255 if let Some(es) = self.exitstatus {
256 return Ok(es);
257 }
258
259 drop(self.imp.take_stdin());
260 let status = self.imp.wait().await?;
261 self.exitstatus = Some(status);
262 Ok(status)
263 }
264
265 /// Attempts to collect the exit status of the child if it has already exited.
266 ///
267 /// See [the Tokio documentation](Child::try_wait) for more.
268 ///
269 /// # Examples
270 ///
271 /// Basic usage:
272 ///
273 /// ```no_run
274 /// # #[tokio::main]
275 /// # async fn main() {
276 /// use tokio::process::Command;
277 /// use command_group::AsyncCommandGroup;
278 ///
279 /// let mut child = Command::new("ls").group_spawn().unwrap();
280 ///
281 /// match child.try_wait() {
282 /// Ok(Some(status)) => println!("exited with: {}", status),
283 /// Ok(None) => {
284 /// println!("status not ready yet, let's really wait");
285 /// let res = child.wait().await;
286 /// println!("result: {:?}", res);
287 /// }
288 /// Err(e) => println!("error attempting to wait: {}", e),
289 /// }
290 /// # }
291 /// ```
292 pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
293 if self.exitstatus.is_some() {
294 return Ok(self.exitstatus);
295 }
296
297 match self.imp.try_wait()? {
298 Some(es) => {
299 self.exitstatus = Some(es);
300 Ok(Some(es))
301 }
302 None => Ok(None),
303 }
304 }
305
306 /// Simultaneously waits for the child to exit and collect all remaining output on the
307 /// stdout/stderr handles, returning an `Output` instance.
308 ///
309 /// See [the Tokio documentation](Child::wait_with_output) for more.
310 ///
311 /// # Examples
312 ///
313 /// Basic usage:
314 ///
315 /// ```should_panic
316 /// # #[tokio::main]
317 /// # async fn main() {
318 /// use std::process::Stdio;
319 /// use tokio::process::Command;
320 /// use command_group::AsyncCommandGroup;
321 ///
322 /// let child = Command::new("/bin/cat")
323 /// .arg("file.txt")
324 /// .stdout(Stdio::piped())
325 /// .group_spawn()
326 /// .expect("failed to execute child");
327 ///
328 /// let output = child
329 /// .wait_with_output()
330 /// .await
331 /// .expect("failed to wait on child");
332 ///
333 /// assert!(output.status.success());
334 /// # }
335 /// ```
336 pub async fn wait_with_output(mut self) -> Result<Output> {
337 drop(self.imp.take_stdin());
338
339 let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
340 match (self.imp.take_stdout(), self.imp.take_stderr()) {
341 (None, None) => {}
342 (Some(mut out), None) => {
343 out.read_to_end(&mut stdout).await?;
344 }
345 (None, Some(mut err)) => {
346 err.read_to_end(&mut stderr).await?;
347 }
348 (Some(mut out), Some(mut err)) => {
349 // TODO: replace with futures crate usage
350 // and drop macros feature from tokio
351 tokio::try_join!(out.read_to_end(&mut stdout), err.read_to_end(&mut stderr),)?;
352 }
353 }
354
355 let status = self.imp.wait().await?;
356 Ok(Output {
357 status,
358 stdout,
359 stderr,
360 })
361 }
362}
363
364#[cfg(unix)]
365impl crate::UnixChildExt for AsyncGroupChild {
366 fn signal(&self, sig: Signal) -> Result<()> {
367 self.imp.signal_imp(sig)
368 }
369}