Need to send input to process numerous times without process being closed

I want to use Rust to create a specialized chess analysis program that will interact with one of the best existing chess programs in the world (Stockfish) as a subprocess. I could need to communicate with that sub-process about 100 times per chess game I want to analyze. Based on the following I was able to successfully communicate with the Stockfish sub-process once: Pipes - Rust By Example

I changed the command as necessary and then sent the input "uci" and got a couple of dozens of lines of output back just like I would on the command line:

id name Stockfish 11 64
id author T. Romstad, M. Costalba, J. Kiiski, G. Linscott
....
....
option name SyzygyProbeDepth type spin default 1 min 1 max 100
option name Syzygy50MoveRule type check default true

Now I would like to send more input to the pipe, next specifically "isready" (to which the response should be "readyok"). However the comments in the code I'm emulating specifically say "stdin does not live after the above calls...the pipe is closed".

I did find the following on stackoverflow command - Unable to pipe to or from spawned child process more than once - Stack Overflow and tried modifiying it to my purpose albeit without success: My idea is to put what I want to send to stdin into an array (or I could change it to vector) and then loop over that. I'm starting with just the 4 data I want to initially pass to the process, there could potentially be dozens more.

use std::process::{Command, Stdio};
use std::io::{BufRead, Write, BufReader};

fn main() {
    const INIT_INPUT: [&'static str; ] = ["uci", "isready", "position startpos", "go infinite"];
    let mut child_shell = Command::new("opt/stockfish_20011801_x64")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let child_in = child_shell.stdin.as_mut().unwrap();
    let mut child_out = BufReader::new(child_shell.stdout.as_mut().unwrap());
    let mut line = String::new();

    for process_input in INIT_INPUT.iter() {
        println!("{}", process_input);
        // child_in.write(process_input.as_bytes()).unwrap();
        // while child_out.read_line(&mut line).unwrap() != 0 {
        //    println!("{}", line);
        // }
    }
}

As it stands, with the input I want to send commented out, it performs as expected, printing to the console the 4 lines of input specified in the array. But if you comment that code in, I get back just one line of output and it does not proceed to the input that I want to send to the pipe.

The stackoverflow Q/A is almost 5 years old, I wonder if there is a more up-to-date way of doing things. Thanks in advance.

Using pipes is roughly the right approach.

You have to be careful about creating deadlocks! You may be waiting until you can write to child's stdin, but the child process won't read it, because it may be waiting for you to read child's stdout. I/O is buffered, so this hides such bugs for small amounts of data, but as soon as some buffer is full and blocking, you can get a deadlock.

So it's best to have a separate thread that always reads stdout from the child process. Use .take() to move stdout handle to another thread, and mpsc to coordinate between your threads.

Well this will be my first non-trivial Rust solution and I'd like to keep it as simple as possible, rather than needing to incorporate even more concepts. The program will be strictly for myself to begin with, so I'm wondering if it might be possible to just add wait()'s at strategic places to try to avoid the deadlocks you mention. Long story short though, I've got a lot of experience researching programming related issues, and there just does not seem to be a canonical approach to what to me wouldn't seem to be altogether too uncommon.

Adding some sort of wait() is not enough to avoid the deadlocks. The deadlocks are caused by the fact that if process A writes something to process B, and process B doesn't that data, then A is blocked. This means that if they both try to write to each other simultaneously, you have a deadlock.

To avoid them you have to ensure you fully read everything the other side wants to send. You should also make sure to flush() your writes, as the other side might not otherwise get everything you wrote, resulting in a sleep on a read. Finally when data is written in both directions, you would have to decide which process writes first, and which reads first (or do both in separate threads as @kornel suggested).

So I have found a library that could well help me out, on github: GitHub - vampirc/vampirc-uci: A Universal Chess Interface (UCI) protocol parser and message generator. and with a separate website at: https://vampirc.kejzar.si/ The author of the code is from Slovenia, as is the chess Grandmaster whose games for which I want to automate the analysis, so it seems the perfect combination! Thanks everybody for their interest and responses

1 Like

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