2012-02-20 19:20:36 +00:00
|
|
|
{- Interface for running a shell command as a coprocess,
|
|
|
|
- sending it queries and getting back results.
|
|
|
|
-
|
2020-06-09 17:30:35 +00:00
|
|
|
- Copyright 2012-2020 Joey Hess <id@joeyh.name>
|
2012-02-20 19:20:36 +00:00
|
|
|
-
|
2014-05-10 14:01:27 +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,
|
2020-04-20 17:53:27 +00:00
|
|
|
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
|
|
|
|
|
2013-05-31 16:20:17 +00:00
|
|
|
import Control.Concurrent.MVar
|
2012-02-20 19:20:36 +00:00
|
|
|
|
2013-05-31 16:20:17 +00:00
|
|
|
type CoProcessHandle = MVar CoProcessState
|
|
|
|
|
|
|
|
data CoProcessState = CoProcessState
|
|
|
|
{ coProcessPid :: ProcessHandle
|
|
|
|
, coProcessTo :: Handle
|
|
|
|
, coProcessFrom :: Handle
|
|
|
|
, coProcessSpec :: CoProcessSpec
|
|
|
|
}
|
|
|
|
|
|
|
|
data CoProcessSpec = CoProcessSpec
|
2014-01-02 01:42:25 +00:00
|
|
|
{ coProcessNumRestarts :: Int
|
2013-05-31 16:20:17 +00:00
|
|
|
, coProcessCmd :: FilePath
|
|
|
|
, coProcessParams :: [String]
|
|
|
|
, coProcessEnv :: Maybe [(String, String)]
|
|
|
|
}
|
|
|
|
|
2014-01-02 01:42:25 +00:00
|
|
|
start :: Int -> FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle
|
2014-06-10 23:20:14 +00:00
|
|
|
start numrestarts cmd params environ = do
|
|
|
|
s <- start' $ CoProcessSpec numrestarts cmd params environ
|
2013-05-31 16:20:17 +00:00
|
|
|
newMVar s
|
|
|
|
|
|
|
|
start' :: CoProcessSpec -> IO CoProcessState
|
|
|
|
start' s = do
|
2013-06-14 21:59:22 +00:00
|
|
|
(pid, from, to) <- startInteractiveProcess (coProcessCmd s) (coProcessParams s) (coProcessEnv s)
|
2016-11-01 18:03:55 +00:00
|
|
|
rawMode from
|
|
|
|
rawMode to
|
2013-05-31 16:20:17 +00:00
|
|
|
return $ CoProcessState pid to from s
|
2016-11-01 18:03:55 +00:00
|
|
|
where
|
|
|
|
#ifdef mingw32_HOST_OS
|
2016-12-24 18:46:31 +00:00
|
|
|
rawMode h = hSetNewlineMode h noNewlineTranslation
|
|
|
|
#else
|
|
|
|
rawMode _ = return ()
|
2016-11-01 18:03:55 +00:00
|
|
|
#endif
|
2012-02-20 19:20:36 +00:00
|
|
|
|
|
|
|
stop :: CoProcessHandle -> IO ()
|
2013-05-31 16:20:17 +00:00
|
|
|
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
|
|
|
|
2020-06-09 17:30:35 +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
|
2013-05-31 16:20:17 +00:00
|
|
|
- receive actions are assumed to mean communication with the process
|
2020-06-09 17:30:35 +00:00
|
|
|
- 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
|
2020-06-09 17:30:35 +00:00
|
|
|
query ch send receive = uninterruptibleMask $ \unmask ->
|
|
|
|
unmask (readMVar ch >>= restartable)
|
|
|
|
`catchAsync` forcerestart
|
2013-05-31 16:20:17 +00:00
|
|
|
where
|
2020-06-09 17:30:35 +00:00
|
|
|
go s = do
|
|
|
|
void $ send $ coProcessTo s
|
|
|
|
hFlush $ coProcessTo s
|
|
|
|
receive $ coProcessFrom s
|
|
|
|
|
|
|
|
restartable s
|
2014-01-02 01:42:25 +00:00
|
|
|
| coProcessNumRestarts (coProcessSpec s) > 0 =
|
2020-06-09 17:30:35 +00:00
|
|
|
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
|
2013-05-31 16:20:17 +00:00
|
|
|
hClose $ coProcessTo s
|
|
|
|
hClose $ coProcessFrom s
|
|
|
|
void $ waitForProcess $ coProcessPid s
|
2020-06-09 17:30:35 +00:00
|
|
|
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
|