tokio_process_terminate/lib.rs
1//! <!-- cargo-sync-readme -->
2//! Extensions to `tokio::process::Child` to terminate processes.
3//!
4//! ```rust
5//! use tokio::process::Command;
6//! use tokio_process_terminate::TerminateExt;
7//!
8//! #[tokio::main]
9//! async fn main() {
10//! # #[cfg(unix)]
11//! # {
12//! let mut command = Command::new("sleep")
13//! .arg("10")
14//! .spawn()
15//! .unwrap();
16//! tokio::time::sleep(std::time::Duration::from_secs(1)).await;
17//! let exit = command.terminate_wait().await.unwrap();
18//! dbg!(exit);
19//! let code = exit.code();
20//! // On Unix, code should be `None` if the process was terminated by a signal.
21//! assert!(code.is_none());
22//! # }
23//! }
24//! ```
25
26use std::{process::ExitStatus, time::Duration};
27
28#[async_trait::async_trait]
29pub trait TerminateExt {
30 /// Send a signal to the process to terminate it.
31 ///
32 /// On Unix, this sends a SIGTERM signal to the process.
33 /// On Windows, this sends a CTRL_C_EVENT to the process.
34 fn terminate(&mut self);
35
36 #[doc(hidden)]
37 async fn _wait(&mut self) -> std::io::Result<ExitStatus>;
38 #[doc(hidden)]
39 async fn _kill(&mut self) -> std::io::Result<()>;
40
41 /// Terminate the process and wait for it to exit.
42 async fn terminate_wait(&mut self) -> std::io::Result<ExitStatus> {
43 self.terminate();
44 self._wait().await
45 }
46
47 /// Terminate the process and wait for it to exit, or kill it after a timeout.
48 ///
49 /// If the process exits before the timeout, the exit status is returned.
50 /// If the timeout elapses before the process exits, it is killed and `None` is returned.
51 async fn terminate_timeout(
52 &mut self,
53 timeout: Duration,
54 ) -> std::io::Result<Option<ExitStatus>> {
55 self.terminate();
56 match tokio::time::timeout(timeout, self._wait()).await {
57 Ok(result) => result.map(Some),
58 Err(_) => self._kill().await.map(|_| None),
59 }
60 }
61}
62
63#[cfg(unix)]
64pub mod unix;
65
66#[cfg(windows)]
67mod windows;