From e4db2dcc3b7dde395358a5b77c2fe6fc18a900eb Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 3 Feb 2020 21:28:43 -0500 Subject: [PATCH] Use jobs when calling subprocesses Many toolchain tools written for POSIX systems rely on the exec system call. Unfortunately, it is not possible to implement `exec` in a POSIX-compliant manner on Windows. In particular, the semantics of the `exec` implementation provided by the widely-used `msvcrt` C runtime will cause process's waiting on the `exec`'ing process to incorrectly conclude that the process has successfully terminated when in fact it is still running in another process. For this reason, the `process` library exposes the `use_process_jobs` flag to use a more strict (although still not POSIX-compliant) mechanism for tracking process completion. This is explained in this comment [2]. Unfortunately, job support in the `process` library is currently quite broken and was only recently fixed [1]. Consequently, we only enable job object support for process releases >= 1.6.8. [1] https://github.com/haskell/process/pull/168 [2] https://github.com/haskell/process/blob/master/System/Process.hs#L399 --- Cabal/Distribution/Simple/Utils.hs | 53 ++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/Cabal/Distribution/Simple/Utils.hs b/Cabal/Distribution/Simple/Utils.hs index 96c8406b7d2..05e5fb71c10 100644 --- a/Cabal/Distribution/Simple/Utils.hs +++ b/Cabal/Distribution/Simple/Utils.hs @@ -230,13 +230,14 @@ import System.IO.Unsafe import qualified Control.Exception as Exception import Foreign.C.Error (Errno (..), ePIPE) +import Data.Maybe (fromJust) import Data.Time.Clock.POSIX (getPOSIXTime, POSIXTime) import Control.Exception (IOException, evaluate, throwIO, fromException) import Numeric (showFFloat) import qualified System.Process as Process - ( CreateProcess(..), StdStream(..), proc) + ( CreateProcess(..), StdStream(..), proc, createProcess ) import System.Process - ( ProcessHandle, createProcess, rawSystem, runInteractiveProcess + ( CreateProcess, ProcessHandle , showCommandForUser, waitForProcess) import qualified GHC.IO.Exception as GHC @@ -680,6 +681,54 @@ maybeExit cmd = do res <- cmd unless (res == ExitSuccess) $ exitWith res +-- | Enable process jobs to ensure accurate determination of process completion +-- in the presence of @exec(3)@ on Windows. +-- +-- Unfortunately the process job support is badly broken in @process@ releases +-- prior to 1.6.8, so we disable it in these versions, despite the fact that +-- this means we may see sporatic build failures without jobs. +enableProcessJobs :: CreateProcess -> CreateProcess +#ifdef MIN_VERSION_process +#if MIN_VERSION_process(1,6,8) +enableProcessJobs cp = cp {Process.use_process_jobs = True} +#else +enableProcessJobs cp = cp +#endif +#else +enableProcessJobs cp = cp +#endif + +-- | 'System.Process.createProcess' with process jobs enabled when appropriate. +-- See 'enableProcessJobs'. +createProcess :: CreateProcess + -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) +createProcess = Process.createProcess . enableProcessJobs + +-- | 'System.Process.rawSystem' with process jobs enabled when appropriate. +-- See 'enableProcessJobs'. +rawSystem :: String -> [String] -> IO ExitCode +rawSystem cmd args = do + (_,_,_,p) <- createProcess (Process.proc cmd args) { Process.delegate_ctlc = True } + waitForProcess p + +-- | 'System.Process.runInteractiveProcess' with process jobs enabled when +-- appropriate. See 'enableProcessJobs'. +runInteractiveProcess + :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details) + -> [String] -- ^ Arguments to pass to the executable + -> Maybe FilePath -- ^ Optional path to the working directory + -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit) + -> IO (Handle,Handle,Handle,ProcessHandle) +runInteractiveProcess cmd args mb_cwd mb_env = do + (mb_in, mb_out, mb_err, p) <- + createProcess (Process.proc cmd args) + { Process.std_in = Process.CreatePipe, + Process.std_out = Process.CreatePipe, + Process.std_err = Process.CreatePipe, + Process.env = mb_env, + Process.cwd = mb_cwd } + return (fromJust mb_in, fromJust mb_out, fromJust mb_err, p) + printRawCommandAndArgs :: Verbosity -> FilePath -> [String] -> IO () printRawCommandAndArgs verbosity path args = withFrozenCallStack $ printRawCommandAndArgsAndEnv verbosity path args Nothing Nothing