3

If in a terminal I enter

mkfifo /tmp/pipe
echo hello > /tmp/pipe

(which blocks) and in another I run the haskell program

main = readFile "/tmp/foobar" >>= putStr

then I see it printing hello.

However, if I redo the experiment without echoing anything to the pipe, then the Haskell program will print the the empty string and return, rather than blocking on the pipe.

Why is that? And do I have something other than readFile that would block when reading from a pipe in which no write has happened yet?


I know I can do readProcessWithExitCode "cat" ["/tmp/foobar"] "", but that spawns a process, but I'm asking precisely because I want to avoid that.

2 Answers 2

7

Per the fifo(7) and pipe(7) manpages, when a fifo is opened for reading in (default) blocking mode, the open call will block until a writer also opens the fifo (and vice versa). Reading from a fifo opened in blocking mode will block until data becomes available (which will return the data) or there are no writers who have the fifo open (which will return EOF). So, if you use cat, which opens the fifo in blocking mode, it will block until the writer opens the fifo, and will read data until the writer closes the file and the cat process gets an EOF.

The Haskell readFile function, like the openFile function, opens a file in non-blocking mode. For a fifo, this means that the open call returns immediately. Reading from a fifo opened in non-blocking mode will return an EAGAIN "error" if there is no data but there are writers holding the fifo open, and it will return EOF if there are no writers holding the fifo open. When Haskell reads from the fifo (or any file), it does so using normal non-blocking I/O semantics, so if there are writers available and it gets an EAGAIN error, it knows to use poll for more data. But, if it reads from a fifo with no writers available, it gets an EOF and -- in the case of readFile -- returns an empty string.

GHC has openFileBlocking and withFileBlocking functions that you can use:

import System.IO
import GHC.IO.Handle.FD
main = openFileBlocking "/tmp/foo" ReadMode >>= hGetContents >>= putStr

This version will block on the open call and wait for the writer to join.

Sign up to request clarification or add additional context in comments.

2 Comments

Hi, sorry for the late reply. I've tried blindly changing (ret, out, err) <- readProcessWithExitCode "cat" [pipe] "" to out <- withFileBlocking pipe ReadMode hGetContents and I don't quite get the same behavior, but this happens in the context of an actual application, so I need some time to understand what's actually going on and distil an example demonstrating the difference in isolation.
I found a bit hard to include more deatails here in the comments or in the question without altering it all, so I've asked a separate question.
1

This is a property of the FIFO, not of readFile. If there are no open writers, reading to the end of the file will return immediately instead of blocking. This is so that a FIFO cooperates better with programs that only know how to deal with ordinary files.

If you want to block anyway, I believe you need to do what e.g. tail --follow does: wait for changes by polling (fstat) or listening (inotify), and reopen the input as needed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.