[Haskell-cafe] System.Process and -threaded

Donn Cave donn at avvanta.com
Sun May 18 16:14:39 UTC 2014

> Try forking a couple of threads to keep those handles clear.

Well ... at what point do these conveniences become too much work?

Quick outline of the problems we can expect to encounter if we use

-- because of Handles for the pipe I/O --
1.  library buffering defaults are the opposite of useful
2.  now we find the depending on -threaded, garbage collection
    may cause handles to be closed
-- because all three standard units are redirected to pipes --
3.  handles are likely to be unused, contributing to problem 2 above
4.  unused handles are also liable to fill with process output and block.
5.  particularly with unit 2 ("stderr"), diagnostic output will be
    discarded unless copied to output by the parent process
6.  it's tricky to handle output on two units - have to avoid blocking
    read, if you don't know there's output.

... and I likely am forgetting one or two more.

My recommendation would be to never use this function, and instead
go to a process/pipe/exec that
1.  creates only the required pipe connections, usually one.
2.  returns the pipe Fd rather than making it into a Handle

I append an example implementation - a little crude inasmuch as
it does nothing about potential exceptional conditions like
exec failure.

module Spawn (spawnFd) where
import System.Directory
import System.Posix.IO
import System.Posix.Process
import System.Posix.Types

--  fork process, exec file
--  exec parameters same as executeFile
--  Fd spec is (unit, write) from the perspective of
--  the child process.
--    To spawn a process that only writes output, and
--    get a pipe to read that output: [(1, True)]
--    To spawn a process that only reads input: [(0, False)]
--    To get all three units like runInteractiveProcess:
--         [(0, False), (1, True), (2, True)]
--  Returned fds are not super convenient, something like
--    let p0 = fromJust $ lookup 0 pipelist
spawnFd :: FilePath -> Bool -> [String] -> Maybe FilePath
  -> Maybe [(String, String)] -> [(Fd, Bool)]
  -> IO ([(Fd, Fd)], ProcessID)
spawnFd path search args wd env fdreq = do
    pp <- mapM pipu fdreq
    pid <- forkProcess $ childPrep pp
    ppp <- mapM repipe pp
    return (ppp, pid)
        pipu (u, w) = do
            p <- createPipe
            return (u, w, p)
        dopipe (u, w, (p0, p1)) = do
            if w
                then dupTo p1 u
                else dupTo p0 u
            closeFd p0
            closeFd p1
        repipe (u, w, (p0, p1))
            | w = do
                closeFd p1
                return (u, p0)
            | otherwise = do
                closeFd p0
                return (u, p1)
        childPrep pp = do
            mapM_ dopipe pp
            case wd of
                Just d -> setCurrentDirectory d
                _ -> return ()
            executeFile path search args env

More information about the Haskell-Cafe mailing list