How to read the stdout of a killed process in Rust?

94 views Asked by At

I have a function which should be able to, given a command, a timeout and an input, return the stdout, the stderr, and status (ok or timeout) and the time elapsed to run the command.

The prompted commands are used to get the output of a program with a given input. For example, it can be node index.js or python main.py (Command::new("node").arg("index.js")).

I currently use the wait-timeout crate, to limit the child process timeout, and Stdio::piped() to handle the input and ouput pipes.

I have currently the following code:


fn exec(mut command: Command, input: &String, time_limit: &f64) -> Result<RunOutput, String> {

    command
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());

    let mut child = match command.spawn() {
        Ok(value) => value,
        Err(err) => {
            let mut message = String::from("Failed to create process: ");
            message.push_str(&err.to_string());

            return Err(message);
        }
    };

    drop(command);

    let mut stdin = child.stdin.take().unwrap();
    stdin
        .write_all(input.as_bytes())
        .expect("Failed to write to stdin");
    stdin.flush().expect("Cannot flush stdin");
    drop(stdin);

    let mut stdout = child.stdout.take().unwrap();
    let mut stderr = child.stderr.take().unwrap();

    let start = Instant::now();
    let secs = Duration::from_secs_f64(*time_limit);

    if child.wait_timeout(secs).unwrap().is_some() {
        let time_elapsed = start.elapsed().as_millis();

        let mut stdout_vec: Vec<u8> = Vec::new();
        let mut stderr_vec: Vec<u8> = Vec::new();

        stdout.read_to_end(&mut stdout_vec).expect("Unable to read stdout");
        stderr.read_to_end(&mut stderr_vec).expect("Unable to read stderr");
        

        return Ok(
            RunOutput {
                status: RunStatus::Ok,
                time: time_elapsed,
                stdout: String::from_utf8_lossy(&stdout_vec).to_string(),
                stderr: String::from_utf8_lossy(&stderr_vec).to_string()
            }
        );
    } else {
        let time_elapsed = start.elapsed().as_millis();

        child.kill().unwrap();
        child.wait().unwrap();
        drop(child);

        
        let mut stdout_vec: Vec<u8> = Vec::new();
        let mut stderr_vec: Vec<u8> = Vec::new();

        stdout.read_to_end(&mut stdout_vec).expect("Unable to read stdout");
        stderr.read_to_end(&mut stderr_vec).expect("Unable to read stderr");

        return Ok(
            RunOutput {
                status: RunStatus::TimeOut,
                time: time_elapsed,
                stdout: String::from_utf8_lossy(&stdout_vec).to_string(),
                stderr: String::from_utf8_lossy(&stderr_vec).to_string()
            }
        );
    }
}

When the child exits before the timeout, everything works fine. However it seems there is in some cases a deadlock which is blocking the process if the command takes too much time. Moreover, it depends on the command too. When it is a node command, it always works, but when calling python and killing the child process it blocks the main thread in some cases.

From what I have seen for now, the process blocks when calling stdout.read_to_end(), but I don't know what could be the reason.

Edit: the blocking only occurs when the stdout is empty

0

There are 0 answers