For version 2999.7.0.0 of my graphviz library, one of the improvements I’ve made was to re-write how to call the actual Graphviz command so that error messages could actually be reported to the user and to make it more robust (compare the old version to the new one). Duncan Coutts helped me out with this, by giving me a starting point from Cabals’ rawSystemStdout'
function.
So I wrote the code, recorded a Darcs patch, then a few days later decided to actually test it (for some reason I didn’t seem to have actually tried using the function after writing it…). But whenever I tried to use it, I kept finding that the call to Graphviz would block: the input would seem to get consumed, but no output was generated. Duncan and I spent a few hours putting trace statements, etc. throughout before we eventually worked out what the problem was.
My challenge: without looking at the released version of the code that I’ve linked to above, try to find the problem part of the code below. There’s a crucial thing to remember here whenever making a system call to another executable. I’ve cleaned up and simplified the actual code I’ve put here, but otherwise its exactly what I had when I tried to work out why it wasn’t working. Note that hGetContents'
is a strict version of hGetContents
-- Extract the output result from calling the given Graphviz command with the given Graphviz output type. -- If the function call wasn't a success, return the error message. graphvizWithHandle :: (PrintDot n) => GraphvizCommand -> DotGraph n -> GraphvizOutput -> IO (Either String String) graphvizWithHandle cmd gr t = handle notRunnable $ bracket (runInteractiveProcess cmd' args Nothing Nothing) (\(inh,outh,errh,_) -> hClose inh >> hClose outh >> hClose errh) $ \(inp,outp,errp,prc) -> do -- The input and error are text, not binary hSetBinaryMode inp False hSetBinaryMode errp False hSetBinaryMode outp $ isBinary t -- Depends on output type forkIO $ hPutStr inp (printDotGraph gr) -- Need to make sure both the output and error handles are -- really fully consumed. mvOutput <- newEmptyMVar mvErr <- newEmptyMVar _ <- forkIO $ signalWhenDone hGetContents' errp mvErr _ <- forkIO $ signalWhenDone hGetContents' outp mvOutput -- When these are both able to be taken, then the forks are finished err <- takeMVar mvErr output <- takeMVar mvOutput case exitCode of ExitSuccess -> return output _ -> return $ Left $ othErr ++ err where notRunnable e@SomeException{} = return . Left $ unwords [ "Unable to call the Graphviz command " , cmd' , " with the arguments: " , unwords args , " because of: " , show e ] cmd' = showCmd cmd args = ["-T" ++ outputCall t] othErr = "Error messages from " ++ cmd' ++ ":\n"
If you can work out what the problem is, reply with a comment below. The first person to spot it gets absolutely nothing except the knowledge that they spotted it first… >_>
On a guess, you use the bracket to close stdin, and dot won’t close stdout or stderr until stdin closes (you can give dot multiple graphs one after another). Stdin/stdout not closing means your signal mvars never get written, and thus you end up blocking…?
Close: dot won’t start doing anything until the stdin handle closes; as such, it just hangs waiting for an EOF that never comes :s
(What makes it even more fun is that I had to keep closing ghci to kill off the zombie processes that it spawned…)
No, dot output stuffs after the first digraph/graph {} is closed, (it may well be using line buffering so it looks like you need to eof when you actually need to newline). Either way, you block on stdout being closed (strict hGetContents), which it doesn’t close until stdin closes (and there is no more work to do).
try:
$ cat | dot
digraph {
}
digraph {
}
dot will output something back, after the first , but leave stdin open for you to enter more stuff…
T
Hmmm…. maybe…
From memory, the reason I need to have strict stdout is to ensure dot fully finishes, so I didn’t think of it as being a problem with the stdin and stdout “clashing” in a sense.