From b18fb1e343e9654207fbebacf686659c75d0fb4c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 13 Sep 2018 10:46:37 -0400 Subject: [PATCH] clean P2P protocol shutdown on EOF Avoids "git-annex-shell: : hGetChar: end of file" being displayed by the test suite, due to the way it runs git-annex-shell without using ssh. git-annex-shell over ssh was not affected because git-annex hangs up the ssh connection and so never sees the error message that git-annnex-shell probably did emit. This commit was sponsored by Ryan Newton on Patreon. --- P2P/IO.hs | 7 +++++-- P2P/Protocol.hs | 4 ++++ Utility/SimpleProtocol.hs | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/P2P/IO.hs b/P2P/IO.hs index 18971d9f3e..59e75456ec 100644 --- a/P2P/IO.hs +++ b/P2P/IO.hs @@ -38,6 +38,7 @@ import Annex.ChangedRefs import Control.Monad.Free import Control.Monad.IO.Class import System.Exit (ExitCode(..)) +import System.IO.Error import Network.Socket import Control.Concurrent import Control.Concurrent.Async @@ -159,9 +160,11 @@ runNet runst conn runner f = case f of Left e -> return (Left (show e)) Right () -> runner next ReceiveMessage next -> do - v <- liftIO $ tryNonAsync $ getProtocolLine (connIhdl conn) + v <- liftIO $ tryIOError $ getProtocolLine (connIhdl conn) case v of - Left e -> return (Left (show e)) + Left e + | isEOFError e -> runner (next (Just ProtocolEOF)) + | otherwise -> return (Left (show e)) Right Nothing -> return (Left "protocol error") Right (Just l) -> case parseMessage l of Just m -> do diff --git a/P2P/Protocol.hs b/P2P/Protocol.hs index 49a3d5bf6f..29fe072ffd 100644 --- a/P2P/Protocol.hs +++ b/P2P/Protocol.hs @@ -83,6 +83,7 @@ data Message | DATA Len -- followed by bytes of data | VALIDITY Validity | ERROR String + | ProtocolEOF deriving (Show) instance Proto.Sendable Message where @@ -108,6 +109,7 @@ instance Proto.Sendable Message where formatMessage (VALIDITY Invalid) = ["INVALID"] formatMessage (DATA len) = ["DATA", Proto.serialize len] formatMessage (ERROR err) = ["ERROR", Proto.serialize err] + formatMessage (ProtocolEOF) = [] instance Proto.Receivable Message where parseCommand "AUTH" = Proto.parse2 AUTH @@ -367,6 +369,8 @@ serverLoop :: (Message -> Proto (ServerHandler a)) -> Proto (Maybe a) serverLoop a = do mcmd <- net receiveMessage case mcmd of + -- Stop loop at EOF + Just ProtocolEOF -> return Nothing -- When the client sends ERROR to the server, the server -- gives up, since it's not clear what state the client -- is in, and so not possible to recover. diff --git a/Utility/SimpleProtocol.hs b/Utility/SimpleProtocol.hs index 7ab3c8c776..f941cfcd2a 100644 --- a/Utility/SimpleProtocol.hs +++ b/Utility/SimpleProtocol.hs @@ -113,6 +113,8 @@ dupIoHandles = do - This implementation is not super efficient, but as long as the Handle - supports buffering, it avoids reading a character at a time at the - syscall level. + - + - Throws isEOFError when no more input is available. -} getProtocolLine :: Handle -> IO (Maybe String) getProtocolLine h = go (32768 :: Int) []