Repository: tokio-rs/tokio
File: tokio/src/process/mod.rs (Rust)

Code before:
    /// # use tokio::process::Command;
    /// # use tokio::sync::oneshot::channel;
    /// #
    /// # use futures::future::join;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let (_tx, rx) = channel::<()>();
    ///
    ///     let mut child = Command::new("echo")
    ///         .arg("Hello World!")
    ///         .stdout(Stdio::piped())
    ///         .spawn()
    ///         .unwrap();
    ///
    ///     let mut stdout = child.stdout.take().expect("stdout is not captured");
    ///     let mut buff = Vec::new();
    ///     let wait_for_output = join(child.wait(), stdout.read_to_end(&mut buff));
    ///
    ///     tokio::select! {
    ///             _ = wait_for_output => {}
    ///             _ = rx => child.kill().await.expect("kill failed"),
    ///     }
    ///
    ///     assert_eq!(buff, b"Hello World!\n");
    /// }
    /// ```
    pub async fn kill(&mut self) -> io::Result<()> {
        self.start_kill()?;
        self.wait().await?;
        Ok(())
    }

    /// Waits for the child to exit completely, returning the status that it
    /// exited with. This function will continue to have the same return value
    /// after it has been called at least once.
    ///
    /// The stdin handle to the child process, if any, will be closed
    /// before waiting. This helps avoid deadlock: it ensures that the
    /// child does not block waiting for input from the parent, while
    /// the parent waits for the child to exit.
    ///
    /// If the caller wishes to explicitly control when the child's stdin
    /// handle is closed, they may `.take()` it before calling `.wait()`:
    ///
    /// # Cancel safety
    ///
    /// This function is cancel safe.
    ///
    /// ```
    /// # if cfg!(miri) { return } // No `pidfd_spawnp` in miri.

Review comment (suggestion):
I think what I would recommend is that you read the output from a background task. Something along these lines:
```rs
let stdout_task = tokio::spawn(async { /* read stdout here */ });
tokio::select! {
     _ = child.wait() => {},
     _ = kill_signal => child.kill().await?,
};
let output = stdout_task.await?;
```


Code after:
    /// # use tokio::process::Command;
    /// # use tokio::sync::oneshot::channel;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let (_tx, rx) = channel::<()>();
    ///
    ///     let mut child = Command::new("echo")
    ///         .arg("Hello World!")
    ///         .stdout(Stdio::piped())
    ///         .spawn()
    ///         .unwrap();
    ///
    ///     let mut stdout = child.stdout.take().expect("stdout is not captured");
    ///
    ///     let read_stdout = tokio::spawn(async move {
    ///         let mut buff = Vec::new();
    ///         let _ = stdout.read_to_end(&mut buff).await;
    ///
    ///         buff
    ///     });
    ///
    ///     tokio::select! {
    ///         _ = child.wait() => {}
    ///         _ = rx => { child.kill().await.expect("kill failed") },
    ///     }
    ///
    ///     let buff = read_stdout.await.unwrap();
    ///
    ///     assert_eq!(buff, b"Hello World!\n");
    /// }
    /// ```
    pub async fn kill(&mut self) -> io::Result<()> {
        self.start_kill()?;
        self.wait().await?;
        Ok(())
    }

    /// Waits for the child to exit completely, returning the status that it
    /// exited with. This function will continue to have the same return value
    /// after it has been called at least once.
    ///
    /// The stdin handle to the child process, if any, will be closed
    /// before waiting. This helps avoid deadlock: it ensures that the
    /// child does not block waiting for input from the parent, while
    /// the parent waits for the child to exit.
    ///
    /// If the caller wishes to explicitly control when the child's stdin
    /// handle is closed, they may `.take()` it before calling `.wait()`:
    ///
    /// # Cancel safety