Can now restart certain long-running git processes if they crash, and continue working.

Fuzz tests have shown that git cat-file --batch sometimes stops running.
It's not yet known why (no error message; repo seems ok). But this is
something we can deal with in the CoProcess framework, since all 3 types of
long-running git processes should be restartable if they fail.

Note that, as implemented, only IO errors are caught. So an error thrown
by the reveiver, when it sees something that is not valid output from
git cat-file (etc) will not cause a restart. I don't want it to retry
if git commands change their output or are just outputting garbage.
This does mean that if the command did a partial output and crashed in the
middle, it would still not be restarted.

There is currently no guard against restarting a command repeatedly, if,
for example, it crashes repeatedly on startup.
This commit is contained in:
Joey Hess 2013-05-31 12:20:17 -04:00
parent 8100cad9d5
commit 91c4dcfc69
6 changed files with 69 additions and 22 deletions

View file

@ -29,7 +29,7 @@ import qualified Utility.CoProcess as CoProcess
type CatFileHandle = CoProcess.CoProcessHandle type CatFileHandle = CoProcess.CoProcessHandle
catFileStart :: Repo -> IO CatFileHandle catFileStart :: Repo -> IO CatFileHandle
catFileStart = CoProcess.rawMode <=< gitCoProcessStart catFileStart = CoProcess.rawMode <=< gitCoProcessStart True
[ Param "cat-file" [ Param "cat-file"
, Param "--batch" , Param "--batch"
] ]

View file

@ -22,7 +22,7 @@ type Attr = String
checkAttrStart :: [Attr] -> Repo -> IO CheckAttrHandle checkAttrStart :: [Attr] -> Repo -> IO CheckAttrHandle
checkAttrStart attrs repo = do checkAttrStart attrs repo = do
cwd <- getCurrentDirectory cwd <- getCurrentDirectory
h <- CoProcess.rawMode =<< gitCoProcessStart params repo h <- CoProcess.rawMode =<< gitCoProcessStart True params repo
return (h, attrs, cwd) return (h, attrs, cwd)
where where
params = params =

View file

@ -109,8 +109,10 @@ leaveZombie :: (a, IO Bool) -> a
leaveZombie = fst leaveZombie = fst
{- Runs a git command as a coprocess. -} {- Runs a git command as a coprocess. -}
gitCoProcessStart :: [CommandParam] -> Repo -> IO CoProcess.CoProcessHandle gitCoProcessStart :: Bool -> [CommandParam] -> Repo -> IO CoProcess.CoProcessHandle
gitCoProcessStart params repo = CoProcess.start "git" (toCommand $ gitCommandLine params repo) (gitEnv repo) gitCoProcessStart restartable params repo = CoProcess.start restartable "git"
(toCommand $ gitCommandLine params repo)
(gitEnv repo)
gitCreateProcess :: [CommandParam] -> Repo -> CreateProcess gitCreateProcess :: [CommandParam] -> Repo -> CreateProcess
gitCreateProcess params repo = gitCreateProcess params repo =

View file

@ -17,7 +17,7 @@ import qualified Utility.CoProcess as CoProcess
type HashObjectHandle = CoProcess.CoProcessHandle type HashObjectHandle = CoProcess.CoProcessHandle
hashObjectStart :: Repo -> IO HashObjectHandle hashObjectStart :: Repo -> IO HashObjectHandle
hashObjectStart = CoProcess.rawMode <=< gitCoProcessStart hashObjectStart = CoProcess.rawMode <=< gitCoProcessStart True
[ Param "hash-object" [ Param "hash-object"
, Param "-w" , Param "-w"
, Param "--stdin-paths" , Param "--stdin-paths"

View file

@ -1,7 +1,7 @@
{- Interface for running a shell command as a coprocess, {- Interface for running a shell command as a coprocess,
- sending it queries and getting back results. - sending it queries and getting back results.
- -
- Copyright 2012 Joey Hess <joey@kitenet.net> - Copyright 2012-2013 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -18,29 +18,72 @@ module Utility.CoProcess (
import Common import Common
type CoProcessHandle = (ProcessHandle, Handle, Handle, CreateProcess) import Control.Concurrent.MVar
start :: FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle type CoProcessHandle = MVar CoProcessState
start command params env = do
(from, to, _err, pid) <- runInteractiveProcess command params Nothing env data CoProcessState = CoProcessState
return (pid, to, from, proc command params) { coProcessPid :: ProcessHandle
, coProcessTo :: Handle
, coProcessFrom :: Handle
, coProcessSpec :: CoProcessSpec
}
data CoProcessSpec = CoProcessSpec
{ coProcessRestartable :: Bool
, coProcessCmd :: FilePath
, coProcessParams :: [String]
, coProcessEnv :: Maybe [(String, String)]
}
start :: Bool -> FilePath -> [String] -> Maybe [(String, String)] -> IO CoProcessHandle
start restartable cmd params env = do
s <- start' $ CoProcessSpec restartable cmd params env
newMVar s
start' :: CoProcessSpec -> IO CoProcessState
start' s = do
(to, from, _err, pid) <- runInteractiveProcess (coProcessCmd s) (coProcessParams s) Nothing (coProcessEnv s)
return $ CoProcessState pid to from s
stop :: CoProcessHandle -> IO () stop :: CoProcessHandle -> IO ()
stop (pid, from, to, p) = do stop ch = do
hClose to s <- readMVar ch
hClose from hClose $ coProcessTo s
forceSuccessProcess p pid hClose $ coProcessFrom s
let p = proc (coProcessCmd $ coProcessSpec s) (coProcessParams $ coProcessSpec s)
forceSuccessProcess p (coProcessPid s)
{- 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 failed action is re-run with a new process. -}
query :: CoProcessHandle -> (Handle -> IO a) -> (Handle -> IO b) -> IO b query :: CoProcessHandle -> (Handle -> IO a) -> (Handle -> IO b) -> IO b
query (_, from, to, _) send receive = do query ch send receive = do
_ <- send to s <- readMVar ch
hFlush to restartable s (send $ coProcessTo s) $ const $
receive from restartable s (hFlush $ coProcessTo s) $ const $
restartable s (receive $ coProcessFrom s) $
return
where
restartable s a cont
| coProcessRestartable (coProcessSpec s) =
maybe restart cont =<< catchMaybeIO a
| otherwise = cont =<< a
restart = do
s <- takeMVar ch
void $ catchMaybeIO $ do
hClose $ coProcessTo s
hClose $ coProcessFrom s
void $ waitForProcess $ coProcessPid s
s' <- start' (coProcessSpec s)
putMVar ch s'
query ch send receive
rawMode :: CoProcessHandle -> IO CoProcessHandle rawMode :: CoProcessHandle -> IO CoProcessHandle
rawMode ch@(_, from, to, _) = do rawMode ch = do
raw from s <- readMVar ch
raw to raw $ coProcessFrom s
raw $ coProcessTo s
return ch return ch
where where
raw h = do raw h = do

2
debian/changelog vendored
View file

@ -21,6 +21,8 @@ git-annex (4.20130522) UNRELEASED; urgency=low
* XMPP: Fix a file descriptor leak. * XMPP: Fix a file descriptor leak.
* Android: Added an "Open WebApp" item to the terminal's menu. * Android: Added an "Open WebApp" item to the terminal's menu.
Should work for Android devices that cannot auto-open the webapp on start. Should work for Android devices that cannot auto-open the webapp on start.
* Can now restart certain long-running git processes if they crash, and
continue working.
-- Joey Hess <joeyh@debian.org> Tue, 21 May 2013 18:22:46 -0400 -- Joey Hess <joeyh@debian.org> Tue, 21 May 2013 18:22:46 -0400