I need to run a process, do something while it is running, and finally terminate it. The
process in question writes things to standard output that I would like to retain. Unfortunately,
it seems that the process dies before I can connect and extract its last words. Having scarce
experience with asynchronous programming, I am having a difficulty finding a nice solution. It
would be fortunate if I can accomplish this task within the framework of RIO.Process
, although I
am prepared to step outside of it if it cannot be avoided. (Note that RIO
employs an unusual
way of invoking external processes via a callback system.)
Below is a highly simplified runnable example of what I am trying to achieve.
Here is an emulation of the program to be run:
(Put it in a file called x.sh
and say chmod +x x.sh
to make it executable.)
#!/bin/sh
trap 'echo "Terminating..."; exit 0' TERM
echo "Initialization complete."
while true; do sleep 1; done
Here is my code:
(Put it in a file called X.hs
and compile with ghc -package rio X.hs
.)
{-# language NoImplicitPrelude #-}
{-# language BlockArguments #-}
{-# language OverloadedStrings #-}
module Main where
import RIO
import RIO.Process
import Data.Text.IO (hGetContents, hGetLine)
main :: IO ()
main = runSimpleApp do
proc "./x.sh" [ ]
\processConfig -> withProcessWait_ (setStdout createPipe processConfig)
\processHandle -> bracket_
(initialize processHandle)
(terminate processHandle)
(return ())
initialize :: (HasProcessContext env, HasLogFunc env) => Process () Handle () -> RIO env ()
initialize processHandle = do
x <- liftIO $ hGetLine (getStdout processHandle)
if x == "Initialization complete." then return () else error "This should not happen."
terminate :: HasLogFunc env => Process () Handle () -> RIO env ()
terminate processHandle = do
log' <- async $ liftIO $ hGetContents (getStdout processHandle)
stopProcess processHandle
log <- wait log'
logInfo $ display log
Here is what happens:
% ./X
X: fd:3: hGetBuffering: illegal operation (handle is closed)
— x.sh
is saying something, but I cannot hear.
What is the right way to manage this?
From the documentation for
stopProcess
:(emphasis mine) You don't want
stopProcess
to do that before you read the output. You just wantterminateProcess
.withProcessWait_
will take care of the rest of it. Unfortuntately, you do have to step outside ofRIO
to do that, withimport System.Process (terminateProcess)
and thenliftIO $ terminateProcess (unsafeProcessHandle processHandle)
.Side notes: You're kind of misusing
bracket_
. Since the "middle" of yourbracket_
is a no-op, and especially now that the beginning and end aren't actually acquiring or releasing any resources, it's kind of pointless. Also, instead of usingasync
at all, you can just read the output normally after terminating the process, since the output that a process already produced doesn't just disappear when it's terminated.Here's your code with all of the above fixed: