@@ -10,6 +10,7 @@ module Network.Transport.TCP ( -- * Main API
10 createTransport
10 createTransport
11 , -- * TCP specific functionality
11 , -- * TCP specific functionality
12 EndPointId
12 EndPointId
13+ , encodeEndPointAddress
13 , decodeEndPointAddress
14 , decodeEndPointAddress
14 , ControlHeader(..)
15 , ControlHeader(..)
15 , ConnectionRequestResponse(..)
16 , ConnectionRequestResponse(..)
@@ -237,6 +238,12 @@ data LocalState = LocalState { _nextConnectionId :: ConnectionId
238-- Since we don't do concurrent reads from the same socket we don't need to
239-- Since we don't do concurrent reads from the same socket we don't need to
239-- take the lock when reading from the socket.
240-- take the lock when reading from the socket.
242+-- Moreover, we insist on the invariant (INV-CLOSE) that whenever we put an
243+-- endpoint in closed state we remove that endpoint from localConnections
244+-- first, so that if a concurrent thread reads the mvar, finds EndPointClosed,
245+-- and then looks up the endpoint in localConnections it is guaranteed to
246+-- either find a different remote endpoint, or else none at all.
241data RemoteState = RemoteEndPointInvalid (FailedWith ConnectErrorCode)
248data RemoteState = RemoteEndPointInvalid (FailedWith ConnectErrorCode)
242 | RemoteEndPointValid RemoteConnection
249 | RemoteEndPointValid RemoteConnection
@@ -258,14 +265,14 @@ data ControlHeader =
258 | CloseConnection -- ^ Tell the remote endpoint we will no longer be using a connection
265 | CloseConnection -- ^ Tell the remote endpoint we will no longer be using a connection
259 | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
266 | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
260 | CloseSocket -- ^ Request to close the connection (see module description)
267 | CloseSocket -- ^ Request to close the connection (see module description)
261− deriving (Enum, Bounded)
268+ deriving (Enum, Bounded, Show)
263-- Response sent by /B/ to /A/ when /A/ tries to connect
270-- Response sent by /B/ to /A/ when /A/ tries to connect
264data ConnectionRequestResponse =
271data ConnectionRequestResponse =
265 ConnectionRequestAccepted -- ^ /B/ accepts the connection
272 ConnectionRequestAccepted -- ^ /B/ accepts the connection
266 | ConnectionRequestEndPointInvalid -- ^ /A/ requested an invalid endpoint
273 | ConnectionRequestEndPointInvalid -- ^ /A/ requested an invalid endpoint
267 | ConnectionRequestCrossed -- ^ /A/s request crossed with a request from /B/ (see protocols)
274 | ConnectionRequestCrossed -- ^ /A/s request crossed with a request from /B/ (see protocols)
268− deriving (Enum, Bounded)
275+ deriving (Enum, Bounded, Show)
270--------------------------------------------------------------------------------
277--------------------------------------------------------------------------------
271-- Top-level functionality --
278-- Top-level functionality --
@@ -393,7 +400,7 @@ apiCloseEndPoint transport ourEndPoint = do
393 -- calls will block. We could change this so that all subsequent calls to
400 -- calls will block. We could change this so that all subsequent calls to
394 -- receive return an error, but this would mean checking for some state on
401 -- receive return an error, but this would mean checking for some state on
395 -- every call to receive, which is an unnecessary overhead.
402 -- every call to receive, which is an unnecessary overhead.
396− writeChan (localChannel ourEndPoint) (ErrorEvent EventErrorEndPointClosed incoming)
403+ writeChan (localChannel ourEndPoint) (ErrorEvent $ ErrorEventEndPointClosed incoming)
398 remoteSocketOf :: RemoteEndPoint -> MaybeT IO N.Socket
405 remoteSocketOf :: RemoteEndPoint -> MaybeT IO N.Socket
399 remoteSocketOf theirEndPoint = do
406 remoteSocketOf theirEndPoint = do
@@ -452,7 +459,7 @@ createLocalEndPoint transport = do
453 modifyMVar (transportState transport) $ \st -> do
460 modifyMVar (transportState transport) $ \st -> do
454 let ix = fromIntegral $ Map.size (st ^. localEndPoints)
461 let ix = fromIntegral $ Map.size (st ^. localEndPoints)
455− let addr = encodeEndPointAddress transport ix
462+ let addr = encodeEndPointAddress (transportHost transport) (transportPort transport) ix
456 let localEndPoint = LocalEndPoint { localAddress = addr
463 let localEndPoint = LocalEndPoint { localAddress = addr
457 , localChannel = chan
464 , localChannel = chan
458 , localState = state
465 , localState = state
@@ -472,7 +479,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
473 let ourState = localState ourEndPoint
480 let ourState = localState ourEndPoint
474 (theirEndPoint, isNew) <- modifyMVar ourState $ \st ->
481 (theirEndPoint, isNew) <- modifyMVar ourState $ \st ->
475− case st ^. connectionTo theirAddress of
482+ case st ^. localConnectionTo theirAddress of
476 Just theirEndPoint ->
483 Just theirEndPoint ->
477 return (st, (theirEndPoint, False))
484 return (st, (theirEndPoint, False))
478 Nothing -> do
485 Nothing -> do
@@ -480,7 +487,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
480 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
487 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
481 , remoteState = theirState
488 , remoteState = theirState
483− return (connectionTo theirAddress ^= Just theirEndPoint $ st, (theirEndPoint, True))
490+ return (localConnectionTo theirAddress ^= Just theirEndPoint $ st, (theirEndPoint, True))
485 -- If this is a new endpoint, fork a thread to listen for incoming
492 -- If this is a new endpoint, fork a thread to listen for incoming
486 -- connections. We don't want to do this while we hold the lock, because
493 -- connections. We don't want to do this while we hold the lock, because
@@ -574,14 +581,14 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
574 -- waiting on the result of this call to connect will get the
581 -- waiting on the result of this call to connect will get the
575 -- RemoteEndPointInvalid; subsequent threads will initiate a new
582 -- RemoteEndPointInvalid; subsequent threads will initiate a new
576 -- connection requests.
583 -- connection requests.
577− modifyMVar_ ourState $ return . (connectionTo theirAddress ^= Nothing)
584+ modifyMVar_ ourState $ return . (localConnectionTo theirAddress ^= Nothing)
578 putMVar theirState (RemoteEndPointInvalid (invalidAddress "Invalid endpoint"))
585 putMVar theirState (RemoteEndPointInvalid (invalidAddress "Invalid endpoint"))
579 N.sClose sock
586 N.sClose sock
580 Right (ConnectionRequestCrossed, sock) -> do
587 Right (ConnectionRequestCrossed, sock) -> do
581 N.sClose sock
588 N.sClose sock
582 Left err -> do
589 Left err -> do
583 -- See comment above
590 -- See comment above
584− modifyMVar_ ourState $ return . (connectionTo theirAddress ^= Nothing)
591+ modifyMVar_ ourState $ return . (localConnectionTo theirAddress ^= Nothing)
585 putMVar theirState (RemoteEndPointInvalid err)
592 putMVar theirState (RemoteEndPointInvalid err)
586 -- TODO: this isn't quite right; socket *might* have been opened here,
593 -- TODO: this isn't quite right; socket *might* have been opened here,
587 -- in which case we should close it
594 -- in which case we should close it
@@ -613,9 +620,9 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
613 EndPointAddress ourAddress = localAddress ourEndPoint
620 EndPointAddress ourAddress = localAddress ourEndPoint
615-- | Encode end point address
622-- | Encode end point address
616−encodeEndPointAddress :: TCPTransport -> EndPointId -> EndPointAddress
623+encodeEndPointAddress :: N.HostName -> N.ServiceName -> EndPointId -> EndPointAddress
617−encodeEndPointAddress transport ix = EndPointAddress . BSC.pack $
624+encodeEndPointAddress host port ix = EndPointAddress . BSC.pack $
618− transportHost transport ++ ":" ++ transportPort transport ++ ":" ++ show ix
625+ host ++ ":" ++ port ++ ":" ++ show ix
620-- | Decode end point address
627-- | Decode end point address
621-- TODO: This uses regular expression parsing, which is nice, but unnecessary
628-- TODO: This uses regular expression parsing, which is nice, but unnecessary
@@ -677,7 +684,7 @@ handleConnectionRequest transport sock = do
677 request <- tryIO $ do
684 request <- tryIO $ do
678 ourEndPointId <- recvInt32 sock
685 ourEndPointId <- recvInt32 sock
679 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
686 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
680− let ourAddress = encodeEndPointAddress transport ourEndPointId
687+ let ourAddress = encodeEndPointAddress (transportHost transport) (transportPort transport) ourEndPointId
681 ourEndPoint <- (^. localEndPointAt ourAddress) <$> readMVar (transportState transport)
688 ourEndPoint <- (^. localEndPointAt ourAddress) <$> readMVar (transportState transport)
682 return (theirAddress, ourEndPoint)
689 return (theirAddress, ourEndPoint)
683 case request of
690 case request of
@@ -686,13 +693,13 @@ handleConnectionRequest transport sock = do
686 N.sClose sock
693 N.sClose sock
687 Right (theirAddress, Just ourEndPoint) -> forkEndPointThread ourEndPoint $ do
694 Right (theirAddress, Just ourEndPoint) -> forkEndPointThread ourEndPoint $ do
688 (crossed, theirEndPoint) <- modifyMVar (localState ourEndPoint) $ \st ->
695 (crossed, theirEndPoint) <- modifyMVar (localState ourEndPoint) $ \st ->
689− case st ^. connectionTo theirAddress of
696+ case st ^. localConnectionTo theirAddress of
690 Nothing -> do
697 Nothing -> do
691 theirState <- newEmptyMVar
698 theirState <- newEmptyMVar
692 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
699 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
693 , remoteState = theirState
700 , remoteState = theirState
695− return (connectionTo theirAddress ^= Just theirEndPoint $ st, (False, theirEndPoint))
702+ return (localConnectionTo theirAddress ^= Just theirEndPoint $ st, (False, theirEndPoint))
696 Just theirEndPoint ->
703 Just theirEndPoint ->
697 return (st, (localAddress ourEndPoint < theirAddress, theirEndPoint))
704 return (st, (localAddress ourEndPoint < theirAddress, theirEndPoint))
698 if crossed
705 if crossed
@@ -733,28 +740,10 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
734 error "handleIncomingMessages RELY violation"
741 error "handleIncomingMessages RELY violation"
736− tryIO $ go sock
743+ mCleanExit <- tryIO $ go sock
738− -- Normally, there would be no unclosed connections, but if the client
739− -- exits prematurely there might be
740− mUnclosedConnections <- modifyMVar theirState $ \st ->
741− case st of
744+ case mCleanExit of
742− RemoteEndPointInvalid _ ->
743− error "handleIncomingMessages RELY violation"
744− RemoteEndPointValid remoteConn ->
745− return (RemoteEndPointClosed, Just $ remoteConn ^. remoteIncoming)
746− RemoteEndPointClosing _ _ ->
747− return (RemoteEndPointClosed, Nothing)
748− RemoteEndPointClosed ->
749− return (st, Nothing)
751− -- TODO: we need to remote this endpoint from localConnections at this point
753− case mUnclosedConnections of
754− Nothing -> return ()
745+ Right () -> return ()
755− Just unclosedConnections ->
746+ Left err -> prematureExit sock err
756− writeChan ourChannel (ErrorEvent EventErrorEndPointClosed (IntSet.elems unclosedConnections))
759 -- Dispatch
748 -- Dispatch
760 go :: N.Socket -> IO ()
749 go :: N.Socket -> IO ()
@@ -835,24 +824,45 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
835 -- Close the socket (if we don't have any outgoing connections)
824 -- Close the socket (if we don't have any outgoing connections)
836 closeSocket :: N.Socket -> IO Bool
825 closeSocket :: N.Socket -> IO Bool
837 closeSocket sock = do
826 closeSocket sock = do
827+ -- We need to check if we can close the socket (that is, if we don't have
828+ -- any outgoing connections), and once we are sure that we can put the
829+ -- endpoint in Closed state. However, by INV-CLOSE we can only put the
830+ -- endpoint in Closed state once we remove the endpoint from our local
831+ -- connections. But we can do that only once we are sure that we can
832+ -- close the endpoint. Catch-22. We can resolve the catch-22 by locking
833+ -- *both* our local state and the endpoint state, but at the cost of
834+ -- introducing a double lock and all the associated perils, or by putting
835+ -- the remote endpoint in Closing state first. We opt for the latter.
838− didClose <- modifyMVar theirState $ \st ->
836+ canClose <- modifyMVar theirState $ \st ->
839 case st of
837 case st of
840− RemoteEndPointValid remoteConn | remoteConn ^. remoteOutgoing == 0 -> do
838+ RemoteEndPointValid remoteConn -> do
839+ -- TODO: We check only for outgoing connections here. That is
840+ -- correct, but we need to makke sure that we send the right event
841+ -- to our endpoint for any incoming connections that are still live
843+ if remoteConn ^. remoteOutgoing == 0
845+ -- TODO: code duplication with closeIfUnused
846+ resolved <- newEmptyMVar
841− sendOn remoteConn [encodeInt32 CloseSocket]
847+ sendOn remoteConn [encodeInt32 CloseSocket]
842− N.sClose sock
848+ return (RemoteEndPointClosing resolved remoteConn, Just resolved)
843− return (RemoteEndPointClosed, True)
849+ else
850+ return (st, Nothing)
844− RemoteEndPointClosing resolved _ -> do
851+ RemoteEndPointClosing resolved _ ->
845− -- If the socket is already in closing state, then we have already
852+ return (st, Just resolved)
846− -- sent a CloseSocket request; hence, we don't need to do it again
850− return (RemoteEndPointClosed, True)
854+ error "handleIncomingConnections RELY violation"
852− return (st, False)
858+ return False
853− when didClose $ modifyMVar_ ourState $ return . (connectionTo theirAddr ^= Nothing)
860+ modifyMVar_ ourState $ return . (localConnectionTo theirAddr ^= Nothing)
861+ modifyMVar_ theirState $ return . const RemoteEndPointClosed
854− return didClose
864+ return True
856 -- Read a message and output it on the endPoint's channel
866 -- Read a message and output it on the endPoint's channel
857 readMessage :: N.Socket -> ConnectionId -> IO ()
867 readMessage :: N.Socket -> ConnectionId -> IO ()
858 readMessage sock connId = recvWithLength sock >>= writeChan ourChannel . Received connId
868 readMessage sock connId = recvWithLength sock >>= writeChan ourChannel . Received connId
@@ -863,6 +873,29 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
863 theirState = remoteState theirEndPoint
873 theirState = remoteState theirEndPoint
864 theirAddr = remoteAddress theirEndPoint
874 theirAddr = remoteAddress theirEndPoint
876+ -- Deal with a premature exit
877+ prematureExit :: N.Socket -> IOException -> IO ()
878+ prematureExit sock _ = do
880+ modifyMVar_ ourState $ return . (localConnectionTo theirAddr ^= Nothing)
881+ mUnclosedConnections <- modifyMVar theirState $ \st ->
883+ RemoteEndPointInvalid _ ->
884+ error "handleIncomingMessages RELY violation"
885+ RemoteEndPointValid remoteConn ->
886+ return (RemoteEndPointClosed, Just $ remoteConn ^. remoteIncoming)
887+ RemoteEndPointClosing _ _ ->
888+ return (RemoteEndPointClosed, Nothing)
889+ RemoteEndPointClosed ->
890+ return (st, Nothing)
892+ -- We send the connection lost message even if unclosedConnections is the
893+ -- empty set, because *outgoing* connections will be broken too now
894+ case mUnclosedConnections of
897+ Just unclosedConnections -> writeChan ourChannel . ErrorEvent $
898+ ErrorEventConnectionLost (remoteAddress theirEndPoint) (IntSet.elems unclosedConnections)
868-- | Get the next connection ID
901-- | Get the next connection ID
@@ -911,8 +944,8 @@ localEndPointAt addr = localEndPoints >>> mapLens addr
911pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
944pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
912pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
945pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
914−connectionTo :: EndPointAddress -> Lens LocalState (Maybe RemoteEndPoint)
947+localConnectionTo :: EndPointAddress -> Lens LocalState (Maybe RemoteEndPoint)
915−connectionTo addr = localConnections >>> mapLens addr
948+localConnectionTo addr = localConnections >>> mapLens addr
917remoteOutgoing :: Lens RemoteConnection Int
950remoteOutgoing :: Lens RemoteConnection Int
918remoteOutgoing = lens _remoteOutgoing (\cs conn -> conn { _remoteOutgoing = cs })
951remoteOutgoing = lens _remoteOutgoing (\cs conn -> conn { _remoteOutgoing = cs })