Read from stdin and write to stdout of child in different threads


#1

Hello Rust users!

I’ve been migrating from C++ to rust starting last year and it is been a challenging but fun experience.

Now I’ve come across a problem that I couldn’t find many documentation / examples to help me solve it. I’m writing a simple gdb GUI front-end for debugging firmware for ARM microcontrollers, my program talks with gdb through the ‘mi’ protocol. This protocol uses a mixture of synchronous and asynchronous messages, so I need a dedicated thread to to read from the gdb stdout (blocking), parse the messages and store the data into a queue.

The problem I’m facing is that I need to write to the gdb stdin from the main thread (send commands to gdb), but once I move the stdout pipe to the reader thread the whole Command::new() object is moved and it is no longer accessible from the main thread (as expected). I couldn’t figure out a nice way to solve it.

Here is my code:

use std::process::{Command, Stdio};
use std::io::{BufRead, Write, BufReader};
use std::io::prelude::*;
use std::ffi::OsStr;
use std::thread;

pub fn start_gdb<I, S>(gdbexec: &str, args: I)
    where I: IntoIterator<Item=S>, S: AsRef<OsStr>
{
    let mut child = Command::new(gdbexec)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .args(args)
        .spawn()
        .expect("Failed to start gdb");

    
    thread::spawn(move || {
        let mut child_out = BufReader::new(child.stdout.as_mut().unwrap());
        let mut val = String::new();
        println!("New thread!");

        loop {
            child_out.read_line(&mut val).unwrap();
            println!("GDB out: {}", val);
            val.clear();
        }
        println!("Dead thread!");
    });

    // How to write to child stdin from here?
}

Have a nice day!


#2

You could get the RawFd of the child stdin before moving to the background thread. Then create a Stdin from that raw fd. This requires a bit of unsafe code and isn’t very satisfactory but should work.

Alternatively, could you make the background thread handle both sides of the communication? The main thread could send a message on a channel to the background thread, and it would write it to stdin. You’d need to arrange to not block indefinitely waiting on stdout but that should be doable.


#3

I though of using the same thread to handle the stdin and stdout of the child process, but it poses another question, how to wait for 2 different events (receive a message from the other thread and data to be read from the child stdout) ?

I don’t known if using select() would be a nice way to solve it.


#4

If you’re not averse to using tokio, you can see if tokio-process crate will make this easier (it should). You can then have a future chain that selects between the ChildStdout stream and a futures mpsc channel.


#5

I’ll take a look on it. If it doesn’t suit my needs I’ll try the unsafe trick.

Thanks!


#6

A common way to do this is to call take() on the stream that you want to use in one of the threads. That will give you ownership and leave the stream as None in the Child process struct.

Here’s an example: https://github.com/sunjay/turtle/blob/1476e1a3e32e87db74c770d149c055aed8660dad/src/renderer_process.rs#L33


#7

That did the trick. Thank you very much!