Search code examples
haskellsubprocesschild-processstmio-monad

Interacting with a subprocess while capturing stderr haskell


So I have a Haskell program that interacts with a subprocess using the System.Process.Typed library. I am trying to capture the stderr of the subprocess during the entire duration of the subprocess's lifespan. The current method doesn't work if the subprocess finishes before I get to line *. I think to do this I need to use STM but I don't know anything about STM and so was wondering if there was a simpler way.

fun :: MyType -> IO MyOtherType
fun inparam = withProcessWait config $ \process -> do
    hPutStrLn (getStdin process) (getStr1 inparam)
    hFlush (getStdin process)
    response1 <- hGetLine (getStdout process)

    hPutStrLn (getStdin process) (getStr2 inparam)
    hFlush (getStdin process)
    response2 <- hGetLine (getStdout process)

    err <- hGetContents (getStderr process) -- this is line *
    hClose (getStdin process)

    exitCode <- timedWaitExitCode 100 process
    return $ MyOtherType response1 response2 err
  where
    config =    setStdin createPipe
              $ setStdout createPipe
              $ setStderr createPipe
              $ fromString (fp inparam)

Thank you in advance.

Edit 1: Fixed the * label

Edit 2: When I try to run the code I get Exception: [..] hGetContents: illegal operation (delayed read on closed handle)


Solution

  • You did not specify what exactly “doesn’t work” in your code, so I’ll try to guess. One potential issue that I can immediately see is that you are returning values that you read from file handles (response1, response2, err) from your function. The problem here is that Haskell is a lazy language, so the values that you return are not actually read from those handles until they are really needed. And by the time they are needed, the child process has exited and the handles are closed, so it is impossible to read from them.

    The simplest fix would be to force those entire strings to be read before you “return” from your function. One standard recipe for this is to use force followed by evaluate. This will make your program actually read the values and remember them, so the handles can be closed.

    So, instead of:

    value <- hGetContents handle
    

    you should do:

    value' <- hGetContents handle
    value <- evaluate $ force value'