{- Pid locking support. - - Copyright 2014-2021 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} {-# LANGUAGE CPP #-} module Annex.PidLock where import Annex.Common import Git #ifndef mingw32_HOST_OS import Git.Env import Annex.GitOverlay import qualified Utility.LockFile.PidLock as PidF import qualified Utility.LockPool.PidLock as PidP import Utility.LockPool (dropLock) import Utility.Env import Config #endif {- When pid locking is in use, this tries to take the pid lock (unless - the process already has it), and if successful, holds it while - running the child process. The child process is run with an env var - set, which prevents it from trying to take the pid lock itself. - - This way, any locking the parent does will not get in the way of - the child. The child is assumed to not do any locking that conflicts - with the parent, but if it did happen to do that, it would be noticed - when git-annex is used without pid locking. - - If another process is already holding the pid lock, the child process - is still run, but without setting the env var, so it can try to take the - pid lock itself, and fail however is appropriate for it in that - situation. -} pidLockChildProcess :: FilePath -> [CommandParam] -> (CreateProcess -> CreateProcess) -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a) -> Annex a pidLockChildProcess cmd ps f a = do let p = f (proc cmd (toCommand ps)) let gonopidlock = withCreateProcess p a #ifndef mingw32_HOST_OS pidLockFile >>= liftIO . \case Nothing -> gonopidlock Just pidlock -> bracket (setup pidlock) cleanup (go gonopidlock p pidlock) where setup pidlock = fmap fst <$> PidP.tryLock' pidlock cleanup (Just h) = dropLock h cleanup Nothing = return () go gonopidlock _ _ Nothing = gonopidlock go _ p pidlock (Just _h) = do v <- PidF.pidLockEnv pidlock baseenv <- case env p of Nothing -> getEnvironment Just baseenv -> pure baseenv let p' = p { env = Just ((v, PidF.pidLockEnvValue) : baseenv) } withCreateProcess p' a #else liftIO gonopidlock #endif {- Wrap around actions that may run a git-annex child process via a git - command. - - This is like pidLockChildProcess, but rather than running a process - itself, it runs the action with a modified Annex state that passes the - necessary env var when running git. -} runsGitAnnexChildProcessViaGit :: Annex a -> Annex a #ifndef mingw32_HOST_OS runsGitAnnexChildProcessViaGit a = pidLockFile >>= \case Nothing -> a Just pidlock -> bracket (setup pidlock) cleanup (go pidlock) where setup pidlock = liftIO $ fmap fst <$> PidP.tryLock' pidlock cleanup (Just h) = liftIO $ dropLock h cleanup Nothing = return () go _ Nothing = a go pidlock (Just _h) = do v <- liftIO $ PidF.pidLockEnv pidlock let addenv g = do g' <- liftIO $ addGitEnv g v PidF.pidLockEnvValue return (g', ()) let rmenv oldg g | any (\(k, _) -> k == v) (fromMaybe [] (Git.gitEnv oldg)) = g | otherwise = let e' = case Git.gitEnv g of Just e -> Just (delEntry v e) Nothing -> Nothing in g { Git.gitEnv = e' } withAltRepo addenv rmenv (const a) #else runsGitAnnexChildProcessViaGit a = a #endif {- Like runsGitAnnexChildProcessViaGit, but the Annex state is not - modified. Instead the input Repo's state is modified to set the - necessary env var when git is run in that Repo. -} runsGitAnnexChildProcessViaGit' :: Git.Repo -> (Git.Repo -> Annex a) -> Annex a #ifndef mingw32_HOST_OS runsGitAnnexChildProcessViaGit' r a = pidLockFile >>= \case Nothing -> a r Just pidlock -> bracketIO (setup pidlock) cleanup (go pidlock) where setup pidlock = fmap fst <$> PidP.tryLock' pidlock cleanup (Just h) = dropLock h cleanup Nothing = return () go _ Nothing = a r go pidlock (Just _h) = do v <- liftIO $ PidF.pidLockEnv pidlock r' <- liftIO $ addGitEnv r v PidF.pidLockEnvValue a r' #else runsGitAnnexChildProcessViaGit' r a = a r #endif