Problems to communicate with subprocess using stdin/stdout

Hi,
I'm a little stuck with some example application:
I want to start another rust executable in a subprocess and communicate with it using stdin/stdout.
basically just a small echo server...where I send some command to the subprocess, do some work, let the subprocess send a response back a.s.o.
In my current setup, I have problems both on the "server" part and the "client" part. Would appreciate some tips :wink:
I pasted the full code, project can be found here: https://github.com/marcmo/process_client and https://github.com/marcmo/process_server

➜  process_server git:(master) ✗ cargo run
   Compiling process_server v0.1.0 (/Users/muellero/dev/git/process_server)
    Finished dev [unoptimized + debuginfo] target(s) in 0.69s
     Running `target/debug/process_server`
SERVER DEBUG - Hello, world!
SERVER DEBUG - sending command: test
SERVER DEBUG - got communicator
CLIENT DEBUG - process_client args: ["../process_client/target/debug/process_client", "arg1", "arg2"]
CLIENT DEBUG - [0] received line: "test"
CLIENT DEBUG - [1] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [2] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [3] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [4] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [5] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [6] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [7] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [8] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [9] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - [10] received line: ""
CLIENT DEBUG - stdin input had len 0...
CLIENT DEBUG - exiting after 10 retries
SERVER DEBUG - called communicate
SERVER DEBUG - SERVER out: response
response
response
response
response
response
response
response
response
response

SERVER DEBUG - child process exited with: Exited(0)

this is what the server-part looks like:

use env_logger::{Builder, Target};
use log::*;
use std::io::Write;
use subprocess::{Popen, PopenConfig, Redirection};

#[cfg(target_os = "windows")]
static CLIENT_PATH: &str = "..\\process_client\\target\\debug\\process_client.exe";

#[cfg(not(target_os = "windows"))]
static CLIENT_PATH: &str = "../process_client/target/debug/process_client";
fn main() -> Result<(), std::io::Error> {
    let mut builder = Builder::new();
    builder
        .format(|buf, record| writeln!(buf, "SERVER {} - {}", record.level(), record.args()))
        .target(Target::Stderr)
        .filter(None, LevelFilter::Debug)
        .init();

    debug!("Hello, world!");

    let mut p = Popen::create(
        &[CLIENT_PATH, "arg1", "arg2"],
        PopenConfig {
            stdout: Redirection::Pipe,
            stdin: Redirection::Pipe,
            ..Default::default()
        },
    )
    .unwrap();
    let commands = vec!["test", "exit"];
    for command in commands {
        debug!("sending command: {}", command);
        let mut communicator = p.communicate_start(Some(command.as_bytes().to_vec()));
        debug!("got communicator");
        if let Ok((out, err)) = communicator.read() {
            debug!("called communicate");
            if let Some(out) = out {
                debug!("SERVER out: {}", String::from_utf8_lossy(&out));
            }
            if let Some(err) = err {
                debug!("SERVER err: {}", String::from_utf8_lossy(&err));
            }
        }
        if let Some(exit_status) = p.poll() {
            debug!("child process exited with: {:?}", exit_status);
            break;
        }
    }
    p.terminate()
}

and this is the client part:

use env_logger::{Builder, Target};
use log::*;
use std::env;
use std::io;
use std::io::{Read, Write};
use std::thread;
use std::time::Duration;

fn main() -> Result<(), io::Error> {
    let mut builder = Builder::new();
    builder
        .format(|buf, record| writeln!(buf, "CLIENT {} - {}", record.level(), record.args()))
        .target(Target::Stderr)
        .filter(None, LevelFilter::Debug)
        .init();
    let args: Vec<String> = env::args().collect();
    debug!("process_client args: {:?}", args);
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    let mut line = String::new();

    let mut count = 0usize;
    loop {
        let n = stdin.read_line(&mut line).expect("read_to_string failed");

        debug!("[{}] received line: \"{}\"", count, line);
        if line == "exit" {
            debug!("exiting!");
            break;
        }
        if n == 0 {
            debug!("stdin input had len 0...");
            if count >= 10 {
                debug!("exiting after {} retries", count);
                break;
            }
        }

        // Write the line to stdout.
        stdout
            .write_all(b"response\n")
            .expect("writing to stdout failed");
        stdout.flush().unwrap();
        line.clear();
        count += 1;
        thread::sleep(Duration::from_millis(500));
    }
    Ok(())
}

It seems like the issue is that after the child process exited, you go around the loop again and try to call communicate on an exited process.

1 Like

Hi Alice, you are right of course...communicating with an exited process cannot work. I updated my example code.
what I still don't get is how it is possible to interact with the subprocess (multiple reads/writes).
this blocks until the subprocess closes stdin/stdout:

if let Ok((out, err)) = communicator.read() {

Well the documentation of Communicator::read says quite explicitly that it'll continue until EOF which means until the subprocess closes the pipe, so you can't use that function for that.

The Popen struct has stdin and stdout as File objects. I supposed you could use the standard IO tools to read and write to those?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.