[commit: packages/process] master: Be even more careful with the threads that consume output (#2233) (d95a670)
git at git.haskell.org
git at git.haskell.org
Sat Nov 30 23:03:37 UTC 2013
Repository : ssh://git@git.haskell.org/process
On branch : master
Link : http://ghc.haskell.org/trac/ghc/changeset/d95a67012ea30b02144d193254d947cdf5400a9d/process
>---------------------------------------------------------------
commit d95a67012ea30b02144d193254d947cdf5400a9d
Author: Duncan Coutts <duncan at community.haskell.org>
Date: Tue Nov 26 00:04:41 2013 +0000
Be even more careful with the threads that consume output (#2233)
There is a potential deadlock with withCreateProcess in the case that
there's an exception: cleanupProcess will try to hClose the various
handles, but if another thread holds the Handle lock then that hClose
will block.
Takano Akio fixed the main case of this (in patch
32223a9ab174c22e939c81e24b6f48223c7cb020) by terminating the process
(before closing the handles) This works because terminating the process
will eventually cause those other threads to finish and release the
Handle lock, so we can hClose.
However on Unix terminateProcess is not guaranteed to terminate the
process since it uses SIGTERM, which can be handled or ignored. So we
have to separately guarantee that the handles can be hClosed, and the
simplest way to do this is to ensure that the thread reading from the
handles get killed in the case there's an exception.
So we change forkWait to withForkWait that will kill off the thread if
the body gets an exception.
Authored-by: Duncan Coutts <duncan at well-typed.com>
Signed-off-by: Herbert Valerio Riedel <hvr at gnu.org>
>---------------------------------------------------------------
d95a67012ea30b02144d193254d947cdf5400a9d
System/Process.hs | 66 ++++++++++++++++++++++++++++++-----------------------
1 file changed, 37 insertions(+), 29 deletions(-)
diff --git a/System/Process.hs b/System/Process.hs
index 5f4e7d1..581ef71 100644
--- a/System/Process.hs
+++ b/System/Process.hs
@@ -423,17 +423,17 @@ readProcess cmd args input = do
-- fork off a thread to start consuming the output
output <- hGetContents outh
- waitOut <- forkWait $ C.evaluate $ rnf output
+ withForkWait (C.evaluate $ rnf output) $ \waitOut -> do
- -- now write and flush any input
- unless (null input) $ do
- ignoreSigPipe $ hPutStr inh input
- hFlush inh
- hClose inh -- done with stdin
+ -- now write and flush any input
+ unless (null input) $ do
+ ignoreSigPipe $ hPutStr inh input
+ hFlush inh
+ hClose inh -- done with stdin
- -- wait on the output
- waitOut
- hClose outh
+ -- wait on the output
+ waitOut
+ hClose outh
-- wait on the process
ex <- waitForProcess ph
@@ -477,37 +477,45 @@ readProcessWithExitCode cmd args input = do
withCreateProcess_ "readProcessWithExitCode" cp_opts $
\(Just inh) (Just outh) (Just errh) ph -> do
- -- fork off a thread to start consuming stdout
out <- hGetContents outh
- waitOut <- forkWait $ C.evaluate $ rnf out
-
- -- fork off a thread to start consuming stderr
err <- hGetContents errh
- waitErr <- forkWait $ C.evaluate $ rnf err
- -- now write and flush any input
- unless (null input) $ do
- ignoreSigPipe $ hPutStr inh input
- hFlush inh
- hClose inh
+ -- fork off threads to start consuming stdout & stderr
+ withForkWait (C.evaluate $ rnf out) $ \waitOut ->
+ withForkWait (C.evaluate $ rnf err) $ \waitErr -> do
+
+ -- now write and flush any input
+ unless (null input) $ do
+ ignoreSigPipe $ hPutStr inh input
+ hFlush inh
+ hClose inh
- -- wait on the output
- waitOut
- waitErr
+ -- wait on the output
+ waitOut
+ waitErr
- hClose outh
- hClose errh
+ hClose outh
+ hClose errh
-- wait on the process
ex <- waitForProcess ph
return (ex, out, err)
-forkWait :: IO a -> IO (IO a)
-forkWait a = do
- res <- newEmptyMVar
- _ <- mask $ \restore -> forkIO $ try (restore a) >>= putMVar res
- return (takeMVar res >>= either (\ex -> throwIO (ex :: SomeException)) return)
+-- | Fork a thread while doing something else, but kill it if there's an
+-- exception.
+--
+-- This is important in the cases above because we want to kill the thread
+-- that is holding the Handle lock, because when we clean up the process we
+-- try to close that handle, which could otherwise deadlock.
+--
+withForkWait :: IO () -> (IO () -> IO a) -> IO a
+withForkWait async body = do
+ waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
+ mask $ \restore -> do
+ tid <- forkIO $ try (restore async) >>= putMVar waitVar
+ let wait = takeMVar waitVar >>= either throwIO return
+ restore (body wait) `C.onException` killThread tid
ignoreSigPipe :: IO () -> IO ()
#if defined(__GLASGOW_HASKELL__)
More information about the ghc-commits
mailing list