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.
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.