rustchild-processkill

Rust kill std::process::Child after finishing executing


I spawned a child process with:

let child = Command::new("./chromedriver")
    .spawn()
    .expect("Failed To Run Chromedriver");

I want to kill it at the end of the execution when the main process terminates. I find it a difficult task as:

  1. It seems like there is no easy way to register function to be run at the end of the process in Rust. I have to use something like libc::atexit().
  2. I do not know how to pass arguments to C function pointers in rust. So I have to use a global variable
  3. I do not know how to store std::process::Child as a global, static variable without defining it. It give errors free static item without body if I simple put static mut CHILD_PROCESS: std::process::Child; in the beginning of the file.

So here is the convoluted solution I managed to produce:

static mut CHILD_PROCESS_ID: i32 = 0;

extern "C" fn kill_child() {
    unsafe {
        let pid: libc::pid_t = CHILD_PROCESS_ID;
        libc::kill(pid, libc::SIGKILL);
    }
}

fn run_chrome_driver() {
    let child = Command::new("./chromedriver")
        .spawn()
        .expect("Failed To Run Chromedriver");
    unsafe {
        let id: i32 = child.id().try_into().unwrap();
        CHILD_PROCESS_ID = id;
        libc::atexit(kill_child);
    }
}

Is there a better approach without using unsafe Rust?


Solution

  • atexit() is probably not what you want. Since it is executed after Rust has considered the process to exit, it is not safe to use any standard library API from inside it. For this reason, it is pretty much impossible to encapsulate it in a safe API. You will have to use unsafe.

    Assuming you have panic = "unwind", the safest option is to have a drop guard in main() that kills the child. This can look like:

    use std::process::{Child, Command};
    use std::sync::Mutex;
    
    static CHILD: Mutex<Option<Child>> = Mutex::new(None);
    
    struct KillChildGuard;
    impl Drop for KillChildGuard {
        fn drop(&mut self) {
            let child = CHILD.lock().unwrap().take();
            if let Some(mut child) = child {
                child.kill().expect("failed to kill");
            }
        }
    }
    
    fn main() {
        let _guard = KillChildGuard;
    
        // Anywhere inside the code, even deep in functions:
        let child = Command::new("./chromedriver")
            .spawn()
            .expect("Failed To Run Chromedriver");
        *CHILD.lock().unwrap() = Some(child);
    }
    

    If you have panic = "abort", then atexit() seems like the only option, but beware of the above warning.