The following program
assumes that
/path/to/mypipeis a named pipe, e.g. created viamkfifo /path/to/mypipe, with no readers/writers waiting yet,runs two threads, of which
the main thread keeps printing
Update looponce a second,the other thread keeps blocking in read mode on said pipe via an external process forked via
readProcessWithExitCode, and prints the read text when the read succeeds, before blocking again.
Therefore, provided I've created the pipe with mkfifo, the program prints this
Update loop
Blocking-read from pipe
Update loop
Update loop
Update loop
Update loop
... goes on ...
(Whether Blocking-read from pipe is print before or after the first Update loop line is an implementation detail and could also change depending on whatever, I think.)
As soon as I write into the pipe, e.g. via echo -n ciao > /path/to/mypipe, I see this
... continuing from before ...
Update loop
Update loop
Read succeeded
success: ciao
Blocking-read from pipe
Update loop
Update loop
Update loop
... goes on ...
testifying that the "async" thread unblocks, prints, and blocks again.
Here's the current program:
import Control.Concurrent.Async (withAsync)
import Control.Monad
import GHC.IO.Exception (ExitCode(..))
import System.Process.Extra (readProcessWithExitCode)
import System.Time.Extra (sleep)
main :: IO ()
main = do
let pipe = "/path/to/mypipe"
withAsync (forever $ do print "Blocking-read from pipe"
(ret, out, _) <- readProcessWithExitCode "cat" [pipe] ""
print "Read succeeded"
case ret of
ExitSuccess -> print $ "success: " ++ out
ExitFailure _ -> error "how is this possible?")
(const $ forever $ print "Update loop" >> sleep 1)
The thing is, I would like to avoid the external process and do the reading in-process, via readFile-like actions. I've tried using withFileBlocking, like this:
import Control.Concurrent.Async (withAsync)
import Control.Monad
import GHC.IO.Handle.FD (withFileBlocking)
import System.IO (hGetContents)
import System.IO.Extra (IOMode(ReadMode))
import System.Time.Extra (sleep)
main :: IO ()
main = do
let pipe = "/home/enrico/deleteme/deleteme/mypipe"
withAsync (forever $ do putStrLn "Blocking-read from pipe"
out <- withFileBlocking pipe ReadMode hGetContents
putStrLn "Read succeeded"
putStrLn $ "success: " ++ out)
(const $ forever $ putStrLn "Update loop" >> sleep 1)
However, the behavior of this program is different, and I don't quite understand it:
as soon as I execute it, it prints the following
Update loop Blocking-read from pipeand sits there, which I don't understand given that the "main" thread is unchanged with respect to my original code; I don't see what could keep it from continuing the
foreveraction;then if I write something in the pipe, e.g. via the same command as above, I see this (I'm omitting the previous two lines)
Update loop Read succeeded Update loop Update loop Update loop Update loop ... goes on ...which also surpises me, because I don't see how this can "unblock" the main thread that has nothing to do with what happens to the pipe, nor I see what can go wrong with
putStrLn $ "success: " ++ outonce theout <- withFileBlocking pipe ReadMode hGetContentsis done;furthermore, if I try to write to the pipe (again, via
echo -n ciao > /path/to/mypipe), it blocks, revealing that even thoughputStrLn "Read succeeded"executed successfully,putStrLn $ "success: " ++ outmust have errored or blocked.
I've read the doc for withFileBlocking, and it does seem the right tool for what I want to do, at first. However, the doc for openFileBlocking (withFileBlocking, I read, opens the file just like openFileBlocking)
says that it is useful for opening a FIFO for writing, whereas am I am using it for reading; I'd assume that's just an example, but maybe I'm wrong;
and it has a note
Note: when blocking happens, an OS thread becomes tied up with the processing, so the program must have at least another OS thread if it wants to unblock itself.
which has probably something to do with the behavior I'm observing?
hGetContentsis lazy, so passing it as the action towithFileBlockingis a little worrying. I'd try withhGetContents'just to rule out that laziness as an issue, even though I don't see a specific way that it would lead to the behavior you see.-threadedflag and (2) create a bound thread eg withwithAsyncBoundinstead ofwithAsync.withAsyncBoundlink to docs forforkOSwhich seems to say that only your (1) is needed: "It is a common misconception that you need to use forkOS instead of forkIO to avoid blocking all the Haskell threads when making a foreign call; this isn't the case". Am I misunderstanding this note?