Command Input/Output and blocking

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
      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… >_>


4 thoughts on “Command Input/Output and blocking

  1. ToRA says:

    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…?

    • Ivan Miljenovic says:

      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…)

      • ToRA says:

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

        $ cat | dot
        digraph {
        digraph {

        dot will output something back, after the first , but leave stdin open for you to enter more stuff…


        • Ivan Miljenovic says:

          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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s