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}