rethought the async protocol some more

Moving jobid generation to the git-annex side lets it be simplified a
lot.

Note that it will also be possible to generate one jobid per connection,
rather than a new job per request. That will make overflow not an issue,
and will avoid some work, and will simplify some of the code.
This commit is contained in:
Joey Hess 2020-08-13 20:18:06 -04:00
parent 59cbb42ee2
commit 72561563d9
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
3 changed files with 104 additions and 182 deletions

View file

@ -26,25 +26,17 @@ import qualified Data.Map.Strict as M
runRelayToExternalAsync :: External -> ExternalState -> IO ExternalAsyncRelay
runRelayToExternalAsync external st = do
jidmap <- newTVarIO M.empty
mapjid <- newTVarIO M.empty
commcounter <- newTVarIO 0
newconns <- newTVarIO []
sendq <- newSendQueue
void $ async $ sendloop st newconns mapjid jidmap sendq
void $ async $ receiveloop external st newconns jidmap mapjid sendq
nextjid <- newTVarIO (JobId 1)
void $ async $ sendloop st nextjid jidmap sendq
void $ async $ receiveloop external st jidmap sendq
return $ ExternalAsyncRelay $ do
n <- atomically $ do
n <- readTVar commcounter
let n' = succ n
writeTVar commcounter n'
return n'
jidv <- newTVarIO Nothing
receiveq <- newReceiveQueue
return $ ExternalState
{ externalSend = \msg ->
atomically $ writeTBMChan sendq
( toAsyncWrapped msg
, (n, receiveq)
)
(toAsyncWrapped msg, (jidv, receiveq))
, externalReceive = atomically (readTBMChan receiveq)
-- This shuts down the whole relay.
, externalShutdown = shutdown external st sendq
@ -62,102 +54,64 @@ type ReceiveQueue = TBMChan String
type SendQueue = TBMChan (AsyncWrapped, Conn)
type ConnNum = Integer
type Conn = (ConnNum, ReceiveQueue)
type NewConns = TVar [Conn]
type MapJid = TVar (M.Map ConnNum JobId)
type Conn = (TVar (Maybe JobId), ReceiveQueue)
type JidMap = TVar (M.Map JobId Conn)
type NextJid = TVar JobId
newReceiveQueue :: IO ReceiveQueue
newReceiveQueue = newTBMChanIO 10
newSendQueue :: IO SendQueue
newSendQueue = newTBMChanIO 10
receiveloop :: External -> ExternalState -> NewConns -> JidMap -> MapJid -> SendQueue -> IO ()
receiveloop external st newconns jidmap mapjid sendq = externalReceive st >>= \case
receiveloop :: External -> ExternalState -> JidMap -> SendQueue -> IO ()
receiveloop external st jidmap sendq = externalReceive st >>= \case
Just l -> case parseMessage l :: Maybe AsyncMessage of
Just (RESULT_ASYNC msg) -> getnext newconns >>= \case
Just (_n, c) -> do
relayto c msg
loop
Nothing -> protoerr "unexpected RESULT-ASYNC"
Just (START_ASYNC jid) -> getnext newconns >>= \case
Just v@(n, _c) -> do
atomically $ do
modifyTVar' jidmap $ M.insert jid v
modifyTVar' mapjid $ M.insert n jid
loop
Nothing -> protoerr "unexpected START-ASYNC"
Just (ASYNC jid msg) -> getjid jid >>= \case
Just (_n, c) -> do
relayto c msg
loop
Nothing -> protoerr "ASYNC with unknown jobid"
Just (AsyncMessage jid msg) ->
M.lookup jid <$> readTVarIO jidmap >>= \case
Just (_jidv, c) -> do
atomically $ writeTBMChan c msg
receiveloop external st jidmap sendq
Nothing -> protoerr "unknown job number"
_ -> protoerr "unexpected non-async message"
Nothing -> closeandshutdown
where
loop = receiveloop external st newconns jidmap mapjid sendq
relayto q msg = atomically $ writeTBMChan q msg
closerelayto q = atomically $ closeTBMChan q
getnext l = atomically $ readTVar l >>= \case
[] -> return Nothing
(c:rest) -> do
writeTVar l rest
return (Just c)
getjid jid = M.lookup jid <$> readTVarIO jidmap
protoerr s = do
warningIO $ "async external special remote protocol error: " ++ s
closeandshutdown
closeandshutdown = do
shutdown external st sendq True
(m, l) <- atomically $ (,)
<$> readTVar jidmap
<*> readTVar newconns
forM_ (M.elems m ++ l) (closerelayto . snd)
m <- atomically $ readTVar jidmap
forM_ (M.elems m) (atomically . closeTBMChan . snd)
sendloop :: ExternalState -> NewConns -> MapJid -> JidMap -> SendQueue -> IO ()
sendloop st newconns mapjid jidmap sendq = atomically (readTBMChan sendq) >>= \case
Just (wrappedmsg, c@(n, _)) -> do
let newconn = atomically $ do
-- This append is not too expensive,
-- because the list length is limited
-- to the maximum number of jobs.
modifyTVar' newconns (++[c])
M.lookup n <$> readTVar mapjid >>= \case
Nothing -> return ()
Just jid -> do
modifyTVar' jidmap (M.delete jid)
modifyTVar' mapjid (M.delete n)
sendloop :: ExternalState -> NextJid -> JidMap -> SendQueue -> IO ()
sendloop st nextjid jidmap sendq = atomically (readTBMChan sendq) >>= \case
Just (wrappedmsg, conn@(jidv, _)) -> do
case wrappedmsg of
AsyncWrappedRequest msg -> do
newconn
externalSend st msg
AsyncWrappedExceptionalMessage msg -> do
newconn
externalSend st msg
jid <- atomically $ do
jid@(JobId n) <- readTVar nextjid
let !jid' = JobId (succ n)
writeTVar nextjid jid'
writeTVar jidv (Just jid)
modifyTVar' jidmap $ M.insert jid conn
return jid
externalSend st $ wrapjid msg jid
AsyncWrappedRemoteResponse msg ->
externalSend st =<< wrapremoteresponse msg n
AsyncWrappedAsyncReply msg ->
readTVarIO jidv >>= \case
Just jid -> externalSend st $ wrapjid msg jid
Nothing -> error "failed to find jid"
AsyncWrappedExceptionalMessage msg ->
externalSend st msg
sendloop st newconns mapjid jidmap sendq
AsyncWrappedAsyncMessage msg ->
externalSend st msg
sendloop st nextjid jidmap sendq
Nothing -> return ()
where
wrapremoteresponse msg n =
M.lookup n <$> readTVarIO mapjid >>= \case
Just jid -> return $ REPLY_ASYNC jid $
unwords $ Proto.formatMessage msg
Nothing -> error "failed to find jobid"
wrapjid msg jid = AsyncMessage jid $ unwords $ Proto.formatMessage msg
shutdown :: External -> ExternalState -> SendQueue -> Bool -> IO ()
shutdown external st sendq b = do

View file

@ -32,10 +32,9 @@ module Remote.External.Types (
RemoteResponse(..),
ExceptionalMessage(..),
AsyncMessage(..),
AsyncReply(..),
AsyncWrapped(..),
ToAsyncWrapped(..),
JobId,
JobId(..),
ErrorMsg,
Setting,
Description,
@ -59,6 +58,7 @@ import qualified Utility.SimpleProtocol as Proto
import Control.Concurrent.STM
import Network.URI
import Data.Char
import Text.Read
data External = External
{ externalType :: ExternalType
@ -363,35 +363,24 @@ instance Proto.Receivable ExceptionalMessage where
parseCommand "ERROR" = Proto.parse1 ERROR
parseCommand _ = Proto.parseFail
-- Messages sent by the special remote in the async protocol extension.
data AsyncMessage
= START_ASYNC JobId
| ASYNC JobId WrappedMsg
| RESULT_ASYNC WrappedMsg
-- Reply sent in the async protocol extension.
data AsyncReply
= REPLY_ASYNC JobId WrappedMsg
data AsyncMessage = AsyncMessage JobId WrappedMsg
instance Proto.Receivable AsyncMessage where
parseCommand "START-ASYNC" = Proto.parse1 START_ASYNC
parseCommand "ASYNC" = Proto.parse2 ASYNC
parseCommand "RESULT-ASYNC" = Proto.parse1 RESULT_ASYNC
parseCommand "J" = Proto.parse2 AsyncMessage
parseCommand _ = Proto.parseFail
instance Proto.Sendable AsyncReply where
formatMessage (REPLY_ASYNC jid msg) = ["REPLY-ASYNC", jid, msg]
instance Proto.Sendable AsyncMessage where
formatMessage (AsyncMessage jid msg) = ["J", Proto.serialize jid, msg]
data AsyncWrapped
= AsyncWrappedRemoteResponse RemoteResponse
| AsyncWrappedRequest Request
| AsyncWrappedExceptionalMessage ExceptionalMessage
| AsyncWrappedAsyncReply AsyncReply
| AsyncWrappedAsyncMessage AsyncMessage
class ToAsyncWrapped t where
toAsyncWrapped :: t -> AsyncWrapped
-- | RemoteResponse is sent wrapped in an async message.
instance ToAsyncWrapped RemoteResponse where
toAsyncWrapped = AsyncWrappedRemoteResponse
@ -401,8 +390,8 @@ instance ToAsyncWrapped Request where
instance ToAsyncWrapped ExceptionalMessage where
toAsyncWrapped = AsyncWrappedExceptionalMessage
instance ToAsyncWrapped AsyncReply where
toAsyncWrapped = AsyncWrappedAsyncReply
instance ToAsyncWrapped AsyncMessage where
toAsyncWrapped = AsyncWrappedAsyncMessage
-- Data types used for parameters when communicating with the remote.
-- All are serializable.
@ -411,12 +400,17 @@ type Setting = String
type Description = String
type ProtocolVersion = Int
type Size = Maybe Integer
type JobId = String
type WrappedMsg = String
newtype JobId = JobId Integer
deriving (Eq, Ord, Show)
supportedProtocolVersions :: [ProtocolVersion]
supportedProtocolVersions = [1]
instance Proto.Serializable JobId where
serialize (JobId n) = show n
deserialize = JobId <$$> readMaybe
instance Proto.Serializable Direction where
serialize Upload = "STORE"
serialize Download = "RETRIEVE"