haskellprocesstyped-process

typed-process withProcessWait_ and getExitCode race condition?


import System.Process.Typed
import Control.Monad.STM
import Control.Concurrent.STM.TVar

processConf = setStderr byteStringOutput . setStdout byteStringOutput

main :: IO ()
main = do
  withProcessWait_ (processConf $ proc "sleep" ["1"])
          $ \p -> do
            atomically (getStdout p) >>= print
            atomically (getStderr p) >>= print
            getExitCode p >>= print
  print "test"

The above code mostly returns Nothing for the exit code, while other times it'll return Just ExitSuccess, so seemingly random / race condition.

Why might this occur?

The function in question is: http://hackage.haskell.org/package/typed-process-0.2.6.0/docs/System-Process-Typed.html#v:withProcessWait_

withProcessWait_ :: MonadUnliftIO m => ProcessConfig stdin stdout stderr -> (Process stdin stdout stderr -> m a) -> m a

My understanding of what the function will do is, run the process, wait till it's terminated and then run the IO a. However... I just noticed there is a readProcess function which sounds like what I should actually be using instead http://hackage.haskell.org/package/typed-process-0.2.6.0/docs/System-Process-Typed.html#v:readProcess

None the less, it would be useful to know what is actually happening in the above / withProcessWait_.


Solution

  • The race condition is that three separate threads are consuming all available standard output (first thread) and standard error (second thread) and waiting on the exit code (third thread). The standard output and standard error thread can complete and publish the full output to STM before the third thread publishes the exit code.

    Since getExitCode checks the exit code TMVar but doesn't wait on it, the exit code may not be available. (In fact, if the child process closes standard output and error before exiting, it may genuinely still be running at this point!)

    If you replace getExitCode with waitExitCode, you reliably get ExitSuccess. Of course, withProcessWait_ is already waiting on the exit code and will raise an exception if it's not ExitSuccess, so there's no particular reason to do this anyway.