2020-07-29 16:00:27 +00:00
|
|
|
{- External addon processes for special remotes and backends.
|
|
|
|
-
|
|
|
|
- Copyright 2013-2020 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Annex.ExternalAddonProcess where
|
|
|
|
|
|
|
|
import qualified Annex
|
|
|
|
import Annex.Common
|
|
|
|
import Git.Env
|
|
|
|
import Utility.Shell
|
|
|
|
import Messages.Progress
|
|
|
|
|
|
|
|
import Control.Concurrent.Async
|
2020-07-29 19:23:18 +00:00
|
|
|
import System.Log.Logger (debugM)
|
2020-07-29 16:00:27 +00:00
|
|
|
|
|
|
|
data ExternalAddonProcess = ExternalAddonProcess
|
|
|
|
{ externalSend :: Handle
|
|
|
|
, externalReceive :: Handle
|
|
|
|
-- Shut down the process. With True, it's forced to stop
|
|
|
|
-- immediately.
|
|
|
|
, externalShutdown :: Bool -> IO ()
|
|
|
|
, externalPid :: ExternalAddonPID
|
2020-07-29 19:23:18 +00:00
|
|
|
, externalProgram :: String
|
2020-07-29 16:00:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ExternalAddonPID = Int
|
|
|
|
|
|
|
|
data ExternalAddonStartError
|
|
|
|
= ProgramNotInstalled String
|
|
|
|
| ProgramFailure String
|
|
|
|
|
2020-07-29 19:23:18 +00:00
|
|
|
startExternalAddonProcess :: String -> ExternalAddonPID -> Annex (Either ExternalAddonStartError ExternalAddonProcess)
|
|
|
|
startExternalAddonProcess basecmd pid = do
|
2020-07-29 16:00:27 +00:00
|
|
|
errrelayer <- mkStderrRelayer
|
|
|
|
g <- Annex.gitRepo
|
|
|
|
cmdpath <- liftIO $ searchPath basecmd
|
|
|
|
liftIO $ start errrelayer g cmdpath
|
|
|
|
where
|
|
|
|
start errrelayer g cmdpath = do
|
|
|
|
(cmd, ps) <- maybe (pure (basecmd, [])) findShellCommand cmdpath
|
|
|
|
let basep = (proc cmd (toCommand ps))
|
|
|
|
{ std_in = CreatePipe
|
|
|
|
, std_out = CreatePipe
|
|
|
|
, std_err = CreatePipe
|
|
|
|
}
|
|
|
|
p <- propgit g basep
|
|
|
|
tryNonAsync (createProcess p) >>= \case
|
2020-07-29 19:23:18 +00:00
|
|
|
Right v -> (Right <$> started cmd errrelayer v)
|
2020-07-29 16:00:27 +00:00
|
|
|
`catchNonAsync` const (runerr cmdpath)
|
|
|
|
Left _ -> runerr cmdpath
|
|
|
|
|
2020-07-29 19:23:18 +00:00
|
|
|
started cmd errrelayer pall@(Just hin, Just hout, Just herr, ph) = do
|
2020-07-29 16:00:27 +00:00
|
|
|
stderrelay <- async $ errrelayer herr
|
|
|
|
let shutdown forcestop = do
|
Improve shutdown process for external special remotes and external backends
Make sure to relay any remaining stderr from the process after it has
shut down, rather than closing stderr just before shutdown. This avoids
a situation where the process is still running and tries to write to
stderr, getting a SIGPIPE. And, it ensures that no stderr output is
lost.
This may fix a problem encountered by datalad on windows, where it hangs
during the external special remote shutdown.
Before commit a49d300545b4b7e7bd1e2719e3042d14f6e32e9c, it closed stdin
and stdout, but left stderr open, and never killed the stderr waiter
thread, which presumably exited on its own. For async exception
safety, do need to at make sure that thread gets waited on, as that
commit does, but it introduced this problem.
Note that, the process's stdout is closed before waiting on it. It's too
late for anything it writes to stdout to be processed, and since we're
not going to consume any such writes, this avoids the process getting
blocked writing to stdout due to us not reading what it's buffered. This
does mean that if the process writes to stdout too late, it will get a
SIGPIPE. (This was already the case before the above-mentioned commit.)
In practice, I think only the protocol's ERROR is allowed to be
sent at a point where this could happen.
2020-11-02 16:35:07 +00:00
|
|
|
-- Close the process's stdin, to let it know there
|
|
|
|
-- are no more requests, so it will exit.
|
|
|
|
hClose hout
|
|
|
|
-- Close the procces's stdout as we're not going to
|
|
|
|
-- process any more output from it.
|
|
|
|
hClose hin
|
2020-07-29 16:00:27 +00:00
|
|
|
if forcestop
|
|
|
|
then cleanupProcess pall
|
Improve shutdown process for external special remotes and external backends
Make sure to relay any remaining stderr from the process after it has
shut down, rather than closing stderr just before shutdown. This avoids
a situation where the process is still running and tries to write to
stderr, getting a SIGPIPE. And, it ensures that no stderr output is
lost.
This may fix a problem encountered by datalad on windows, where it hangs
during the external special remote shutdown.
Before commit a49d300545b4b7e7bd1e2719e3042d14f6e32e9c, it closed stdin
and stdout, but left stderr open, and never killed the stderr waiter
thread, which presumably exited on its own. For async exception
safety, do need to at make sure that thread gets waited on, as that
commit does, but it introduced this problem.
Note that, the process's stdout is closed before waiting on it. It's too
late for anything it writes to stdout to be processed, and since we're
not going to consume any such writes, this avoids the process getting
blocked writing to stdout due to us not reading what it's buffered. This
does mean that if the process writes to stdout too late, it will get a
SIGPIPE. (This was already the case before the above-mentioned commit.)
In practice, I think only the protocol's ERROR is allowed to be
sent at a point where this could happen.
2020-11-02 16:35:07 +00:00
|
|
|
else void (waitForProcess ph)
|
|
|
|
`onException` cleanupProcess pall
|
|
|
|
-- This thread will exit after consuming any
|
|
|
|
-- remaining stderr from the process.
|
|
|
|
wait stderrelay
|
|
|
|
hClose herr
|
2020-07-29 16:00:27 +00:00
|
|
|
return $ ExternalAddonProcess
|
|
|
|
{ externalSend = hin
|
|
|
|
, externalReceive = hout
|
|
|
|
, externalPid = pid
|
|
|
|
, externalShutdown = shutdown
|
2020-07-29 19:23:18 +00:00
|
|
|
, externalProgram = cmd
|
2020-07-29 16:00:27 +00:00
|
|
|
}
|
2020-07-29 19:23:18 +00:00
|
|
|
started _ _ _ = giveup "internal"
|
2020-07-29 16:00:27 +00:00
|
|
|
|
|
|
|
propgit g p = do
|
|
|
|
environ <- propGitEnv g
|
|
|
|
return $ p { env = Just environ }
|
|
|
|
|
|
|
|
runerr (Just cmd) =
|
|
|
|
return $ Left $ ProgramFailure $
|
|
|
|
"Cannot run " ++ cmd ++ " -- Make sure it's executable and that its dependencies are installed."
|
|
|
|
runerr Nothing = do
|
|
|
|
path <- intercalate ":" <$> getSearchPath
|
|
|
|
return $ Left $ ProgramNotInstalled $
|
|
|
|
"Cannot run " ++ basecmd ++ " -- It is not installed in PATH (" ++ path ++ ")"
|
2020-07-29 19:23:18 +00:00
|
|
|
|
|
|
|
protocolDebug :: ExternalAddonProcess -> Bool -> String -> IO ()
|
|
|
|
protocolDebug external sendto line = debugM "external" $ unwords
|
|
|
|
[ externalProgram external ++
|
|
|
|
"[" ++ show (externalPid external) ++ "]"
|
|
|
|
, if sendto then "<--" else "-->"
|
|
|
|
, line
|
|
|
|
]
|