Skip to content

Commit de7379d

Browse files
committed
An API for inter-process communication via Handles
This commit adds the System.Process.CommunicationHandle module, which provides the cross-platform CommunicationHandle abstraction which allows Handles to be passed to child processes for inter-process communication. A high-level API is provided by the function `readCreateProcessWithExitCodeCommunicationHandle`, which can be consulted for further details about how the functionality is meant to be used. To test this functionality, we created a new "cli-child" executable component to the library. To work around Cabal bug #9854, it was necessary to change the build-type of the library to `Custom`, in order to make the "cli-child" executable visible when running the test-suite. The custom Setup.hs script contains more details about the problem.
1 parent a590acd commit de7379d

File tree

8 files changed

+636
-43
lines changed

8 files changed

+636
-43
lines changed

Setup.hs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,82 @@
1+
{-# OPTIONS_GHC -Wall #-}
2+
13
module Main (main) where
24

5+
-- Cabal
36
import Distribution.Simple
7+
( defaultMainWithHooks
8+
, autoconfUserHooks
9+
, UserHooks(buildHook)
10+
)
11+
import Distribution.Simple.BuildPaths
12+
( autogenComponentModulesDir
13+
, exeExtension
14+
)
15+
import Distribution.Simple.LocalBuildInfo
16+
( hostPlatform
17+
, buildDir
18+
, withTestLBI
19+
)
20+
import Distribution.Types.LocalBuildInfo
21+
( LocalBuildInfo
22+
, allTargetsInBuildOrder'
23+
)
24+
import Distribution.Types.Component
25+
( Component(CExe) )
26+
import Distribution.Types.Executable
27+
( Executable(exeName) )
28+
import Distribution.Types.PackageDescription
29+
( PackageDescription )
30+
import Distribution.Types.TargetInfo
31+
( targetComponent )
32+
import Distribution.Types.UnqualComponentName
33+
( unUnqualComponentName )
34+
35+
-- directory
36+
import System.Directory
37+
( createDirectoryIfMissing )
38+
39+
-- filepath
40+
import System.FilePath
41+
( (</>), (<.>), takeDirectory )
42+
43+
--------------------------------------------------------------------------------
444

545
main :: IO ()
6-
main = defaultMainWithHooks autoconfUserHooks
46+
main = defaultMainWithHooks processHooks
47+
48+
-- The following code works around Cabal bug #9854.
49+
--
50+
-- The process package has an executable component named "cli-child",
51+
-- used for testing. We want to invoke this executable when running tests;
52+
-- however, due to the Cabal bug this executable does not get added to PATH.
53+
-- To fix this, we create a "Test.Paths" module in a Custom setup script,
54+
-- which contains paths to executables used for testing.
55+
processHooks :: UserHooks
56+
processHooks =
57+
defaultConfigureHooks
58+
{ buildHook = \ pd lbi userHooks buildFlags ->
59+
withTestLBI pd lbi $ \ _testSuite clbi -> do
60+
let pathsFile = autogenComponentModulesDir lbi clbi </> "Test" </> "Paths" <.> "hs"
61+
createDirectoryIfMissing True (takeDirectory pathsFile)
62+
writeFile pathsFile $ unlines
63+
[ "module Test.Paths where"
64+
, "processInternalExes :: [(String, FilePath)]"
65+
, "processInternalExes = " ++ show (processInternalExes pd lbi)
66+
]
67+
buildHook defaultConfigureHooks pd lbi userHooks buildFlags
68+
}
69+
70+
defaultConfigureHooks :: UserHooks
71+
defaultConfigureHooks = autoconfUserHooks
72+
73+
processInternalExes :: PackageDescription -> LocalBuildInfo -> [(String, FilePath)]
74+
processInternalExes pd lbi =
75+
[ (toolName, toolLocation)
76+
| tgt <- allTargetsInBuildOrder' pd lbi
77+
, CExe exe <- [targetComponent tgt]
78+
, let toolName = unUnqualComponentName $ exeName exe
79+
toolLocation =
80+
buildDir lbi
81+
</> (toolName </> toolName <.> exeExtension (hostPlatform lbi))
82+
]

System/Process.hs

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ import System.Process.Internals
8989

9090
import Control.Concurrent
9191
import Control.DeepSeq (rnf)
92-
import Control.Exception (SomeException, mask
92+
import Control.Exception (
9393
#if !defined(javascript_HOST_ARCH)
94-
, allowInterrupt
94+
allowInterrupt,
9595
#endif
96-
, bracket, try, throwIO)
96+
bracket)
9797
import qualified Control.Exception as C
9898
import Control.Monad
9999
import Data.Maybe
@@ -112,7 +112,7 @@ import System.Posix.Process (getProcessID)
112112
import System.Posix.Types (CPid (..))
113113
#endif
114114

115-
import GHC.IO.Exception ( ioException, IOErrorType(..), IOException(..) )
115+
import GHC.IO.Exception ( ioException, IOErrorType(..) )
116116

117117
#if defined(wasm32_HOST_ARCH)
118118
import GHC.IO.Exception ( unsupportedOperation )
@@ -616,28 +616,6 @@ readCreateProcessWithExitCode cp input = do
616616
(_,Nothing,_) -> error "readCreateProcessWithExitCode: Failed to get a stdout handle."
617617
(_,_,Nothing) -> error "readCreateProcessWithExitCode: Failed to get a stderr handle."
618618

619-
-- | Fork a thread while doing something else, but kill it if there's an
620-
-- exception.
621-
--
622-
-- This is important in the cases above because we want to kill the thread
623-
-- that is holding the Handle lock, because when we clean up the process we
624-
-- try to close that handle, which could otherwise deadlock.
625-
--
626-
withForkWait :: IO () -> (IO () -> IO a) -> IO a
627-
withForkWait async body = do
628-
waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
629-
mask $ \restore -> do
630-
tid <- forkIO $ try (restore async) >>= putMVar waitVar
631-
let wait = takeMVar waitVar >>= either throwIO return
632-
restore (body wait) `C.onException` killThread tid
633-
634-
ignoreSigPipe :: IO () -> IO ()
635-
ignoreSigPipe = C.handle $ \e -> case e of
636-
IOError { ioe_type = ResourceVanished
637-
, ioe_errno = Just ioe }
638-
| Errno ioe == ePIPE -> return ()
639-
_ -> throwIO e
640-
641619
-- ----------------------------------------------------------------------------
642620
-- showCommandForUser
643621

0 commit comments

Comments
 (0)