1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//! <!-- cargo-sync-readme -->
//! Extensions to `tokio::process::Child` to terminate processes.
//!
//! ```rust
//! use tokio::process::Command;
//! use tokio_process_terminate::TerminateExt;
//!
//! #[tokio::main]
//! async fn main() {
//! #   #[cfg(unix)]
//! #   {
//!     let mut command = Command::new("sleep")
//!         .arg("10")
//!         .spawn()
//!         .unwrap();
//!     tokio::time::sleep(std::time::Duration::from_secs(1)).await;
//!     let exit = command.terminate_wait().await.unwrap();
//!     dbg!(exit);
//!     let code = exit.code();
//!     // On Unix, code should be `None` if the process was terminated by a signal.
//!     assert!(code.is_none());
//! #  }
//! }
//! ```

use std::{process::ExitStatus, time::Duration};

#[async_trait::async_trait]
pub trait TerminateExt {
    /// Send a signal to the process to terminate it.
    ///
    /// On Unix, this sends a SIGTERM signal to the process.
    /// On Windows, this sends a CTRL_C_EVENT to the process.
    fn terminate(&mut self);

    #[doc(hidden)]
    async fn _wait(&mut self) -> std::io::Result<ExitStatus>;
    #[doc(hidden)]
    async fn _kill(&mut self) -> std::io::Result<()>;

    /// Terminate the process and wait for it to exit.
    async fn terminate_wait(&mut self) -> std::io::Result<ExitStatus> {
        self.terminate();
        self._wait().await
    }

    /// Terminate the process and wait for it to exit, or kill it after a timeout.
    ///
    /// If the process exits before the timeout, the exit status is returned.
    /// If the timeout elapses before the process exits, it is killed and `None` is returned.
    async fn terminate_timeout(
        &mut self,
        timeout: Duration,
    ) -> std::io::Result<Option<ExitStatus>> {
        self.terminate();
        match tokio::time::timeout(timeout, self._wait()).await {
            Ok(result) => result.map(Some),
            Err(_) => self._kill().await.map(|_| None),
        }
    }
}

#[cfg(unix)]
mod unix;

#[cfg(windows)]
mod windows;