Kill child process while waiting for it

2019-01-20 04:28发布

I want to execute another process and normally want to wait until it has finished. Lets say we spawn and wait for the process in thread T1:

let child = Command::new("rustc").spawn().unwrap();
child.wait();

Now, if a special event occurs (which thread T0 is waiting for) I want to kill the spawned process:

if let Ok(event) = special_event_notifier.recv() {
    child.kill();
}

But I don't see a way to do it: both kill and wait take a mutable reference to Child and are therefore mutually exclusive. After calling wait no one can have any reference to child anymore.

I've found the wait-timeout crate, but I want to know if there's another way.

标签: rust
2条回答
在下西门庆
2楼-- · 2019-01-20 04:51

If the child subprocess do not close stdout before finishing, it's possible to wait reading stdout. Here is an example

use std::io::Read;
use std::process::*;
use std::thread;
use std::time::Duration;

fn wait_on_output(mut out: ChildStdout) {
    while out.read_exact(&mut [0; 1024]).is_ok() { }
}

fn wait_or_kill(cmd: &mut Command, max: Duration) {
    let mut child = cmd.stdout(Stdio::piped())
                       .spawn()
                       .expect("Cannot spawn child");

    let out = child.stdout.take().expect("No stdout on child");

    let h = thread::spawn(move || {
        thread::sleep(max);
        child.kill().expect("Cannot kill child");
        println!("{:?}", child.wait());
    });

    wait_on_output(out);
    h.join().expect("join fail");
}

fn main() {
    wait_or_kill(Command::new("sleep").arg("1"), Duration::new(2, 0));
    wait_or_kill(Command::new("sleep").arg("3"), Duration::new(2, 0));
}

The output of this program on my system is

Ok(ExitStatus(ExitStatus(0)))
Ok(ExitStatus(ExitStatus(9)))

Although not in the docs, killing a finished child returns Ok.

This works because killing a process close the files associated with it. However, if the child spawn new processes, killing the child may not kill these other processes and they may keep the stdout opened.

查看更多
别忘想泡老子
3楼-- · 2019-01-20 04:59

Obviously, you can just kill the process yourself. The Child::id method gives you the "OS-assigned process identifier" that should be sufficient for that.

The only problem is that killing a process is a platform-dependent action. On UNIX killing a process is handled with the kill function:

#![feature(libc)]
extern crate libc;
use std::env::args;
use std::process::Command;
use std::thread::{spawn, sleep};
use std::time::Duration;
use libc::{kill, SIGTERM};

fn main() {
    let mut child = Command::new("/bin/sh").arg("-c").arg("sleep 1; echo foo").spawn().unwrap();
    let child_id = child.id();
    if args().any(|arg| arg == "--kill") {
        spawn(move || {
            sleep(Duration::from_millis(100));
            unsafe {
                kill(child_id as i32, SIGTERM);
            }
        });
    }
    child.wait().unwrap();
}

On Windows you might try the OpenProcess and TerminateProcess functions (available with the kernel32-sys crate).

查看更多
登录 后发表回答