@@ -72,7 +72,7 @@ import Control.Concurrent.MVar ( MVar
7272 )
7373import Control.Category ((>>>))
7474import Control.Applicative ((<$>))
75−import Control.Monad (when, unless, join, liftM)
75+import Control.Monad (when, unless)
7676import Control.Monad.Error (ErrorT(..), runErrorT)
7777import Control.Exception ( IOException
7878 , SomeException
@@ -250,6 +250,43 @@ data ValidLocalEndPointState = ValidLocalEndPointState
250250 , _nextRemoteId :: !Int
251251 }
252252
253+-- REMOTE ENDPOINTS
254+--
255+-- Remote endpoints (basically, TCP connections) have the following lifecycle:
256+--
257+-- Init ----+---> Invalid
258+-- |
259+-- | /----------------\
260+-- | | |
261+-- | v |
262+-- \---> Valid ----+---> Closing
263+-- | |
264+-- | v
265+-- \---> Closed
266+--
267+-- Init: There are two places where we create new remote endpoints: in
268+-- requestConnectionTo (in response to an API 'connect' call) and in
269+-- handleConnectionRequest (when a remote node tries to connect to us).
270+-- 'Init' carries an MVar () 'resolved' which concurrent threads can use to
271+-- wait for the remote endpoint to finish initialization.
272+--
273+-- Invalid: We put the remote endpoint in invalid state only during
274+-- requestConnectionTo when we fail to connect.
275+--
276+-- Valid: This is the "normal" state for a working remote endpoint.
277+--
278+-- Closing: When we detect that a remote endpoint is no longer used, we send a
279+-- CloseSocket request across the connection and put the remote endpoint in
280+-- closing state. As with Init, 'Closing' carries an MVar () 'resolved' which
281+-- concurrent threads can use to wait for the remote endpoint to either be
282+-- closed fully (if the communication parnet responds with another
283+-- CloseSocket) or be put back in 'Valid' state if the remote endpoint denies
284+-- the request.
285+--
286+-- Closed: The endpoint is put in Closed state after a successful garbage
287+-- collection, when the endpoint (or the whole transport) is closed manually,
288+-- or when an IO exception occurs during communication.
289+--
253290-- Invariants for dealing with remote endpoints:
254291--
255292-- INV-SEND: Whenever we send data the remote endpoint must be locked (to avoid
@@ -269,7 +306,7 @@ data ValidLocalEndPointState = ValidLocalEndPointState
269306-- both signal on 'resolved'.
270307--
271308-- INV-LOST: If a send or recv fails, or a socket is closed unexpectedly, we
272−-- first put the remote endpoint in Closing or Closed state, and then send a
309+-- first put the remote endpoint in Closed state, and then send a
273310-- EventConnectionLost event. This guarantees that we only send this event
274311-- once.
275312--
@@ -448,11 +485,6 @@ apiConnect ourEndPoint theirAddress _reliability _hints =
448485 }
449486
450487-- | Close a connection
451−--
452−-- RELY: The endpoint must not be invalid
453−-- GUARANTEE: If the connection is alive on entry then the remote endpoint will
454−-- either be RemoteEndPointValid or RemoteEndPointClosing. Otherwise, the
455−-- state of the remote endpoint will not be changed.
456488apiClose :: EndPointPair -> ConnectionId -> IORef Bool -> IO ()
457489apiClose (ourEndPoint, theirEndPoint) connId connAlive = void . tryIO $ do
458490 modifyRemoteState_ (ourEndPoint, theirEndPoint) remoteStateIdentity
@@ -472,20 +504,13 @@ apiClose (ourEndPoint, theirEndPoint) connId connAlive = void . tryIO $ do
472504 closeIfUnused (ourEndPoint, theirEndPoint)
473505
474506-- | Send data across a connection
475−--
476−-- RELY: The endpoint must not be in invalid state.
477−-- GUARANTEE: The state of the remote endpoint will not be changed.
478507apiSend :: EndPointPair -- ^ Local and remote endpoint
479508 -> ConnectionId -- ^ Connection ID (supplied by remote endpoint)
480509 -> IORef Bool -- ^ Is the connection still alive?
481510 -> [ByteString] -- ^ Payload
482511 -> IO (Either (TransportError SendErrorCode) ())
483512apiSend (ourEndPoint, theirEndPoint) connId connAlive payload =
484− -- The 'join' joins the inner exception (which we explicitly return, for
485− -- instance if the connection is closed) with the outer exception (which is
486− -- returned by 'try' when an exception is thrown by 'sendOn', and handled
487− -- by 'withRemoteState')
488− liftM join . try . mapIOException sendFailed $
513+ try . mapIOException sendFailed $
489514 withRemoteState (ourEndPoint, theirEndPoint) RemoteStatePatternMatch
490515 { caseInvalid = \_ ->
491516 relyViolation (ourEndPoint, theirEndPoint) "apiSend"
@@ -494,21 +519,18 @@ apiSend (ourEndPoint, theirEndPoint) connId connAlive payload =
494519 , caseValid = \vst -> do
495520 alive <- readIORef connAlive
496521 if alive
497− then do
498− sendOn vst (encodeInt32 connId : prependLength payload)
499− return . Right $ ()
500− else
501− return . Left $ TransportError SendClosed "Connection closed"
522+ then sendOn vst (encodeInt32 connId : prependLength payload)
523+ else throwIO $ TransportError SendClosed "Connection closed"
502524 , caseClosing = \_ _ -> do
503525 alive <- readIORef connAlive
504526 if alive
505− then return . Left $ TransportError SendFailed "Connection lost"
506− else return . Left $ TransportError SendClosed "Connection closed"
527+ then throwIO $ TransportError SendFailed "Connection lost"
528+ else throwIO $ TransportError SendClosed "Connection closed"
507529 , caseClosed = do
508530 alive <- readIORef connAlive
509531 if alive
510− then return . Left $ TransportError SendFailed "Connection lost"
511− else return . Left $ TransportError SendClosed "Connection closed"
532+ then throwIO $ TransportError SendFailed "Connection lost"
533+ else throwIO $ TransportError SendClosed "Connection closed"
512534 }
513535 where
514536 sendFailed = TransportError SendFailed . show
@@ -704,7 +726,7 @@ requestConnectionTo ourEndPoint theirAddress = go
704726 case decodeInt32 . BS.concat $ reply of
705727 Nothing ->
706728 throwIO (connectFailed $ userError "Invalid integer")
707− Just cid -> do
729+ Just cid ->
708730 return (theirEndPoint, cid)
709731
710732 connectFailed = TransportError ConnectFailed . show