@@ -17,6 +17,7 @@ module Network.Transport.TCP ( -- * Main API
17 createTransport
17 createTransport
18 -- * Internals (exposed for unit tests)
18 -- * Internals (exposed for unit tests)
19 , createTransportExposeInternals
19 , createTransportExposeInternals
20+ , TransportInternals(..)
20 , EndPointId
21 , EndPointId
21 , encodeEndPointAddress
22 , encodeEndPointAddress
22 , decodeEndPointAddress
23 , decodeEndPointAddress
@@ -74,8 +75,18 @@ import Control.Concurrent.MVar ( MVar
75import Control.Category ((>>>))
76import Control.Category ((>>>))
76import Control.Applicative ((<$>))
77import Control.Applicative ((<$>))
77−import Control.Monad (forM_, when, unless)
78+import Control.Monad (when, unless, join)
79+import Control.Monad.Error (ErrorT(..), runErrorT)
78−import Control.Exception (IOException, SomeException, handle, throw, try, bracketOnError, mask_)
80+import Control.Exception ( IOException
79import Data.IORef (IORef, newIORef, writeIORef, readIORef)
90import Data.IORef (IORef, newIORef, writeIORef, readIORef)
80import Data.ByteString (ByteString)
91import Data.ByteString (ByteString)
81import qualified Data.ByteString as BS (concat)
92import qualified Data.ByteString as BS (concat)
@@ -89,6 +100,7 @@ import Data.Map (Map)
89import qualified Data.Map as Map (empty, elems)
100import qualified Data.Map as Map (empty, elems)
90import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
101import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
91import qualified Data.Accessor.Container as DAC (mapMaybe, intMapMaybe)
102import qualified Data.Accessor.Container as DAC (mapMaybe, intMapMaybe)
103+import Data.Foldable (forM_)
92import System.IO (hPutStrLn, stderr)
104import System.IO (hPutStrLn, stderr)
94-- $design
106-- $design
@@ -229,6 +241,7 @@ data ValidLocalEndPointState = ValidLocalEndPointState
229 , _nextCtrlRequestId :: ControlRequestId
241 , _nextCtrlRequestId :: ControlRequestId
230 , _localConnections :: Map EndPointAddress RemoteEndPoint
242 , _localConnections :: Map EndPointAddress RemoteEndPoint
231 , _internalThreads :: [ThreadId]
243 , _internalThreads :: [ThreadId]
244+ , _nextRemoteId :: Int
234-- A remote endpoint has incoming and outgoing connections, and when the total
247-- A remote endpoint has incoming and outgoing connections, and when the total
@@ -270,15 +283,31 @@ data ValidLocalEndPointState = ValidLocalEndPointState
270-- Since we don't do concurrent reads from the same socket we don't need to
283-- Since we don't do concurrent reads from the same socket we don't need to
271-- take the lock when reading from the socket.
284-- take the lock when reading from the socket.
273−-- Moreover, we insist on the invariant (INV-CLOSE) that whenever we put an
286+-- Invariants:
274−-- endpoint in closed state we remove that endpoint from localConnections
288+-- INV-CLOSE: Whenever we put an endpoint in closed state we remove that
275−-- first, so that if a concurrent thread reads the mvar, finds EndPointClosed,
289+-- endpoint from localConnections first, so that if a concurrent thread reads
290+-- the mvar, finds EndPointClosed, and then looks up the endpoint in
276−-- and then looks up the endpoint in localConnections it is guaranteed to
291+-- localConnections it is guaranteed to either find a different remote
277−-- either find a different remote endpoint, or else none at all.
292+-- endpoint, or else none at all.
293+-- INV-RESOLVE: Whenever we move a endpoint from Closing to Closed state, we
294+-- signal on the corresponding MVar only *after* the endpoint has been put in
295+-- Closed state. This way when to threads try to resolve they don't both
296+-- attempt to write to the "resolved" MVar. TODO: Make sure that this
297+-- invariant is adhered too.
298+-- INV-LOST: If a send or recv fails, or a socket is closed unexpectedly, we
299+-- first put the remote endpoint in Closing or Closed state, and then send a
300+-- EventConnectionLost event. This guarantees that we only send this event
302+-- INV-CLOSING: An endpoint in closing state is for all intents and purposes
303+-- closed; that is, we shouldn't do any 'send's on it (although 'recv' is
304+-- acceptable, of course -- as we are waiting for the remote endpoint to
305+-- confirm or deny the request).
279data RemoteEndPoint = RemoteEndPoint
307data RemoteEndPoint = RemoteEndPoint
280 { remoteAddress :: EndPointAddress
308 { remoteAddress :: EndPointAddress
281 , remoteState :: MVar RemoteState
309 , remoteState :: MVar RemoteState
284data RemoteState =
313data RemoteState =
@@ -313,6 +342,14 @@ data ConnectionRequestResponse =
313 | ConnectionRequestCrossed -- ^ /A/s request crossed with a request from /B/ (see protocols)
342 | ConnectionRequestCrossed -- ^ /A/s request crossed with a request from /B/ (see protocols)
314 deriving (Enum, Bounded, Show)
343 deriving (Enum, Bounded, Show)
345+-- Internal functionality we expose for unit testing
346+data TransportInternals = TransportInternals
347+ { -- | The ID of the thread that listens for new incoming connections
348+ transportThread :: ThreadId
349+ -- | Find the socket between a local and a remote endpoint
350+ , socketBetween :: EndPointAddress -> EndPointAddress -> IO (Either String N.Socket)
316--------------------------------------------------------------------------------
353--------------------------------------------------------------------------------
317-- Top-level functionality --
354-- Top-level functionality --
318--------------------------------------------------------------------------------
355--------------------------------------------------------------------------------
@@ -330,7 +367,7 @@ createTransport host port = do
330 return $ Right transport
367 return $ Right transport
332-- | You should probably not use this function (used for unit testing only)
369-- | You should probably not use this function (used for unit testing only)
333−createTransportExposeInternals :: N.HostName -> N.ServiceName -> IO (Either IOException (Transport, ThreadId))
370+createTransportExposeInternals :: N.HostName -> N.ServiceName -> IO (Either IOException (Transport, TransportInternals))
334createTransportExposeInternals host port = do
371createTransportExposeInternals host port = do
335 state <- newMVar . TransportValid $ ValidTransportState
372 state <- newMVar . TransportValid $ ValidTransportState
336 { _localEndPoints = Map.empty
373 { _localEndPoints = Map.empty
@@ -344,11 +381,13 @@ createTransportExposeInternals host port = do
344 -- For a discussion of the use of N.sOMAXCONN, see
381 -- For a discussion of the use of N.sOMAXCONN, see
345 -- http://tangentsoft.net/wskfaq/advanced.html#backlog
382 -- http://tangentsoft.net/wskfaq/advanced.html#backlog
346 -- http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html
383 -- http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html
347− transportThread <- forkServer host port N.sOMAXCONN (terminationHandler transport) (handleConnectionRequest transport)
384+ tid <- forkServer host port N.sOMAXCONN (terminationHandler transport) (handleConnectionRequest transport)
348 return ( Transport { newEndPoint = apiNewEndPoint transport
385 return ( Transport { newEndPoint = apiNewEndPoint transport
349− , closeTransport = apiCloseTransport transport (Just transportThread) [EndPointClosed]
386+ , closeTransport = apiCloseTransport transport (Just tid) [EndPointClosed]
351− , transportThread
388+ , TransportInternals { transportThread = tid
389+ , socketBetween = internalSocketBetween transport
354 terminationHandler :: TCPTransport -> SomeException -> IO ()
393 terminationHandler :: TCPTransport -> SomeException -> IO ()
@@ -366,22 +405,13 @@ apiCloseTransport transport mTransportThread evs = do
366 mTSt <- modifyMVar (transportState transport) $ \st -> case st of
405 mTSt <- modifyMVar (transportState transport) $ \st -> case st of
367 TransportValid vst -> return (TransportClosed, Just vst)
406 TransportValid vst -> return (TransportClosed, Just vst)
368 TransportClosed -> return (TransportClosed, Nothing)
407 TransportClosed -> return (TransportClosed, Nothing)
371− -- Transport already closed
373− Just tSt -> do
408+ forM_ mTSt $ \tSt -> do
374− mapM_ (apiCloseEndPoint transport evs) (Map.elems $ tSt ^. localEndPoints)
409+ mapM_ (apiCloseEndPoint transport evs) (Map.elems $ tSt ^. localEndPoints)
375− case mTransportThread of
410+ forM_ mTransportThread $ \tid -> do
376− Just transportThread ->
377− -- This will invoke the termination handler, which in turn will call
411+ -- This will invoke the termination handler, which in turn will call
378− -- apiCloseTransport again, but at that point the transport is
412+ -- apiCloseTransport again, but at that point the transport is
379− -- already in closed state so at that point we return immediately
413+ -- already in closed state so at that point we return immediately
380− killThread transportThread
414+ killThread tid
382− -- This will happen is apiCloseTransport was invoked *because* the
383− -- transport thread died (in which case we don't need to kill it)
386-- | Create a new endpoint
416-- | Create a new endpoint
387apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
417apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
@@ -412,8 +442,8 @@ apiConnect ourEndPoint theirAddress _reliability _hints = try $ do
412 -- connAlive can be an IORef rather than an MVar because it is protected by
442 -- connAlive can be an IORef rather than an MVar because it is protected by
413 -- the remoteState MVar. We don't need the overhead of locking twice.
443 -- the remoteState MVar. We don't need the overhead of locking twice.
414 connAlive <- newIORef True
444 connAlive <- newIORef True
415− return $ Connection { send = apiSend theirEndPoint connId connAlive
445+ return $ Connection { send = apiSend (ourEndPoint, theirEndPoint) connId connAlive
416− , close = apiClose theirEndPoint connId connAlive
446+ , close = apiClose (ourEndPoint, theirEndPoint) connId connAlive
419-- | Close a connection
449-- | Close a connection
@@ -422,53 +452,49 @@ apiConnect ourEndPoint theirAddress _reliability _hints = try $ do
422-- GUARANTEE: If the connection is alive on entry then the remote endpoint will
452-- GUARANTEE: If the connection is alive on entry then the remote endpoint will
423-- either be RemoteEndPointValid or RemoteEndPointClosing. Otherwise, the state
453-- either be RemoteEndPointValid or RemoteEndPointClosing. Otherwise, the state
424-- of the remote endpoint will not be changed.
454-- of the remote endpoint will not be changed.
425−apiClose :: RemoteEndPoint -> ConnectionId -> IORef Bool -> IO ()
455+apiClose :: EndPointPair -> ConnectionId -> IORef Bool -> IO ()
426−apiClose theirEndPoint connId connAlive = void . tryIO $ do
456+apiClose (ourEndPoint, theirEndPoint) connId connAlive = void . tryIO $ do
427− modifyMVar_ (remoteState theirEndPoint) $ \st -> case st of
457+ modifyRemoteState_ (ourEndPoint, theirEndPoint)
458+ -- RemoteEndPointInvalid
459+ (\_ -> fail "apiClose RELY violation")
428− RemoteEndPointValid vst -> do
460+ -- RemoteEndPointValid
429 alive <- readIORef connAlive
462 alive <- readIORef connAlive
430 if alive
463 if alive
432 writeIORef connAlive False
465 writeIORef connAlive False
433− -- Possible exception caught top-level
434 sendOn vst [encodeInt32 CloseConnection, encodeInt32 connId]
466 sendOn vst [encodeInt32 CloseConnection, encodeInt32 connId]
435 return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ vst)
467 return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ vst)
437− return (RemoteEndPointValid vst)
469+ return (RemoteEndPointValid vst))
470+ -- RemoteEndPointClosing
471+ (\(resolved, vst) -> return $ RemoteEndPointClosing resolved vst)
472+ -- RemoteEndPoinClosed
438− RemoteEndPointClosed -> do
473+ (return $ RemoteEndPointClosed)
440− RemoteEndPointClosing _ _ ->
441− -- See discussion in apiSend why we must allow for this case
443− RemoteEndPointInvalid _ ->
444− fail "apiClose RELY violation"
445− closeIfUnused theirEndPoint
474+ closeIfUnused (ourEndPoint, theirEndPoint)
447-- | Send data across a connection
476-- | Send data across a connection
449-- RELY: The endpoint must not be in invalid state.
478-- RELY: The endpoint must not be in invalid state.
450-- GUARANTEE: The state of the remote endpoint will not be changed.
479-- GUARANTEE: The state of the remote endpoint will not be changed.
451−apiSend :: RemoteEndPoint -> ConnectionId -> IORef Bool -> [ByteString] -> IO (Either (TransportError SendErrorCode) ())
480+apiSend :: EndPointPair -> ConnectionId -> IORef Bool -> [ByteString] -> IO (Either (TransportError SendErrorCode) ())
452−apiSend theirEndPoint connId connAlive payload = do
481+apiSend (ourEndPoint, theirEndPoint) connId connAlive payload = do
453− withMVar (remoteState theirEndPoint) $ \st -> case st of
482+ join <$> (try . mapExceptionIO sendFailed $ modifyRemoteState (ourEndPoint, theirEndPoint)
483+ -- RemoteEndPointInvalid
484+ (\_ -> fail "apiSend RELY violation")
454− RemoteEndPointValid vst -> do
485+ -- RemoteEndPointValid
455 alive <- readIORef connAlive
487 alive <- readIORef connAlive
456− try $ if alive
488+ if alive
457− then mapExceptionIO sendFailed $ sendOn vst (encodeInt32 connId : prependLength payload)
490+ sendOn vst (encodeInt32 connId : prependLength payload)
458− else throw $ TransportError SendClosed "Connection closed"
491+ return (RemoteEndPointValid vst, Right $ ())
459− RemoteEndPointClosed -> do
492+ else
460− return . Left $ TransportError SendClosed "Connection lost"
493+ return (RemoteEndPointValid vst, Left $ TransportError SendClosed "Connection closed"))
461− RemoteEndPointClosing _ _ ->
494+ -- RemoteEndPointClosing
462− -- The only way for the endpoint to be in closing state, while a
495+ (\(resolved, vst) -> return (RemoteEndPointClosing resolved vst, Left $ TransportError SendClosed "Connection lost"))
463− -- connection is still active, is for the application to call 'close'
496+ -- RemoteEndPointClosed
464− -- on the connection triggering "garbage collection" of the TCP channel
465− -- (CloseSocket request to the remote endpoint) which hasn't completed
466− -- yet. Even if the remote endpoint comes back with "please don't
467− -- close", this still means that our *outgoing* connection has been
469− return . Left $ TransportError SendClosed "Connection lost"
497+ (return (RemoteEndPointClosed, Left $ TransportError SendClosed "Connection lost")))
470− RemoteEndPointInvalid _ ->
471− error "apiSend RELY violation"
473 sendFailed :: IOException -> TransportError SendErrorCode
499 sendFailed :: IOException -> TransportError SendErrorCode
474 sendFailed = TransportError SendFailed . show
500 sendFailed = TransportError SendFailed . show
@@ -488,14 +514,11 @@ apiCloseEndPoint transport evs ourEndPoint = do
488 return (LocalEndPointClosed, Just vst)
514 return (LocalEndPointClosed, Just vst)
489 LocalEndPointClosed ->
515 LocalEndPointClosed ->
490 return (LocalEndPointClosed, Nothing)
516 return (LocalEndPointClosed, Nothing)
491− case mOurState of
517+ forM_ mOurState $ \vst -> do
492− Nothing -> -- Already closed
495− -- Close all endpoints and kill all threads
518+ -- Close all endpoints and kill all threads
496− forM_ (Map.elems $ vst ^. localConnections) $ tryCloseRemoteSocket
519+ forM_ (Map.elems $ vst ^. localConnections) $ tryCloseRemoteSocket
497− forM_ (vst ^. internalThreads) killThread
520+ forM_ (vst ^. internalThreads) killThread
498− forM_ evs $ writeChan (localChannel ourEndPoint)
521+ forM_ evs $ writeChan (localChannel ourEndPoint)
500 -- Close the remote socket and return the set of all incoming connections
523 -- Close the remote socket and return the set of all incoming connections
501 tryCloseRemoteSocket :: RemoteEndPoint -> IO ()
524 tryCloseRemoteSocket :: RemoteEndPoint -> IO ()
@@ -552,6 +575,43 @@ connectToSelf ourEndPoint = do
552 when alive $ writeChan ourChan (ConnectionClosed connId)
575 when alive $ writeChan ourChan (ConnectionClosed connId)
553 return False
576 return False
578+--------------------------------------------------------------------------------
579+-- Functions from TransportInternals --
580+--------------------------------------------------------------------------------
582+-- Find a socket between two endpoints
583+internalSocketBetween :: TCPTransport -- ^ Transport
584+ -> EndPointAddress -- ^ Local endpoint
585+ -> EndPointAddress -- ^ Remote endpoint
586+ -> IO (Either String N.Socket)
587+internalSocketBetween transport ourAddress theirAddress = runErrorT $ do
588+ ourEndPoint <- ErrorT $ do
589+ withMVar (transportState transport) $ \st -> case st of
591+ return . Left $ "Transport closed"
592+ TransportValid vst -> do
593+ case vst ^. localEndPointAt ourAddress of
594+ Nothing -> return . Left $ "Local endpoint not found"
595+ Just ep -> return . Right $ ep
596+ theirEndPoint <- ErrorT $ do
597+ withMVar (localState ourEndPoint) $ \st -> case st of
598+ LocalEndPointClosed ->
599+ return . Left $ "Local endpoint closed"
600+ LocalEndPointValid vst -> do
601+ case vst ^. localConnectionTo theirAddress of
602+ Nothing -> return . Left $ "Remote endpoint not found"
603+ Just ep -> return . Right $ ep
605+ withMVar (remoteState theirEndPoint) $ \st -> case st of
606+ RemoteEndPointValid vst ->
607+ return . Right $ remoteSocket vst
608+ RemoteEndPointClosing _ _ ->
609+ return . Left $ "Remote endpoint closing"
610+ RemoteEndPointClosed ->
611+ return . Left $ "Remote endpoint closed"
612+ RemoteEndPointInvalid _ ->
613+ return . Left $ "Remote endpoint invalid"
555--------------------------------------------------------------------------------
615--------------------------------------------------------------------------------
556-- Lower level functionality --
616-- Lower level functionality --
557--------------------------------------------------------------------------------
617--------------------------------------------------------------------------------
@@ -568,6 +628,7 @@ createLocalEndPoint transport = do
568 , _nextCtrlRequestId = 0
628 , _nextCtrlRequestId = 0
569 , _localConnections = Map.empty
629 , _localConnections = Map.empty
570 , _internalThreads = []
630 , _internalThreads = []
572 modifyMVar (transportState transport) $ \st -> case st of
633 modifyMVar (transportState transport) $ \st -> case st of
573 TransportValid vst -> do
634 TransportValid vst -> do
@@ -635,23 +696,15 @@ requestConnectionTo ourEndPoint theirAddress = go
637 RemoteEndPointValid _ -> do
698 RemoteEndPointValid _ -> do
638− -- On a failure we decrement the refcount again and return an error.
639− -- The only state the remote endpoint can be in at this point is
640− -- valid. As mentioned above, we can rely on the endpoint being in
641− -- valid state at this point.
642− let failureHandler :: IOException -> IO b
643− failureHandler err = do
644− modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
645− return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ ep)
646− throw $ TransportError ConnectFailed (show err)
648 -- Do the actual connection request. This blocks until the remote
699 -- Do the actual connection request. This blocks until the remote
649 -- endpoint replies (but note that we don't hold any locks at this
700 -- endpoint replies (but note that we don't hold any locks at this
650− -- point)
701+ -- point). Note that doRemoteRequest may throw an error if the send
702+ -- fails, and if it does, it will have put the remote endpoint in
651− reply <- handle failureHandler $ doRemoteRequest (ourEndPoint, theirEndPoint) RequestConnectionId
704+ reply <- mapExceptionIO connectFailed $ doRemoteRequest (ourEndPoint, theirEndPoint) RequestConnectionId
652 case decodeInt32 . BS.concat $ reply of
705 case decodeInt32 . BS.concat $ reply of
653 Nothing ->
706 Nothing ->
654− failureHandler (userError "Invalid integer")
707+ throw (connectFailed $ userError "Invalid integer")
655 Just cid -> do
708 Just cid -> do
656 return (theirEndPoint, cid)
709 return (theirEndPoint, cid)
@@ -672,8 +725,9 @@ requestConnectionTo ourEndPoint theirAddress = go
672 theirState <- newEmptyMVar
725 theirState <- newEmptyMVar
673 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
726 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
674 , remoteState = theirState
727 , remoteState = theirState
728+ , remoteId = vst ^. nextRemoteId
676− return (LocalEndPointValid . (localConnectionTo theirAddress ^= Just theirEndPoint) $ vst, (theirEndPoint, True))
730+ return (LocalEndPointValid . (localConnectionTo theirAddress ^= Just theirEndPoint) . (nextRemoteId ^: (+ 1)) $ vst, (theirEndPoint, True))
678 -- The only way for forkEndPointThread to fail is if the local endpoint
732 -- The only way for forkEndPointThread to fail is if the local endpoint
679 -- gets closed. This error will be caught elsewhere, so we ignore it
733 -- gets closed. This error will be caught elsewhere, so we ignore it
@@ -685,6 +739,9 @@ requestConnectionTo ourEndPoint theirAddress = go
685 ourState :: MVar LocalEndPointState
739 ourState :: MVar LocalEndPointState
686 ourState = localState ourEndPoint
740 ourState = localState ourEndPoint
742+ connectFailed :: IOException -> TransportError ConnectErrorCode
743+ connectFailed = TransportError ConnectFailed . show
688-- | Set up a remote endpoint
745-- | Set up a remote endpoint
690-- RELY: The state of the remote endpoint must be uninitialized.
747-- RELY: The state of the remote endpoint must be uninitialized.
@@ -700,7 +757,7 @@ setupRemoteEndPoint (ourEndPoint, theirEndPoint) = do
700 { remoteSocket = sock
757 { remoteSocket = sock
701 , _remoteOutgoing = 0
758 , _remoteOutgoing = 0
702 , _remoteIncoming = IntSet.empty
759 , _remoteIncoming = IntSet.empty
703− , sendOn = sendMany sock
760+ , sendOn = \msg -> sendMany sock msg `onException` (tryIO $ N.sClose sock)
705 putMVar theirState (RemoteEndPointValid vst)
762 putMVar theirState (RemoteEndPointValid vst)
706 return True
763 return True
@@ -783,11 +840,20 @@ socketToEndPoint (EndPointAddress ourAddress) theirAddress = try $ do
783-- If the local endpoint is closed, do nothing
840-- If the local endpoint is closed, do nothing
784removeRemoteEndPoint :: EndPointPair -> IO ()
841removeRemoteEndPoint :: EndPointPair -> IO ()
785removeRemoteEndPoint (ourEndPoint, theirEndPoint) = do
842removeRemoteEndPoint (ourEndPoint, theirEndPoint) = do
786− modifyMVar_ (localState ourEndPoint) $ \st -> case st of
843+ modifyMVar_ ourState $ \st -> case st of
787− LocalEndPointValid vst ->
844+ LocalEndPointValid vst ->
845+ case vst ^. localConnectionTo theirAddress of
848+ Just remoteEndPoint' ->
849+ if remoteId remoteEndPoint' == remoteId theirEndPoint
788− return (LocalEndPointValid . (localConnectionTo (remoteAddress theirEndPoint) ^= Nothing) $ vst)
850+ then return (LocalEndPointValid . (localConnectionTo (remoteAddress theirEndPoint) ^= Nothing) $ vst)
789− LocalEndPointClosed ->
852+ LocalEndPointClosed ->
790− return LocalEndPointClosed
853+ return LocalEndPointClosed
855+ ourState = localState ourEndPoint
856+ theirAddress = remoteAddress theirEndPoint
792-- | Remove reference to a local endpoint from the transport state
858-- | Remove reference to a local endpoint from the transport state
@@ -817,11 +883,11 @@ decodeEndPointAddress (EndPointAddress bs) = case map BSC.unpack $ BSC.split ':'
818-- | Do a (blocking) remote request
884-- | Do a (blocking) remote request
820−-- RELY: Remote endpoint must be in valid state.
886+-- RELY: Remote endpoint must be in valid or closed state.
821-- GUARANTEE: Will not change the state of the remote endpoint.
887-- GUARANTEE: Will not change the state of the remote endpoint.
823−-- May throw IO (user) exception if the local endpoint is closed or if the send
889+-- May throw IO (user) exception if the local or the remote endpoint is closed,
824−-- fails.
890+-- or if the send fails.
825doRemoteRequest :: EndPointPair -> ControlHeader -> IO [ByteString]
891doRemoteRequest :: EndPointPair -> ControlHeader -> IO [ByteString]
826doRemoteRequest (ourEndPoint, theirEndPoint) header = do
892doRemoteRequest (ourEndPoint, theirEndPoint) header = do
827 reply <- newEmptyMVar
893 reply <- newEmptyMVar
@@ -831,24 +897,35 @@ doRemoteRequest (ourEndPoint, theirEndPoint) header = do
831 return (LocalEndPointValid . (nextCtrlRequestId ^: (+ 1)) . (pendingCtrlRequestsAt reqId ^= Just reply) $ vst, reqId)
897 return (LocalEndPointValid . (nextCtrlRequestId ^: (+ 1)) . (pendingCtrlRequestsAt reqId ^= Just reply) $ vst, reqId)
832 LocalEndPointClosed ->
898 LocalEndPointClosed ->
833 throw (userError "Local endpoint closed")
899 throw (userError "Local endpoint closed")
900+ withRemoteState (ourEndPoint, theirEndPoint)
901+ -- RemoteEndPointInvalid
902+ (\_ -> fail "doRemoteRequest RELY violation")
834− withMVar (remoteState theirEndPoint) $ \(RemoteEndPointValid vst) ->
903+ -- RemoteEndPointValid
835− sendOn vst [encodeInt32 header, encodeInt32 reqId]
904+ (\vst -> sendOn vst [encodeInt32 header, encodeInt32 reqId])
905+ -- RemoteEndPointClosing
906+ (\_ -> fail "doRemoteRequest RELY violation")
907+ -- RemoteEndPointClosed
908+ (throw (userError "Remote endpoint closed"))
836 takeMVar reply
909 takeMVar reply
838-- | Check if the remote endpoint is unused, and if so, send a CloseSocket request
911-- | Check if the remote endpoint is unused, and if so, send a CloseSocket request
839−closeIfUnused :: RemoteEndPoint -> IO ()
912+closeIfUnused :: EndPointPair -> IO ()
840−closeIfUnused theirEndPoint = modifyMVar_ (remoteState theirEndPoint) $ \st ->
913+closeIfUnused (ourEndPoint, theirEndPoint) =
841− case st of
914+ modifyRemoteState_ (ourEndPoint, theirEndPoint)
842− RemoteEndPointValid vst -> do
915+ -- RemoteEndPointInvalid
916+ (return . RemoteEndPointInvalid)
917+ -- RemoteEndPointValid
843− if vst ^. remoteOutgoing == 0 && IntSet.null (vst ^. remoteIncoming)
918+ (\vst -> if vst ^. remoteOutgoing == 0 && IntSet.null (vst ^. remoteIncoming)
844− then do
919+ then do
845− resolved <- newEmptyMVar
846− sendOn vst [encodeInt32 CloseSocket]
920+ sendOn vst [encodeInt32 CloseSocket]
921+ resolved <- newEmptyMVar
847− return (RemoteEndPointClosing resolved vst)
922+ return $ RemoteEndPointClosing resolved vst
849− return st
924+ return $ RemoteEndPointValid vst)
850− _ -> do
925+ -- RemoteEndPointClosing
851− return st
926+ (return . uncurry RemoteEndPointClosing)
927+ -- RemoteEndPointClosed
928+ (return RemoteEndPointClosed)
853-- | Fork a new thread and store its ID as part of the transport state
930-- | Fork a new thread and store its ID as part of the transport state
@@ -886,6 +963,109 @@ forkEndPointThread ourEndPoint p =
886 ourState :: MVar LocalEndPointState
963 ourState :: MVar LocalEndPointState
887 ourState = localState ourEndPoint
964 ourState = localState ourEndPoint
966+-- | Like modifyMVar, but if an exception occurs don't restore the remote
967+-- endpoint to its original value but close it instead
968+modifyRemoteState :: EndPointPair -- ^ Local and remote endpoint
969+ -> (TransportError ConnectErrorCode -> IO (RemoteState, a)) -- ^ Case for RemoteEndPointInvalid
970+ -> (ValidRemoteEndPointState -> IO (RemoteState, a)) -- ^ Case for RemoteEndPointValid
971+ -> ((MVar (), ValidRemoteEndPointState) -> IO (RemoteState, a)) -- ^ Case for RemoteEndPointClosing
972+ -> IO (RemoteState, a) -- ^ Case for RemoteEndPointClosed
974+modifyRemoteState (ourEndPoint, theirEndPoint) caseInvalid caseValid caseClosing caseClosed = do
975+ mask $ \restore -> do
976+ st <- takeMVar theirState
978+ RemoteEndPointValid vst -> do
979+ mResult <- try $ restore (caseValid vst)
981+ Right (st', a) -> do
982+ putMVar theirState st'
985+ handleException ex vst
986+ -- The other cases are less interesting, because unless the endpoint is
987+ -- in Valid state we're not supposed to do any IO on it
988+ RemoteEndPointClosing resolved vst -> do
989+ (st', a) <- restore (caseClosing (resolved, vst)) `onException` putMVar theirState st
990+ putMVar theirState st'
992+ RemoteEndPointInvalid err -> do
993+ (st', a) <- restore (caseInvalid err) `onException` putMVar theirState st
994+ putMVar theirState st'
996+ RemoteEndPointClosed -> do
997+ (st', a) <- restore caseClosed `onException` putMVar theirState st
998+ putMVar theirState st'
1001+ theirState :: MVar RemoteState
1002+ theirState = remoteState theirEndPoint
1004+ handleException :: SomeException -> ValidRemoteEndPointState -> IO a
1005+ handleException ex vst = do
1006+ -- We need to remove the remote endpoint from the local endpoint before
1007+ -- putting it in Closed state (by INV-CLOSE), but we don't want to hold
1008+ -- two locks at the same time. So we put it in closing state first ..
1009+ resolved <- newEmptyMVar
1010+ putMVar theirState (RemoteEndPointClosing resolved vst)
1011+ -- .. then remove it from the local endpoint ..
1012+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1013+ -- .. close it and notify the local endpoint we lost the connection ..
1014+ mIncoming <- closeRemoteEndPoint
1015+ forM_ mIncoming $ \incoming -> do
1016+ let err = TransportError (EventConnectionLost (remoteAddress theirEndPoint) incoming) (show ex)
1017+ writeChan (localChannel ourEndPoint) $ ErrorEvent err
1018+ -- .. and finally rethrow the exception
1021+ -- Returns the set of incoming connections if we closed the connection, or
1022+ -- 'Nothing' if the endpoint was already closed
1023+ closeRemoteEndPoint :: IO (Maybe [ConnectionId])
1024+ closeRemoteEndPoint = do
1025+ st <- takeMVar theirState
1027+ RemoteEndPointClosing resolved vst -> do
1028+ putMVar theirState RemoteEndPointClosed
1029+ putMVar resolved ()
1030+ return . Just . IntSet.elems $ vst ^. remoteIncoming
1031+ RemoteEndPointClosed -> do
1032+ putMVar theirState RemoteEndPointClosed
1035+ fail "the impossible happened"
1037+-- | Like 'modifyRemoteState' but without a return value
1038+modifyRemoteState_ :: EndPointPair -- ^ Local and remote endpoint
1039+ -> (TransportError ConnectErrorCode -> IO RemoteState) -- ^ Case for RemoteEndPointInvalid
1040+ -> (ValidRemoteEndPointState -> IO RemoteState) -- ^ Case for RemoteEndPointValid
1041+ -> ((MVar (), ValidRemoteEndPointState) -> IO RemoteState) -- ^ Case for RemoteEndPointClosing
1042+ -> IO RemoteState -- ^ Case for RemoteEndPointClosed
1044+modifyRemoteState_ (ourEndPoint, theirEndPoint) caseInvalid caseValid caseClosing caseClosed =
1045+ modifyRemoteState (ourEndPoint, theirEndPoint)
1051+ u :: IO a -> IO (a, ())
1052+ u p = p >>= \a -> return (a, ())
1054+-- | Like 'modifyRemoteState' but without the ability to change the state
1055+withRemoteState :: EndPointPair -- ^ Local and remote endpoint
1056+ -> (TransportError ConnectErrorCode -> IO a) -- ^ Case for RemoteEndPointInvalid
1057+ -> (ValidRemoteEndPointState -> IO a) -- ^ Case for RemoteEndPointValid
1058+ -> ((MVar (), ValidRemoteEndPointState) -> IO a) -- ^ Case for RemoteEndPointClosing
1059+ -> IO a -- ^ Case for RemoteEndPointClosed
1061+withRemoteState (ourEndPoint, theirEndPoint) caseInvalid caseValid caseClosing caseClosed = do
1062+ modifyRemoteState (ourEndPoint, theirEndPoint)
1063+ (\err -> (,) (RemoteEndPointInvalid err) <$> caseInvalid err)
1064+ (\vst -> (,) (RemoteEndPointValid vst) <$> caseValid vst)
1065+ (\(resolved, vst) -> (,) (RemoteEndPointClosing resolved vst) <$> caseClosing (resolved, vst))
1066+ ((,) RemoteEndPointClosed <$> caseClosed)
889--------------------------------------------------------------------------------
1069--------------------------------------------------------------------------------
890-- Incoming requests --
1070-- Incoming requests --
891--------------------------------------------------------------------------------
1071--------------------------------------------------------------------------------
@@ -928,8 +1108,9 @@ handleConnectionRequest transport sock = handle tryCloseSocket $ do
928 theirState <- newEmptyMVar
1108 theirState <- newEmptyMVar
929 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
1109 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
930 , remoteState = theirState
1110 , remoteState = theirState
1111+ , remoteId = vst ^. nextRemoteId
932− return (LocalEndPointValid . (localConnectionTo theirAddress ^= Just theirEndPoint) $ vst, (False, theirEndPoint))
1113+ return (LocalEndPointValid . (localConnectionTo theirAddress ^= Just theirEndPoint) . (nextRemoteId ^: (+ 1)) $ vst, (False, theirEndPoint))
933 Just theirEndPoint ->
1114 Just theirEndPoint ->
934 return (st, (localAddress ourEndPoint < theirAddress, theirEndPoint))
1115 return (st, (localAddress ourEndPoint < theirAddress, theirEndPoint))
935 LocalEndPointClosed ->
1116 LocalEndPointClosed ->
@@ -958,11 +1139,7 @@ handleConnectionRequest transport sock = handle tryCloseSocket $ do
958 -- been recorded as part of the remote endpoint. Either way, we no longer
1139 -- been recorded as part of the remote endpoint. Either way, we no longer
959 -- have to worry about closing the socket on receiving an asynchronous
1140 -- have to worry about closing the socket on receiving an asynchronous
960 -- exception from this point forward.
1141 -- exception from this point forward.
964− Just theirEndPoint ->
965− handleIncomingMessages (ourEndPoint, theirEndPoint)
1142+ forM_ mEndPoint $ handleIncomingMessages . (,) ourEndPoint
967 tryCloseSocket :: IOException -> IO ()
1144 tryCloseSocket :: IOException -> IO ()
968 tryCloseSocket _ = void . tryIO $ N.sClose sock
1145 tryCloseSocket _ = void . tryIO $ N.sClose sock
@@ -1076,7 +1253,7 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
1076 writeChan ourChannel (ConnectionClosed cid)
1253 writeChan ourChannel (ConnectionClosed cid)
1077 modifyMVar_ theirState $ \(RemoteEndPointValid vst) ->
1254 modifyMVar_ theirState $ \(RemoteEndPointValid vst) ->
1078 return (RemoteEndPointValid . (remoteIncoming ^: IntSet.delete cid) $ vst)
1255 return (RemoteEndPointValid . (remoteIncoming ^: IntSet.delete cid) $ vst)
1079− closeIfUnused theirEndPoint
1256+ closeIfUnused (ourEndPoint, theirEndPoint)
1081 -- Close the socket (if we don't have any outgoing connections)
1258 -- Close the socket (if we don't have any outgoing connections)
1082 closeSocket :: N.Socket -> IO Bool
1259 closeSocket :: N.Socket -> IO Bool
@@ -1138,24 +1315,18 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
1138 prematureExit sock err = do
1315 prematureExit sock err = do
1139 N.sClose sock
1316 N.sClose sock
1140 removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1317 removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1141− mUnclosedConnections <- modifyMVar theirState $ \st ->
1318+ modifyMVar_ theirState $ \st ->
1142 case st of
1319 case st of
1143 RemoteEndPointInvalid _ ->
1320 RemoteEndPointInvalid _ ->
1144 error "handleIncomingMessages RELY violation"
1321 error "handleIncomingMessages RELY violation"
1145− RemoteEndPointValid vst ->
1322+ RemoteEndPointValid vst -> do
1146− return (RemoteEndPointClosed, Just $ vst ^. remoteIncoming)
1323+ writeChan ourChannel . ErrorEvent $
1324+ TransportError (EventConnectionLost (remoteAddress theirEndPoint) (IntSet.elems $ vst ^. remoteIncoming)) (show err)
1325+ return RemoteEndPointClosed
1147 RemoteEndPointClosing _ _ ->
1326 RemoteEndPointClosing _ _ ->
1148− return (RemoteEndPointClosed, Nothing)
1327+ return RemoteEndPointClosed
1149 RemoteEndPointClosed ->
1328 RemoteEndPointClosed ->
1150− return (st, Nothing)
1329+ return RemoteEndPointClosed
1152− -- We send the connection lost message even if unclosedConnections is the
1153− -- empty set, because *outgoing* connections will be broken too now
1154− case mUnclosedConnections of
1157− Just unclosedConnections -> writeChan ourChannel . ErrorEvent $
1158− TransportError (EventConnectionLost (remoteAddress theirEndPoint) (IntSet.elems unclosedConnections)) (show err)
1160-- | Get the next connection ID
1331-- | Get the next connection ID
@@ -1202,6 +1373,9 @@ localConnections = accessor _localConnections (\es st -> st { _localConnections
1202internalThreads :: Accessor ValidLocalEndPointState [ThreadId]
1373internalThreads :: Accessor ValidLocalEndPointState [ThreadId]
1203internalThreads = accessor _internalThreads (\ts st -> st { _internalThreads = ts })
1374internalThreads = accessor _internalThreads (\ts st -> st { _internalThreads = ts })
1376+nextRemoteId :: Accessor ValidLocalEndPointState Int
1377+nextRemoteId = accessor _nextRemoteId (\rid st -> st { _nextRemoteId = rid })
1205localEndPointAt :: EndPointAddress -> Accessor ValidTransportState (Maybe LocalEndPoint)
1379localEndPointAt :: EndPointAddress -> Accessor ValidTransportState (Maybe LocalEndPoint)
1206localEndPointAt addr = localEndPoints >>> DAC.mapMaybe addr
1380localEndPointAt addr = localEndPoints >>> DAC.mapMaybe addr