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
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Process guard
//!
//! A process guard takes ownership of a `process::Child` and gently or forcefully kills it upon,
//! prevent the process from running on. Example:
//!
//! ```rust
//! use process_guard::ProcessGuard;
//! use std::process;
//!
//! fn insomnia() {
//!     let cmd = process::Command::new("sleep").arg("120");
//!     let pg = ProcessGuard::spawn(cmd);
//!
//!     // a two-minute sleep process has been started, which will be killed as soon as this
//!     // function returns
//! }
//! ```

extern crate nix;
extern crate ticktock;

use std::{io, mem, process, time};

/// Retry an IO operation if it returns with `EINTR`.
#[inline]
fn io_retry<T, F: FnMut() -> io::Result<T>>(mut f: F) -> io::Result<T> {
    // FIXME: do we really need/want `FnMut` here?
    loop {
        match f() {
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
            r @ _ => break r,
        }
    }
}

/// Interval used when polling, waiting for a process to exit
const POLL_INTERVAL: time::Duration = time::Duration::from_millis(100);

/// Protects a process from becoming an orphan or zombie by killing it when the guard is dropped
pub struct ProcessGuard {
    /// Child process. The process might be removed prematurely, in which case we do not kill
    /// anything
    child: Option<process::Child>,

    /// An optional grace time. If set, sends a `SIGTERM` and waits up to `grace_time` before
    /// sending `SIGKILL`.
    grace_time: Option<time::Duration>,
}

impl ProcessGuard {
    /// Create a new child process
    ///
    /// # unsafe
    ///
    /// It is unsafe to put any arbitrary child process into a process guard, mainly because the
    /// guard relies on the child not having been waited on beforehand. Otherwise, it cannot be
    /// guaranteed that the child process has not exited and its PID been reused, potentially
    /// killing an innocent bystander process on `Drop`.
    pub unsafe fn new(child: process::Child, grace_time: Option<time::Duration>) -> ProcessGuard {
        ProcessGuard {
            child: Some(child),
            grace_time,
        }
    }

    /// Retrieves the child process from the process guard
    pub fn into_inner(mut self) -> Option<process::Child> {
        let mut child = None;

        mem::swap(&mut self.child, &mut child);
        child
    }

    /// Spawns a command
    ///
    /// Equivalent to calling `cmd.spawn()`, followed by `new`.
    pub fn spawn(cmd: &mut process::Command) -> io::Result<ProcessGuard> {
        let child = cmd.spawn()?;
        Ok(unsafe { Self::new(child, None) })
    }

    /// Spawns a command with a grace timeout
    ///
    /// Equivalent to calling `cmd.spawn()`, followed by `new`.
    pub fn spawn_graceful(
        cmd: &mut process::Command,
        grace_time: time::Duration,
    ) -> io::Result<ProcessGuard> {
        let child = cmd.spawn()?;
        Ok(unsafe { Self::new(child, Some(grace_time)) })
    }
}

impl Drop for ProcessGuard {
    fn drop(&mut self) {
        if let Some(ref mut child) = self.child {
            // NOTE: we assume that it is impossible for a child's PID to be reused before it has
            //       been reaped, i.e. since we are the first to call `wait` on it, we should never
            //       kill the wrong process

            // upon drop, we terminate, then kill the child
            if let Some(grace_time) = self.grace_time {
                // send SIGKILL, we ignore the result of the kill call, we cannot do anything about
                // any Err. According to the kill(2) manpage, EINTR is not possible when calling
                // kill().
                nix::sys::signal::kill(
                    nix::unistd::Pid::from_raw(child.id() as i32),
                    nix::sys::signal::Signal::SIGTERM,
                ).ok();

                // until we reach `grace_time`, try to reap the child in POLL_INTERVAL intervals
                for _ in ticktock::clock::Clock::new(POLL_INTERVAL)
                    .rel_iter()
                    .take_while(|(_, t)| t <= &grace_time)
                {
                    match io_retry(|| child.try_wait()) {
                        // process did not exit yet, keep polling
                        Ok(None) => continue,
                        // process did exit, we are done
                        Ok(_) => return,
                        // error occured - we won't keep trying to wait, but will make another
                        // effort using SIGKILL
                        Err(_) => break,
                    }
                }
            }

            // SIGTERM was either not requested or unsuccessful, proceed with SIGKILL
            io_retry(|| child.kill()).ok();

            // now wait is bound work. if it fails, we give up
            io_retry(|| child.wait()).ok();
        }
    }
}