getChar + System.Cmd.system + threads causes hangups

Hello Using system or any variant of it from System.Process seems broken in multithreaded environments. This example will fail with and without -threaded. When run the program will print "hello: start" and then freeze. After pressing enter (the first getChar) System.Cmd.system will complete, but without that it will freeze for all eternity. What is the best way to fix this? I could use System.Posix, but that would lose windows portablity which is needed. import Control.Concurrent import System.Cmd main = do forkIO (threadDelay 100000 >> hello) getChar getChar hello = do putStrLn "hello: start" system "echo hello world!" putStrLn "hello: done" - Einar Karttunen

Here is a version that works fine: myRawSystem cmd args = do (inP, outP, errP, pid) <- runInteractiveProcess cmd args Nothing Nothing hClose inP os <- pGetContents outP es <- pGetContents errP ec <- waitForProcess pid case ec of ExitSuccess -> return () ExitFailure e -> do hPutStrLn stderr ("Running process "++unwords (cmd:args)++" FAILED ("++show e++")") hPutStrLn stderr os hPutStrLn stderr es hPutStrLn stderr ("Raising error...") fail "Running external command failed" pGetContents h = do mv <- newEmptyMVar let put [] = putMVar mv [] put xs = last xs `seq` putMVar mv xs forkIO (hGetContents h >>= put) takeMVar mv

Einar Karttunen wrote:
Hello
Using system or any variant of it from System.Process seems broken in multithreaded environments. This example will fail with and without -threaded.
When run the program will print "hello: start" and then freeze. After pressing enter (the first getChar) System.Cmd.system will complete, but without that it will freeze for all eternity.
What is the best way to fix this? I could use System.Posix, but that would lose windows portablity which is needed.
import Control.Concurrent import System.Cmd
main = do forkIO (threadDelay 100000 >> hello) getChar getChar
hello = do putStrLn "hello: start" system "echo hello world!" putStrLn "hello: done"
The reason for the deadlock is because getChar is holding a lock on stdin, and System.Cmd.system needs to access the stdin Handle in order to know which file descriptor to dup as stdin in the child process (the stdin Handle isn't always FD 0, because of hDuplicateTo). Maybe getChar shouldn't hold the lock while it is waiting. I was vaguely aware of this when I wrote System.IO, but couldn't see an easy way to implement it, so currently all operations that block in I/O hold the Handle lock while they block. Mostly this isn't a problem, but it does mean that things like hClose will block if there's another thread blocked in hGetChar on the same Handle (maybe you want it to cause the hGetChar to immediately fail instead). One way to work around it in this case is to hDuplicate the standard Handles, and call runProcess passing your duplicate Handles. I've just checked; this works fine. import GHC.Handle (hDuplicate) main = do i <- hDuplicate stdin o <- hDuplicate stdout e <- hDuplicate stderr forkIO (threadDelay 100000 >> hello i o e) getChar getChar hello i o e = do putStrLn "hello: start" p <- runProcess "echo" ["hello world!"] Nothing Nothing (Just i) (Just o) (Just e) waitForProcess p putStrLn "hello: done" Cheers, Simon

On Tue, 21 Feb 2006, Simon Marlow wrote: ...
The reason for the deadlock is because getChar is holding a lock on stdin, and System.Cmd.system needs to access the stdin Handle in order to know which file descriptor to dup as stdin in the child process (the stdin Handle isn't always FD 0, because of hDuplicateTo).
I was puzzled by this; from a quick review of the source, it seems that this is because system calls runProcessPosix, which has optional arguments for (stdin, stdout, stderr) for the child process, and when value is Nothing the parent's stdin etc. are used instead. That last part doesn't seem right to me, so if it's awkward to implement, I hope you will consider the possibility that no one needs it. The default value for fd 0 should be fd 0. A process that intends to change the default input stream on this level where the same stream should be inherited by children, can open or dup another file onto fd 0. Conversely, if a program contrives to change stdin to something besides fd 0, I would have assumed the intent was to avoid any affect on child processes. Donn Cave, donn@drizzle.com

Donn Cave wrote:
On Tue, 21 Feb 2006, Simon Marlow wrote: ....
The reason for the deadlock is because getChar is holding a lock on stdin, and System.Cmd.system needs to access the stdin Handle in order to know which file descriptor to dup as stdin in the child process (the stdin Handle isn't always FD 0, because of hDuplicateTo).
I was puzzled by this; from a quick review of the source, it seems that this is because system calls runProcessPosix, which has optional arguments for (stdin, stdout, stderr) for the child process, and when value is Nothing the parent's stdin etc. are used instead.
That last part doesn't seem right to me, so if it's awkward to implement, I hope you will consider the possibility that no one needs it. The default value for fd 0 should be fd 0. A process that intends to change the default input stream on this level where the same stream should be inherited by children, can open or dup another file onto fd 0. Conversely, if a program contrives to change stdin to something besides fd 0, I would have assumed the intent was to avoid any affect on child processes.
That's certainly a reasonable point of view. Currently the semantics are such that you can say h <- openFile "out" WriteMode hDuplicateTo h stdout system "echo \"hello\"" and the output from the echo command will go to the file "out". I'm perfectly willing to accept that supporting this behaviour is of limited usefulness, and even if you need it then you can call system the long way using runProcess. Anyone else have any opinions on this? Cheers, Simon
participants (3)
-
Donn Cave
-
Einar Karttunen
-
Simon Marlow