1

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?

6
  • 2
    My implementation of tail -f does not print incomplete lines. You can see the same behavior if you replace your program with { printf x; sleep 1; echo; } You need something that reads the fifo without waiting for complete lines. Commented Apr 1 at 11:59
  • @WilliamPursell adding newlines to my payload doesn't change anything though. And writing echo "foobar" > /tmp/my_named_pipe in a different terminal does exactly what I want my rust program to do.
    – Kolja
    Commented Apr 1 at 13:22
  • You need flush. tail needs \n (see earlier comment). So include in program. Then test, then post new tested program in question. Commented Apr 1 at 14:17
  • 1
    The problem comes from tail -f not properly watching the pipe. cat /tmp/my_named_pipe works perfectly even without \n.
    – prog-fh
    Commented Apr 1 at 19:43
  • @prog-fh That's it! Post your comment as an answer and I'll mark it as correct (still curious why tail -f is misbehaving...)
    – Kolja
    Commented Apr 1 at 21:18

1 Answer 1

1

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.