Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kill child process while waiting for it

Tags:

rust

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.

like image 386
Lukas Kalbertodt Avatar asked Sep 25 '22 12:09

Lukas Kalbertodt


2 Answers

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).

like image 176
ArtemGr Avatar answered Sep 29 '22 03:09

ArtemGr


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.

like image 29
malbarbo Avatar answered Sep 29 '22 01:09

malbarbo