implement send-receive-acknowledge-retry loop for requests

This commit is contained in:
Trolli Schmittlauch 2020-05-19 17:53:13 +02:00
parent 0e6f126b3b
commit 8d18f952cd

View file

@ -121,41 +121,71 @@ markCacheEntryAsVerified timestamp = Map.adjust adjustFunc
-- ====== message send and receive operations ====== -- ====== message send and receive operations ======
requestQueryID :: NodeState -> NodeID -> IO NodeState --requestQueryID :: NodeState -> NodeID -> IO NodeState
-- 1. do a local lookup for the l closest nodes ---- 1. do a local lookup for the l closest nodes
-- 2. create l sockets ---- 2. create l sockets
-- 3. send a message async concurrently to all l nodes ---- 3. send a message async concurrently to all l nodes
-- 4. collect the results, insert them into cache ---- 4. collect the results, insert them into cache
-- 5. repeat until FOUND (problem: new entries not necessarily already in cache, explicitly compare with closer results) ---- 5. repeat until FOUND (problem: new entries not necessarily already in cache, explicitly compare with closer results)
requestQueryID ns targetID = do --requestQueryID ns targetID = do
cacheSnapshot <- readIORef $ getNodeCacheRef ns -- cacheSnapshot <- readIORef $ getNodeCacheRef ns
let localResult = queryLocalCache ns cacheSnapshot (fromMaybe 1 $ getLNumBestNodes ns) targetID -- let localResult = queryLocalCache ns cacheSnapshot (fromMaybe 1 $ getLNumBestNodes ns) targetID
-- FOUND can only be returned if targetID is owned by local node -- -- FOUND can only be returned if targetID is owned by local node
case localResult of -- case localResult of
FOUND thisNode -> return thisNode -- FOUND thisNode -> return thisNode
FORWARD nodeSet -> -- FORWARD nodeSet ->
sockets <- mapM (\resultNode -> mkSendSocket (domain result) (dhtPort resultNode)) $ Set.toList nodeSet -- sockets <- mapM (\resultNode -> mkSendSocket (domain result) (dhtPort resultNode)) $ Set.toList nodeSet
-- ToDo: process results immediately instead of waiting for the last one to finish, see https://stackoverflow.com/a/38815224/9198613 -- -- ToDo: process results immediately instead of waiting for the last one to finish, see https://stackoverflow.com/a/38815224/9198613
responses = mapM -- responses = mapM
-- | Generic function for sending a request over a connected socket and collecting the response.
-- Serialises the message and tries to deliver its parts for a number of attempts within a specified timeout.
sendRequestTo :: Int -- ^ timeout in seconds sendRequestTo :: Int -- ^ timeout in seconds
-> Int -- ^ number of retries -> Int -- ^ number of retries
-> FediChordMessage -- ^ the message to be sent -> FediChordMessage -- ^ the message to be sent
-> Socket -- ^ connected socket to use for sending -> Socket -- ^ connected socket to use for sending
-> IO (Set.Set FediChordMessage) -- ^ responses -> IO (Set.Set FediChordMessage) -- ^ responses
sendRequestTo timeout attempts msg sock = do sendRequestTo timeoutMillis numAttempts msg sock = do
let requests = serialiseMessage 1200 msg let requests = serialiseMessage 1200 msg
-- create a queue for passing received response messages back, even after a timeout
responseQ <- newTBQueueIO $ 2*maximumParts -- keep room for duplicate packets
-- start sendAndAck with timeout
-- ToDo: make attempts and timeout configurable -- ToDo: make attempts and timeout configurable
attempts 3 . timeout 5000 $ do attempts numAttempts . timeout timeoutMillis $ sendAndAck responseQ sock requests
-- after timeout, check received responses, delete them from unacked message set/ map and rerun senAndAck with that if necessary.
recvdParts <- atomically $ flushTBQueue responseQ
-- PLACEHOLDER
pure Set.empty
where where
-- state reingeben: state = noch nicht geackte messages, result = responses -- state reingeben: state = noch nicht geackte messages, result = responses
sendAndAck :: Socket -> StateT (Map.Map Integer BS.ByteString) IO (Set.Set FediChordMessage) sendAndAck :: TBQueue FediChordMessage -- ^ the queue for putting in the received responses
sendAndAck sock = do -> Socket -- ^ the socket used for sending and receiving for this particular remote node
remainingSends <- get -> Map.Map Integer BS.ByteString -- ^ the remaining unacked request parts
-> IO ()
sendAndAck responseQueue sock remainingSends = do
sendMany sock $ Map.elems remainingSends sendMany sock $ Map.elems remainingSends
-- timeout pro receive socket, danach catMaybes -- if all requests have been acked/ responded to, return prematurely
-- wichtig: Pakete können dupliziert werden, dh es können mehr ACKs als gesendete parts ankommen recvLoop responseQueue remainingSends Set.empty
replicateM recvLoop :: TBQueue FediChordMessage -- ^ the queue for putting in the received responses
-> Map.Map Integer BS.ByteString -- ^ the remaining unacked request parts
-> Set.Set Integer -- ^ already received response part numbers
-> IO ()
recvLoop responseQueue remainingSends' receivedPartNums = do
-- 65535 is maximum length of UDP packets, as long as
-- no IPv6 jumbograms are used
response <- deserialiseMessage <$> recv sock 65535
case response of
-- drop errors
Left _ -> recvLoop responseQueue remainingSends' receivedPartNums
Right msg -> do
atomically $ writeTBQueue responseQueue msg
let
newRemaining = Map.delete (part msg) remainingSends'
newReceivedParts = Set.insert (part msg) receivedPartNums
-- ToDo: handle responses with more parts than the request
if Map.null newRemaining && Set.size receivedPartNums == fromIntegral (parts msg)
then pure ()
else recvLoop responseQueue newRemaining receivedPartNums