[Git][ghc/ghc][master] SysTools: Avoid race conditions when processing output (fixes #16450)

Marge Bot (@marge-bot) gitlab at gitlab.haskell.org
Tue Dec 3 22:12:47 UTC 2024



Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC


Commits:
f98b3ac0 by Simon Hengel at 2024-12-03T17:11:30-05:00
SysTools: Avoid race conditions when processing output (fixes #16450)

- - - - -


1 changed file:

- compiler/GHC/SysTools/Process.hs


Changes:

=====================================
compiler/GHC/SysTools/Process.hs
=====================================
@@ -22,6 +22,14 @@ import GHC.Utils.CliOption
 import GHC.Types.SrcLoc ( SrcLoc, mkSrcLoc, mkSrcSpan )
 import GHC.Data.FastString
 
+import GHC.IO.Encoding
+
+#if defined(__IO_MANAGER_WINIO__)
+import GHC.IO.SubSystem ((<!>))
+import GHC.IO.Handle.Windows (handleToHANDLE)
+import GHC.Event.Windows (associateHandle')
+#endif
+
 import Control.Concurrent
 import Data.Char
 
@@ -222,11 +230,21 @@ handleProc pgm phase_name proc = do
 
     does_not_exist = throwGhcExceptionIO (InstallationError ("could not execute: " ++ pgm))
 
+withPipe :: ((Handle, Handle) -> IO a) -> IO a
+withPipe = bracket createPipe $ \ (readEnd, writeEnd) -> do
+  hClose readEnd
+  hClose writeEnd
 
 builderMainLoop :: Logger -> (String -> String) -> FilePath
                 -> [String] -> Maybe FilePath -> Maybe [(String, String)]
                 -> IO ExitCode
-builderMainLoop logger filter_fn pgm real_args mb_cwd mb_env = do
+builderMainLoop logger filter_fn pgm real_args mb_cwd mb_env = withPipe $ \ (readEnd, writeEnd) -> do
+
+#if defined(__IO_MANAGER_WINIO__)
+  return () <!> do
+    associateHandle' =<< handleToHANDLE readEnd
+#endif
+
   chan <- newChan
 
   -- We use a mask here rather than a bracket because we want
@@ -243,63 +261,59 @@ builderMainLoop logger filter_fn pgm real_args mb_cwd mb_env = do
         -- finish.
         let procdata =
               enableProcessJobs
-              $ (proc pgm real_args) { cwd = mb_cwd
-                                     , env = mb_env
-                                     , std_in  = CreatePipe
-                                     , std_out = CreatePipe
-                                     , std_err = CreatePipe
-                                     }
-        (Just hStdIn, Just hStdOut, Just hStdErr, hProcess) <- restore $
+              $ (proc pgm real_args) {
+                cwd = mb_cwd
+              , env = mb_env
+              , std_in  = CreatePipe
+
+              -- We used to treat stdout/stderr as separate streams, but this
+              -- was racy (see #25517).  We now treat them as one stream and
+              -- that is fine for our use-case.  We rely on upstream programs
+              -- to serialize writes to the two streams appropriately (note
+              -- that they already need to do that to produce deterministic
+              -- output when used interactively / on the command-line).
+              , std_out = UseHandle writeEnd
+              , std_err = UseHandle writeEnd
+              }
+        (Just hStdIn, Nothing, Nothing, hProcess) <- restore $
           createProcess_ "builderMainLoop" procdata
-        let cleanup_handles = do
-              hClose hStdIn
-              hClose hStdOut
-              hClose hStdErr
+        hClose writeEnd
         r <- try $ restore $ do
-          hSetBuffering hStdOut LineBuffering
-          hSetBuffering hStdErr LineBuffering
+          getLocaleEncoding >>= hSetEncoding readEnd
+          hSetNewlineMode readEnd nativeNewlineMode
+          hSetBuffering readEnd LineBuffering
           let make_reader_proc h = forkIO $ readerProc chan h filter_fn
-          bracketOnError (make_reader_proc hStdOut) killThread $ \_ ->
-            bracketOnError (make_reader_proc hStdErr) killThread $ \_ ->
+          bracketOnError (make_reader_proc readEnd) killThread $ \_ ->
             inner hProcess
+        hClose hStdIn
         case r of
-          -- onException
           Left (SomeException e) -> do
             terminateProcess hProcess
-            cleanup_handles
             throw e
-          -- cleanup when there was no exception
           Right s -> do
-            cleanup_handles
             return s
   safely $ \h -> do
-    -- we don't want to finish until 2 streams have been complete
-    -- (stdout and stderr)
-    log_loop chan (2 :: Integer)
-    -- after that, we wait for the process to finish and return the exit code.
+    processBuildMessages chan
     waitForProcess h
   where
-    -- t starts at the number of streams we're listening to (2) decrements each
-    -- time a reader process sends EOF. We are safe from looping forever if a
-    -- reader thread dies, because they send EOF in a finally handler.
-    log_loop _ 0 = return ()
-    log_loop chan t = do
+    processBuildMessages :: Chan BuildMessage -> IO ()
+    processBuildMessages chan = do
       msg <- readChan chan
       case msg of
         BuildMsg msg -> do
           logInfo logger $ withPprStyle defaultUserStyle msg
-          log_loop chan t
+          processBuildMessages chan
         BuildError loc msg -> do
           logMsg logger errorDiagnostic (mkSrcSpan loc loc)
               $ withPprStyle defaultUserStyle msg
-          log_loop chan t
+          processBuildMessages chan
         EOF ->
-          log_loop chan  (t-1)
+          return ()
 
 readerProc :: Chan BuildMessage -> Handle -> (String -> String) -> IO ()
 readerProc chan hdl filter_fn =
     (do str <- hGetContents hdl
-        loop (linesPlatform (filter_fn str)) Nothing)
+        loop (lines (filter_fn str)) Nothing)
     `finally`
        writeChan chan EOF
         -- ToDo: check errors more carefully
@@ -372,21 +386,3 @@ data BuildMessage
   = BuildMsg   !SDoc
   | BuildError !SrcLoc !SDoc
   | EOF
-
--- Divvy up text stream into lines, taking platform dependent
--- line termination into account.
-linesPlatform :: String -> [String]
-#if !defined(mingw32_HOST_OS)
-linesPlatform ls = lines ls
-#else
-linesPlatform "" = []
-linesPlatform xs =
-  case lineBreak xs of
-    (as,xs1) -> as : linesPlatform xs1
-  where
-   lineBreak "" = ("","")
-   lineBreak ('\r':'\n':xs) = ([],xs)
-   lineBreak ('\n':xs) = ([],xs)
-   lineBreak (x:xs) = let (as,bs) = lineBreak xs in (x:as,bs)
-
-#endif



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f98b3ac03f5e49d62669e52e8ed0fcdec66c596b

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f98b3ac03f5e49d62669e52e8ed0fcdec66c596b
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20241203/c5961c8f/attachment-0001.html>


More information about the ghc-commits mailing list