rustnamed-pipes

data only written to named pipe after program quits


I am trying to write a program in Rust that writes a string to a named pipe. However, the data only shows up on the receiving end after the program quits.

Here's my code:

use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use std::io::Write;
use std::fs::{File, OpenOptions};

fn send_data(file: &mut File, payload: &str) -> Result<(), std::io::Error> {
    file.write(payload.as_bytes()).unwrap();
    // I tried file.write_all() and file.flush(); same result
    Ok(())
}

fn main() {
    let mut file = OpenOptions::new()
        .write(true)
        .open("/tmp/my_named_pipe")
        .expect("Failed to open named pipe for writing");

    let mut stdout = std::io::stdout().into_raw_mode().unwrap();
    stdout.flush().unwrap();

    loop {
        let key = match std::io::stdin().keys().next() {
            Some(Ok(input)) => input,
            _ => break,
        };
        match key { // press x to send data, q to quit
            Key::Char('q') => break,
            Key::Char('x') => send_data(&mut file, "foobar").unwrap(),
            _ => (),
        }
    }
}

I'm creating a named pipe with mkfifo /tmp/my_named_pipe and start listening to it with tail -f /tmp/my_named_pipe. Then, in a different terminal window, I run my program (and press 'x' several times). The 'tail -f' doesn't show anything. When I quit my program (by pressing 'q') all strings show all at once.

I'd expect every key press to call send_data() and every call to render on the tail -f side immediately. How can this be accomplished?


Solution

  • As suggested, here is my comment slightly developed in an answer.

    The problem does not stand on the Rust side, but on the tail -f side.
    After creating the pipe with mkfifo /tmp/my_named_pipe, we can start a producer with:

    ( while true; do echo -n X ; sleep 1 ; done ) >/tmp/my_named_pipe
    

    This writes a single X, without new-line, into the pipe, every second.

    In another terminal, trying to consume the pipe with tail -f /tmp/my_named_pipe does not show anything till the producer is stopped.
    This is the problem reported in the question.
    If we replace echo -n X with echo X (i.e. we generate a new-line each time), in the producer, this does not change anything in the consumer.
    Then, the problem is not related to tail waiting for a line before outputting anything; it's probably due to the way it watches a pipe (I did not take the time to study the source code).

    However, trying to consume the pipe with dd if=/tmp/my_named_pipe bs=1 or simply cat /tmp/my_named_pipe shows an X every second, even without new-line.
    This confirms that the problem is due to tail.