git-annex/Utility/CoProcess.hs

108 lines
2.9 KiB
Haskell
Raw Normal View History

2012-02-20 19:20:36 +00:00
{- Interface for running a shell command as a coprocess,
- sending it queries and getting back results.
-
- Copyright 2012-2020 Joey Hess <id@joeyh.name>
2012-02-20 19:20:36 +00:00
-
- License: BSD-2-clause
2012-02-20 19:20:36 +00:00
-}
2013-05-12 03:11:56 +00:00
{-# LANGUAGE CPP #-}
2012-02-20 19:20:36 +00:00
module Utility.CoProcess (
2020-05-04 16:44:26 +00:00
CoProcessHandle,
CoProcessState(..),
2012-02-20 19:20:36 +00:00
start,
stop,
2013-05-12 03:11:56 +00:00
query,
2012-02-20 19:20:36 +00:00
) where
import Common
import Control.Concurrent.MVar
2012-02-20 19:20:36 +00:00
type CoProcessHandle = MVar CoProcessState
data CoProcessState = CoProcessState
{ coProcessPid :: ProcessHandle
, coProcessTo :: Handle
, coProcessFrom :: Handle
, coProcessSpec :: CoProcessSpec
}
data CoProcessSpec = CoProcessSpec
{ coProcessNumRestarts :: Int
, coProcessCmd :: FilePath
, coProcessParams :: [String]
, coProcessEnv :: Maybe [(String, String)]
}
start :: Int -> FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle
start numrestarts cmd params environ = do
s <- start' $ CoProcessSpec numrestarts cmd params environ
newMVar s
start' :: CoProcessSpec -> IO CoProcessState
start' s = do
(pid, from, to) <- startInteractiveProcess (coProcessCmd s) (coProcessParams s) (coProcessEnv s)
rawMode from
rawMode to
return $ CoProcessState pid to from s
where
#ifdef mingw32_HOST_OS
rawMode h = hSetNewlineMode h noNewlineTranslation
#else
rawMode _ = return ()
#endif
2012-02-20 19:20:36 +00:00
stop :: CoProcessHandle -> IO ()
stop ch = do
s <- readMVar ch
hClose $ coProcessTo s
hClose $ coProcessFrom s
let p = proc (coProcessCmd $ coProcessSpec s) (coProcessParams $ coProcessSpec s)
forceSuccessProcess p (coProcessPid s)
2012-02-20 19:20:36 +00:00
{- Note that concurrent queries are not safe to perform; caller should
- serialize calls to query.
-
- To handle a restartable process, any IO exception thrown by the send and
- receive actions are assumed to mean communication with the process
- failed, and the query is re-run with a new process.
-
- If an async exception is received during a query, the state of
- communication with the process is unknown, so it is killed, and a new
- one started so the CoProcessHandle can continue to be used by other
- threads.
-}
2012-02-20 19:20:36 +00:00
query :: CoProcessHandle -> (Handle -> IO a) -> (Handle -> IO b) -> IO b
query ch send receive = uninterruptibleMask $ \unmask ->
unmask (readMVar ch >>= restartable)
`catchAsync` forcerestart
where
go s = do
void $ send $ coProcessTo s
hFlush $ coProcessTo s
receive $ coProcessFrom s
restartable s
| coProcessNumRestarts (coProcessSpec s) > 0 =
catchMaybeIO (go s)
>>= maybe (restart s increstarts restartable) return
| otherwise = go s
increstarts s = s { coProcessNumRestarts = coProcessNumRestarts s - 1 }
restart s f cont = do
void $ tryNonAsync $ do
hClose $ coProcessTo s
hClose $ coProcessFrom s
void $ waitForProcess $ coProcessPid s
s' <- withMVarMasked ch $ \_ -> start' (f (coProcessSpec s))
cont s'
forcerestart ex = do
s <- readMVar ch
terminateProcess (coProcessPid s)
restart s id $ \s' -> void $ swapMVar ch s'
either throwM throwM ex