@@ -46,7 +46,7 @@ import qualified Network.Socket as N ( HostName
48import Network.Socket.ByteString (sendMany)
48import Network.Socket.ByteString (sendMany)
49−import Control.Concurrent (forkIO, ThreadId, killThread)
49+import Control.Concurrent (forkIO, ThreadId, killThread, myThreadId)
50import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
50import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
51import Control.Concurrent.MVar ( MVar
51import Control.Concurrent.MVar ( MVar
@@ -57,25 +57,27 @@ import Control.Concurrent.MVar ( MVar
58 , newEmptyMVar
58 , newEmptyMVar
59 , withMVar
59 , withMVar
61import Control.Category ((>>>))
62import Control.Category ((>>>))
62import Control.Applicative ((<*>), (*>), (<$>))
63import Control.Applicative ((<*>), (*>), (<$>))
63−import Control.Monad (forM_, void, when)
64+import Control.Monad (forM_, void, when, mzero, unless)
64import Control.Monad.Error (ErrorT(ErrorT), runErrorT, throwError)
65import Control.Monad.Error (ErrorT(ErrorT), runErrorT, throwError)
66+import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
65import Control.Monad.IO.Class (liftIO)
67import Control.Monad.IO.Class (liftIO)
66import Control.Exception (IOException, SomeException)
68import Control.Exception (IOException, SomeException)
67−import Data.IORef (IORef, newIORef, writeIORef, readIORef, modifyIORef)
69+import Data.IORef (IORef, newIORef, writeIORef, readIORef)
68import Data.ByteString (ByteString)
70import Data.ByteString (ByteString)
69import qualified Data.ByteString as BS (concat)
71import qualified Data.ByteString as BS (concat)
70import qualified Data.ByteString.Char8 as BSC (pack, unpack)
72import qualified Data.ByteString.Char8 as BSC (pack, unpack)
71import Data.Int (Int32)
73import Data.Int (Int32)
72import Data.IntMap (IntMap)
74import Data.IntMap (IntMap)
73−import qualified Data.IntMap as IntMap (empty, size)
75+import qualified Data.IntMap as IntMap (empty)
74import Data.IntSet (IntSet)
76import Data.IntSet (IntSet)
75−import qualified Data.IntSet as IntSet (empty, insert, delete, elems)
77+import qualified Data.IntSet as IntSet (empty, insert, elems, singleton, null, delete)
76import Data.Map (Map)
78import Data.Map (Map)
77−import qualified Data.Map as Map (empty)
79+import qualified Data.Map as Map (empty, elems, size)
78−import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
80+import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=), (^%=))
79import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
81import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
80import System.IO (hPutStrLn, stderr)
82import System.IO (hPutStrLn, stderr)
@@ -183,18 +185,18 @@ data LocalEndPoint = LocalEndPoint { localAddress :: EndPointAddress
184data RemoteEndPoint = RemoteEndPoint { remoteAddress :: EndPointAddress
186data RemoteEndPoint = RemoteEndPoint { remoteAddress :: EndPointAddress
185 , remoteState :: MVar RemoteState
187 , remoteState :: MVar RemoteState
186− , remoteThread :: ThreadId
189-- We use underscores for fields that we might update (using lenses).
190-- We use underscores for fields that we might update (using lenses).
191−data TransportState = TransportState { _localEndPoints :: IntMap LocalEndPoint
192+data TransportState = TransportState { _localEndPoints :: Map EndPointAddress LocalEndPoint
192 , _acceptNewConnections :: Bool
193 , _acceptNewConnections :: Bool
194data LocalState = LocalState { _nextConnectionId :: ConnectionId
195data LocalState = LocalState { _nextConnectionId :: ConnectionId
195 , _pendingCtrlRequests :: IntMap (MVar [ByteString])
196 , _pendingCtrlRequests :: IntMap (MVar [ByteString])
196 , _nextCtrlRequestId :: ControlRequestId
197 , _nextCtrlRequestId :: ControlRequestId
197 , _localConnections :: Map EndPointAddress RemoteEndPoint
198 , _localConnections :: Map EndPointAddress RemoteEndPoint
199+ , _internalThreads :: [ThreadId]
200-- A remote endpoint has incoming and outgoing connections, and when the total
202-- A remote endpoint has incoming and outgoing connections, and when the total
@@ -238,9 +240,10 @@ data LocalState = LocalState { _nextConnectionId :: ConnectionId
239data RemoteState = RemoteEndPointInvalid (FailedWith ConnectErrorCode)
241data RemoteState = RemoteEndPointInvalid (FailedWith ConnectErrorCode)
240 | RemoteEndPointValid RemoteConnection
242 | RemoteEndPointValid RemoteConnection
241− | RemoteEndPointClosing RemoteConnection (MVar ())
243+ | RemoteEndPointClosing (MVar ()) RemoteConnection
242 | RemoteEndPointClosed
244 | RemoteEndPointClosed
243−data RemoteConnection = RemoteConnection { _remoteRefCount :: !Int
245+data RemoteConnection = RemoteConnection { _remoteOutgoing :: !Int
246+ , _remoteIncoming :: IntSet
244 , remoteSocket :: N.Socket
247 , remoteSocket :: N.Socket
245 , sendOn :: [ByteString] -> IO ()
248 , sendOn :: [ByteString] -> IO ()
@@ -273,7 +276,7 @@ data ConnectionRequestResponse =
273-- TODOs: deal with hints
276-- TODOs: deal with hints
274createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
277createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
275createTransport host port = do
278createTransport host port = do
276− state <- newMVar TransportState { _localEndPoints = IntMap.empty
279+ state <- newMVar TransportState { _localEndPoints = Map.empty
277 , _acceptNewConnections = True
280 , _acceptNewConnections = True
279 let transport = TCPTransport { transportState = state
282 let transport = TCPTransport { transportState = state
@@ -308,9 +311,10 @@ apiCloseTransport transportThread = do
308apiNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
311apiNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
309apiNewEndPoint transport = do
312apiNewEndPoint transport = do
310 ourEndPoint <- createLocalEndPoint transport
313 ourEndPoint <- createLocalEndPoint transport
311− return . Right $ EndPoint { receive = readChan (localChannel ourEndPoint)
314+ return . Right $ EndPoint { receive = readChan (localChannel ourEndPoint)
312− , address = localAddress ourEndPoint
315+ , address = localAddress ourEndPoint
313− , connect = apiConnect ourEndPoint
316+ , connect = apiConnect ourEndPoint
317+ , closeEndPoint = apiCloseEndPoint transport ourEndPoint
314 , newMulticastGroup = return . Left $ newMulticastGroupError
318 , newMulticastGroup = return . Left $ newMulticastGroupError
315 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
319 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
@@ -345,14 +349,16 @@ apiConnect ourEndPoint theirAddress _ = runErrorT $ do
345-- TODO: We ignore errors during a close. Is that right?
349-- TODO: We ignore errors during a close. Is that right?
346apiClose :: RemoteEndPoint -> ConnectionId -> IORef Bool -> IO ()
350apiClose :: RemoteEndPoint -> ConnectionId -> IORef Bool -> IO ()
347apiClose theirEndPoint connId connAlive = void . tryIO $ do
351apiClose theirEndPoint connId connAlive = void . tryIO $ do
348− wasAlive <- withMVar (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) -> do
352+ modifyMVar_ (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) -> do
349 alive <- readIORef connAlive
353 alive <- readIORef connAlive
350− when alive $ do
354+ if alive
351− sendOn remoteConn [encodeInt32 CloseConnection, encodeInt32 connId]
356+ sendOn remoteConn [encodeInt32 CloseConnection, encodeInt32 connId]
352− writeIORef connAlive False
357+ writeIORef connAlive False
353− return alive
358+ return (RemoteEndPointValid . (remoteOutgoing ^+= (-1)) $ remoteConn)
354− -- We don't want to call decreaseRemoteRefCount while we still hold the lock
359+ else
355− when wasAlive $ decreaseRemoteRefCount theirEndPoint
360+ return (RemoteEndPointValid remoteConn)
361+ closeIfUnused theirEndPoint
357-- | Send data across a connection
363-- | Send data across a connection
@@ -369,6 +375,37 @@ apiSend theirEndPoint connId connAlive payload = do
370 throwError $ FailedWith SendConnectionClosed "Connection closed"
376 throwError $ FailedWith SendConnectionClosed "Connection closed"
378+-- | Force-close the endpoint
379+apiCloseEndPoint :: TCPTransport -> LocalEndPoint -> IO ()
380+apiCloseEndPoint transport ourEndPoint = do
381+ -- Remove the reference from the transport state
382+ modifyMVar_ (transportState transport) $ return . (localEndPointAt (localAddress ourEndPoint) ^= Nothing)
383+ -- We take the endpoint state and don't put it back
384+ -- (this may block some threads, but we don't care: we are killing them anyway)
385+ ourState <- takeMVar (localState ourEndPoint)
386+ --incoming <- IntSet.elems <$> takeMVar (localIncoming ourEndPoint)
387+ let incoming = undefined
388+ -- Kill all threads associated with this endpoint and close all sockets
389+ forM_ (ourState ^. internalThreads) killThread
390+ forM_ (Map.elems $ ourState ^. localConnections) $ \theirEndPoint ->
391+ runMaybeT $ remoteSocketOf theirEndPoint >>= liftIO . N.sClose
392+ -- We send a single message to the endpoint that it is closed. Subsequent
393+ -- 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
395+ -- every call to receive, which is an unnecessary overhead.
396+ writeChan (localChannel ourEndPoint) (ErrorEvent EventErrorEndPointClosed incoming)
398+ remoteSocketOf :: RemoteEndPoint -> MaybeT IO N.Socket
399+ remoteSocketOf theirEndPoint = do
400+ -- Like above, we take but don't put back
401+ mRemoteState <- liftIO $ tryTakeMVar (remoteState theirEndPoint)
402+ case mRemoteState of
404+ Just (RemoteEndPointInvalid _) -> mzero
405+ Just (RemoteEndPointValid conn) -> return $ remoteSocket conn
406+ Just (RemoteEndPointClosing _ conn) -> return $ remoteSocket conn
407+ Just RemoteEndPointClosed -> mzero
372-- | Special case of 'apiConnect': connect an endpoint to itself
409-- | Special case of 'apiConnect': connect an endpoint to itself
373connectToSelf :: LocalEndPoint -> IO (Either (FailedWith ConnectErrorCode) Connection)
410connectToSelf :: LocalEndPoint -> IO (Either (FailedWith ConnectErrorCode) Connection)
374connectToSelf ourEndPoint = do
411connectToSelf ourEndPoint = do
@@ -411,17 +448,16 @@ createLocalEndPoint transport = do
411 , _pendingCtrlRequests = IntMap.empty
448 , _pendingCtrlRequests = IntMap.empty
412 , _nextCtrlRequestId = 0
449 , _nextCtrlRequestId = 0
413 , _localConnections = Map.empty
450 , _localConnections = Map.empty
451+ , _internalThreads = []
415− let host = transportHost transport
416− let port = transportPort transport
417 modifyMVar (transportState transport) $ \st -> do
453 modifyMVar (transportState transport) $ \st -> do
418− let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
454+ let ix = fromIntegral $ Map.size (st ^. localEndPoints)
419− let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show ix
455+ let addr = encodeEndPointAddress transport ix
420− let localEndPoint = LocalEndPoint { localAddress = addr
456+ let localEndPoint = LocalEndPoint { localAddress = addr
421− , localChannel = chan
457+ , localChannel = chan
422− , localState = state
458+ , localState = state
424− return ((localEndPointAt ix ^= Just localEndPoint) $ st, localEndPoint)
460+ return ((localEndPointAt addr ^= Just localEndPoint) $ st, localEndPoint)
426-- | Request a connection to a remote endpoint
462-- | Request a connection to a remote endpoint
@@ -435,17 +471,22 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
437 let ourState = localState ourEndPoint
473 let ourState = localState ourEndPoint
438− theirEndPoint <- modifyMVar ourState $ \st ->
474+ (theirEndPoint, isNew) <- modifyMVar ourState $ \st ->
439 case st ^. connectionTo theirAddress of
475 case st ^. connectionTo theirAddress of
440 Just theirEndPoint ->
476 Just theirEndPoint ->
441− return (st, theirEndPoint)
477+ return (st, (theirEndPoint, False))
442 Nothing -> do
478 Nothing -> do
443 theirState <- newEmptyMVar
479 theirState <- newEmptyMVar
444 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
480 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
445 , remoteState = theirState
481 , remoteState = theirState
447− forkIO $ connectToRemoteEndPoint (ourEndPoint, theirEndPoint)
448− return (connectionTo theirAddress ^= Just theirEndPoint $ st, theirEndPoint)
483+ return (connectionTo theirAddress ^= Just theirEndPoint $ st, (theirEndPoint, True))
485+ -- 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
487+ -- forkEndPointThread modifies the local state too (to record the thread ID)
488+ when isNew . forkEndPointThread ourEndPoint $
489+ connectToRemoteEndPoint (ourEndPoint, theirEndPoint)
450 -- Before we initiate the new connection request we want to make sure
491 -- Before we initiate the new connection request we want to make sure
451 -- that refcount on the endpoint is incremented so that a concurrent
492 -- that refcount on the endpoint is incremented so that a concurrent
@@ -456,7 +497,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
456 endPointStateSnapshot <- modifyMVar theirState $ \st ->
497 endPointStateSnapshot <- modifyMVar theirState $ \st ->
457 case st of
498 case st of
458 RemoteEndPointValid ep ->
499 RemoteEndPointValid ep ->
459− return (RemoteEndPointValid . (remoteRefCount ^+= 1) $ ep, st)
500+ return (RemoteEndPointValid . (remoteOutgoing ^+= 1) $ ep, st)
461 return (st, st)
502 return (st, st)
@@ -471,7 +512,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
471 RemoteEndPointInvalid err -> do
512 RemoteEndPointInvalid err -> do
472 return . Left $ err
513 return . Left $ err
474− RemoteEndPointClosing _ resolved -> do
515+ RemoteEndPointClosing resolved _ -> do
475 -- If the remote endpoint is closing, then we need to block until
516 -- If the remote endpoint is closing, then we need to block until
476 -- this is resolved and we then try again
517 -- this is resolved and we then try again
477 readMVar resolved
518 readMVar resolved
@@ -498,12 +539,13 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
498 -- valid state at this point.
539 -- valid state at this point.
499 let failed err = do
540 let failed err = do
500 modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
541 modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
501− return (RemoteEndPointValid . (remoteRefCount ^+= (-1)) $ ep)
542+ return (RemoteEndPointValid . (remoteOutgoing ^+= (-1)) $ ep)
543+ -- TODO: should we call closeIfUnused here?
502 return . Left $ FailedWith ConnectFailed err
544 return . Left $ FailedWith ConnectFailed err
504 case mcid of
546 case mcid of
505 Left err -> failed (show err)
547 Left err -> failed (show err)
506− Right Nothing -> failed ("Invalid integer")
548+ Right Nothing -> failed "Invalid integer"
507 Right (Just cid) -> return . Right $ (theirEndPoint, cid)
549 Right (Just cid) -> return . Right $ (theirEndPoint, cid)
509-- | Establish a connection to a remote endpoint
551-- | Establish a connection to a remote endpoint
@@ -520,11 +562,12 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
520 case result of
562 case result of
521 Right (ConnectionRequestAccepted, sock) -> do
563 Right (ConnectionRequestAccepted, sock) -> do
522 let remoteConn = RemoteConnection { remoteSocket = sock
564 let remoteConn = RemoteConnection { remoteSocket = sock
523− , _remoteRefCount = 0
565+ , _remoteOutgoing = 0
566+ , _remoteIncoming = IntSet.empty
524 , sendOn = sendMany sock
567 , sendOn = sendMany sock
526 putMVar theirState (RemoteEndPointValid remoteConn)
569 putMVar theirState (RemoteEndPointValid remoteConn)
527− void . forkIO $ handleIncomingMessages (ourEndPoint, theirEndPoint)
570+ handleIncomingMessages (ourEndPoint, theirEndPoint)
528 Right (ConnectionRequestEndPointInvalid, sock) -> do
571 Right (ConnectionRequestEndPointInvalid, sock) -> do
529 -- We remove the endpoint from our local state again because the next
572 -- We remove the endpoint from our local state again because the next
530 -- call to 'connect' might give a different result. Threads that were
573 -- call to 'connect' might give a different result. Threads that were
@@ -569,6 +612,11 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
569 ourAddress :: ByteString
612 ourAddress :: ByteString
570 EndPointAddress ourAddress = localAddress ourEndPoint
613 EndPointAddress ourAddress = localAddress ourEndPoint
615+-- | Encode end point address
616+encodeEndPointAddress :: TCPTransport -> EndPointId -> EndPointAddress
617+encodeEndPointAddress transport ix = EndPointAddress . BSC.pack $
618+ transportHost transport ++ ":" ++ transportPort transport ++ ":" ++ show ix
572-- | Decode end point address
620-- | Decode end point address
573-- TODO: This uses regular expression parsing, which is nice, but unnecessary
621-- TODO: This uses regular expression parsing, which is nice, but unnecessary
574decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
622decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
@@ -581,7 +629,7 @@ decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack
582-- RELY: Remote endpoint must be in valid state.
630-- RELY: Remote endpoint must be in valid state.
583-- GUARANTEE: Will not change the state of the remote endpoint.
631-- GUARANTEE: Will not change the state of the remote endpoint.
584−doRemoteRequest :: EndPointPair -> ControlHeader -> IO ([ByteString])
632+doRemoteRequest :: EndPointPair -> ControlHeader -> IO [ByteString]
585doRemoteRequest (ourEndPoint, theirEndPoint) header = do
633doRemoteRequest (ourEndPoint, theirEndPoint) header = do
586 reply <- newEmptyMVar
634 reply <- newEmptyMVar
587 reqId <- modifyMVar (localState ourEndPoint) $ \st -> do
635 reqId <- modifyMVar (localState ourEndPoint) $ \st -> do
@@ -591,22 +639,32 @@ doRemoteRequest (ourEndPoint, theirEndPoint) header = do
591 sendOn remoteConn [encodeInt32 header, encodeInt32 reqId]
639 sendOn remoteConn [encodeInt32 header, encodeInt32 reqId]
592 takeMVar reply
640 takeMVar reply
594−-- | Decrease the remote refcount of a remote endpoint. When the refcount
642+-- | Check if the remote endpoint is unused, and if so, send a CloseSocket request
595−-- reaches zero, initiate socket closure.
597−-- RELY: Remote endpoint must be in valid state.
598−-- GUARANTEE: Will leave the endpoint in its current state or change it to
599−-- RemoteEndPointClosing.
600−decreaseRemoteRefCount :: RemoteEndPoint -> IO ()
643+closeIfUnused :: RemoteEndPoint -> IO ()
601−decreaseRemoteRefCount theirEndPoint = do
644+closeIfUnused theirEndPoint = modifyMVar_ (remoteState theirEndPoint) $ \st ->
602− modifyMVar_ (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) ->
646+ RemoteEndPointValid remoteConn -> do
603− if remoteConn ^. remoteRefCount == 1
647+ if remoteConn ^. remoteOutgoing == 0 && IntSet.null (remoteConn ^. remoteIncoming)
605− resolved <- newEmptyMVar
649+ resolved <- newEmptyMVar
606− sendOn remoteConn [encodeInt32 CloseSocket]
650+ sendOn remoteConn [encodeInt32 CloseSocket]
607− return (RemoteEndPointClosing remoteConn resolved)
651+ return (RemoteEndPointClosing resolved remoteConn)
609− return (RemoteEndPointValid . (remoteRefCount ^+= (-1)) $ remoteConn)
653+ return st
657+-- | Fork a new thread and store its ID as part of the transport state
658+forkEndPointThread :: LocalEndPoint -> IO () -> IO ()
659+forkEndPointThread ourEndPoint p = do
660+ modifyMVar_ (localState ourEndPoint) $ \st -> do
661+ tid <- forkIO (p >> removeThread)
662+ return (internalThreads ^%= (tid :) $ st)
664+ removeThread :: IO ()
667+ modifyMVar_ (localState ourEndPoint) $ return . (internalThreads ^%= filter (/= tid))
611--------------------------------------------------------------------------------
669--------------------------------------------------------------------------------
612-- Incoming requests --
670-- Incoming requests --
@@ -616,17 +674,17 @@ decreaseRemoteRefCount theirEndPoint = do
616-- establish a TCP connection with us)
674-- establish a TCP connection with us)
617handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
675handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
618handleConnectionRequest transport sock = do
676handleConnectionRequest transport sock = do
619− let tState = transportState transport
620 request <- tryIO $ do
677 request <- tryIO $ do
621 ourEndPointId <- recvInt32 sock
678 ourEndPointId <- recvInt32 sock
622 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
679 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
680+ let ourAddress = encodeEndPointAddress transport ourEndPointId
623− ourEndPoint <- (^. localEndPointAt ourEndPointId) <$> readMVar tState
681+ ourEndPoint <- (^. localEndPointAt ourAddress) <$> readMVar (transportState transport)
624 return (theirAddress, ourEndPoint)
682 return (theirAddress, ourEndPoint)
625 case request of
683 case request of
626 Right (_, Nothing) -> do
684 Right (_, Nothing) -> do
627 sendMany sock [encodeInt32 ConnectionRequestEndPointInvalid]
685 sendMany sock [encodeInt32 ConnectionRequestEndPointInvalid]
628 N.sClose sock
686 N.sClose sock
629− Right (theirAddress, Just ourEndPoint) -> do
687+ Right (theirAddress, Just ourEndPoint) -> forkEndPointThread ourEndPoint $ do
630 (crossed, theirEndPoint) <- modifyMVar (localState ourEndPoint) $ \st ->
688 (crossed, theirEndPoint) <- modifyMVar (localState ourEndPoint) $ \st ->
631 case st ^. connectionTo theirAddress of
689 case st ^. connectionTo theirAddress of
632 Nothing -> do
690 Nothing -> do
@@ -643,14 +701,14 @@ handleConnectionRequest transport sock = do
643 N.sClose sock
701 N.sClose sock
645 let remoteConn = RemoteConnection { remoteSocket = sock
703 let remoteConn = RemoteConnection { remoteSocket = sock
646− , _remoteRefCount = 0
704+ , _remoteOutgoing = 0
705+ , _remoteIncoming = IntSet.empty
647 , sendOn = sendMany sock
706 , sendOn = sendMany sock
708+ -- TODO: this putMVar might block
649 putMVar (remoteState theirEndPoint) (RemoteEndPointValid remoteConn)
709 putMVar (remoteState theirEndPoint) (RemoteEndPointValid remoteConn)
650 sendMany sock [encodeInt32 ConnectionRequestAccepted]
710 sendMany sock [encodeInt32 ConnectionRequestAccepted]
651− -- TODO: we should store the tid as part of the endpoint state
652− tid <- forkIO $ handleIncomingMessages (ourEndPoint, theirEndPoint)
711+ handleIncomingMessages (ourEndPoint, theirEndPoint)
654 Left _ -> do
712 Left _ -> do
655 -- Invalid request
713 -- Invalid request
656 N.sClose sock
714 N.sClose sock
@@ -665,59 +723,72 @@ handleConnectionRequest transport sock = do
665-- GUARANTEE: May change the remote endpoint to RemoteEndPointClosed state.
723-- GUARANTEE: May change the remote endpoint to RemoteEndPointClosed state.
666handleIncomingMessages :: EndPointPair -> IO ()
724handleIncomingMessages :: EndPointPair -> IO ()
667handleIncomingMessages (ourEndPoint, theirEndPoint) = do
725handleIncomingMessages (ourEndPoint, theirEndPoint) = do
668− -- We use an IORef rather than the state monad because when an exception occurs we
669− -- want to know the current list of open connections
670− openConnections <- newIORef IntSet.empty
672 -- For efficiency sake we get the socket once and for all
726 -- For efficiency sake we get the socket once and for all
673 sock <- withMVar theirState $ \st ->
727 sock <- withMVar theirState $ \st ->
674 case st of
728 case st of
675 RemoteEndPointValid ep ->
729 RemoteEndPointValid ep ->
676 return (remoteSocket ep)
730 return (remoteSocket ep)
677− RemoteEndPointClosing ep _ ->
731+ RemoteEndPointClosing _ ep ->
678 return (remoteSocket ep)
732 return (remoteSocket ep)
680 error "handleIncomingMessages RELY violation"
734 error "handleIncomingMessages RELY violation"
682− tryIO $ go sock openConnections
736+ tryIO $ go sock
738+ -- Normally, there would be no unclosed connections, but if the client
739+ -- exits prematurely there might be
683− unclosedConnections <- readIORef openConnections
740+ unclosedConnections <- modifyMVar theirState $ \st ->
742+ RemoteEndPointInvalid _ ->
743+ error "handleIncomingMessages RELY violation"
744+ RemoteEndPointValid remoteConn ->
745+ return (RemoteEndPointValid . (remoteIncoming ^= IntSet.empty) $ remoteConn, remoteConn ^. remoteIncoming)
746+ RemoteEndPointClosing resolved remoteConn ->
747+ return (RemoteEndPointClosing resolved . (remoteIncoming ^= IntSet.empty) $ remoteConn, remoteConn ^. remoteIncoming)
748+ RemoteEndPointClosed ->
749+ return (st, IntSet.empty)
751+ -- TODO: should we give a single ErrorEvent instead?
684− forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $
752+ forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $ \cid -> do
685− writeChan ourChannel . ConnectionClosed
753+ writeChan ourChannel $ ConnectionClosed cid
755+ -- TODO: if the endpoint was not in closing state, should we make it so?
687 -- Dispatch
757 -- Dispatch
688− go :: N.Socket -> IORef IntSet -> IO ()
758+ go :: N.Socket -> IO ()
689− go sock openConnections = do
759+ go sock = do
690 connId <- recvInt32 sock
760 connId <- recvInt32 sock
691 if connId >= firstNonReservedConnectionId
761 if connId >= firstNonReservedConnectionId
693 readMessage sock connId
763 readMessage sock connId
694− go sock openConnections
764+ go sock
696 case tryToEnum (fromIntegral connId) of
766 case tryToEnum (fromIntegral connId) of
697 Just RequestConnectionId -> do
767 Just RequestConnectionId -> do
698− recvInt32 sock >>= createNewConnection openConnections
768+ recvInt32 sock >>= createNewConnection
699− go sock openConnections
769+ go sock
700 Just ControlResponse -> do
770 Just ControlResponse -> do
701 recvInt32 sock >>= readControlResponse sock
771 recvInt32 sock >>= readControlResponse sock
702− go sock openConnections
772+ go sock
703 Just CloseConnection -> do
773 Just CloseConnection -> do
704− recvInt32 sock >>= closeConnection openConnections
774+ recvInt32 sock >>= closeConnection
705− go sock openConnections
775+ go sock
706 Just CloseSocket -> do
776 Just CloseSocket -> do
707− closeSocket sock
777+ didClose <- closeSocket sock
708− go sock openConnections
778+ unless didClose $ go sock
709 Nothing ->
779 Nothing ->
710 -- Invalid control request, exit
780 -- Invalid control request, exit
711 hPutStrLn stderr "Warning: invalid control request"
781 hPutStrLn stderr "Warning: invalid control request"
713 -- Create a new connection
783 -- Create a new connection
714− createNewConnection :: IORef IntSet -> ControlRequestId -> IO ()
784+ createNewConnection :: ControlRequestId -> IO ()
715− createNewConnection openConnections reqId = do
785+ createNewConnection reqId = do
786+ newId <- getNextConnectionId ourEndPoint
716− modifyMVar_ theirState $ \st ->
787+ modifyMVar_ theirState $ \st -> do
717− case st of
788+ remoteConn <- case st of
718− RemoteEndPointValid ep ->
789+ RemoteEndPointValid remoteConn ->
719− return (RemoteEndPointValid . (remoteRefCount ^+= 1) $ ep)
790+ return (remoteIncoming ^%= IntSet.insert (fromIntegral newId) $ remoteConn)
720− RemoteEndPointClosing ep resolved -> do
791+ RemoteEndPointClosing resolved remoteConn -> do
721 -- If the endpoint is in closing state that means we send a
792 -- If the endpoint is in closing state that means we send a
722 -- CloseSocket request to the remote endpoint. If the remote
793 -- CloseSocket request to the remote endpoint. If the remote
723 -- endpoint replies with the request to create a new connection, it
794 -- endpoint replies with the request to create a new connection, it
@@ -725,19 +796,17 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
725 -- ours. Either way, at this point we simply restore the endpoint
796 -- ours. Either way, at this point we simply restore the endpoint
726 -- to RemoteEndPointValid
797 -- to RemoteEndPointValid
727 putMVar resolved ()
798 putMVar resolved ()
728− return (RemoteEndPointValid . (remoteRefCount ^= 1) $ ep)
799+ return (remoteIncoming ^= IntSet.singleton newId $ remoteConn)
730 error "handleIncomingMessages RELY violation"
801 error "handleIncomingMessages RELY violation"
731− newId <- getNextConnectionId ourEndPoint
732− withMVar theirState $ \(RemoteEndPointValid remoteConn) ->
733 sendOn remoteConn ( encodeInt32 ControlResponse
802 sendOn remoteConn ( encodeInt32 ControlResponse
734 : encodeInt32 reqId
803 : encodeInt32 reqId
735 : prependLength [encodeInt32 newId]
804 : prependLength [encodeInt32 newId]
806+ -- We add the new connection ID to the list of open connections only once the
807+ -- endpoint has been notified of the new connection (sendOn may fail)
808+ return (RemoteEndPointValid remoteConn)
737 writeChan ourChannel (ConnectionOpened newId ReliableOrdered theirAddr)
809 writeChan ourChannel (ConnectionOpened newId ReliableOrdered theirAddr)
738− -- We add the new connection ID to the list of open connections only once the
739− -- endpoint has been notified of the new connection (sendOn may fail)
740− modifyIORef openConnections (IntSet.insert (fromIntegral newId))
742 -- Read a control response
811 -- Read a control response
743 readControlResponse :: N.Socket -> ControlRequestId -> IO ()
812 readControlResponse :: N.Socket -> ControlRequestId -> IO ()
@@ -753,22 +822,24 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
753 putMVar mvar response
822 putMVar mvar response
755 -- Close a connection
824 -- Close a connection
756− closeConnection :: IORef IntSet -> ConnectionId -> IO ()
825+ closeConnection :: ConnectionId -> IO ()
757− closeConnection openConnections cid = do
826+ closeConnection cid = do
827+ -- TODO: we should check that this connection is in fact open
758 writeChan ourChannel (ConnectionClosed cid)
828 writeChan ourChannel (ConnectionClosed cid)
759− decreaseRemoteRefCount theirEndPoint
829+ modifyMVar_ theirState $ \(RemoteEndPointValid remoteConn) ->
760− modifyIORef openConnections (IntSet.delete (fromIntegral cid))
830+ return (RemoteEndPointValid . (remoteIncoming ^%= IntSet.delete cid) $ remoteConn)
831+ closeIfUnused theirEndPoint
762 -- Close the socket (if we don't have any outgoing connections)
833 -- Close the socket (if we don't have any outgoing connections)
763 closeSocket :: N.Socket -> IO Bool
834 closeSocket :: N.Socket -> IO Bool
764 closeSocket sock = do
835 closeSocket sock = do
765 didClose <- modifyMVar theirState $ \st ->
836 didClose <- modifyMVar theirState $ \st ->
766 case st of
837 case st of
767− RemoteEndPointValid remoteConn | remoteConn ^. remoteRefCount == 0 -> do
838+ RemoteEndPointValid remoteConn | remoteConn ^. remoteOutgoing == 0 -> do
768 sendOn remoteConn [encodeInt32 CloseSocket]
839 sendOn remoteConn [encodeInt32 CloseSocket]
769 N.sClose sock
840 N.sClose sock
770 return (RemoteEndPointClosed, True)
841 return (RemoteEndPointClosed, True)
771− RemoteEndPointClosing _ resolved -> do
842+ RemoteEndPointClosing resolved _ -> do
772 -- If the socket is already in closing state, then we have already
843 -- If the socket is already in closing state, then we have already
773 -- sent a CloseSocket request; hence, we don't need to do it again
844 -- sent a CloseSocket request; hence, we don't need to do it again
774 -- at this point
845 -- at this point
@@ -785,10 +856,12 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
785 readMessage sock connId = recvWithLength sock >>= writeChan ourChannel . Received connId
856 readMessage sock connId = recvWithLength sock >>= writeChan ourChannel . Received connId
787 -- Arguments
858 -- Arguments
788− ourChannel = localChannel ourEndPoint
859+ ourChannel = localChannel ourEndPoint
789− ourState = localState ourEndPoint
860+ ourState = localState ourEndPoint
790− theirState = remoteState theirEndPoint
861+ theirState = remoteState theirEndPoint
791− theirAddr = remoteAddress theirEndPoint
862+ theirAddr = remoteAddress theirEndPoint
793-- | Get the next connection ID
866-- | Get the next connection ID
794getNextConnectionId :: LocalEndPoint -> IO ConnectionId
867getNextConnectionId :: LocalEndPoint -> IO ConnectionId
@@ -809,7 +882,7 @@ firstNonReservedConnectionId = 1024
809-- Lens definitions --
882-- Lens definitions --
810--------------------------------------------------------------------------------
883--------------------------------------------------------------------------------
812−localEndPoints :: Lens TransportState (IntMap LocalEndPoint)
885+localEndPoints :: Lens TransportState (Map EndPointAddress LocalEndPoint)
813localEndPoints = lens _localEndPoints (\es st -> st { _localEndPoints = es })
886localEndPoints = lens _localEndPoints (\es st -> st { _localEndPoints = es })
815acceptNewConnections :: Lens TransportState Bool
888acceptNewConnections :: Lens TransportState Bool
@@ -827,8 +900,11 @@ nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = c
827localConnections :: Lens LocalState (Map EndPointAddress RemoteEndPoint)
900localConnections :: Lens LocalState (Map EndPointAddress RemoteEndPoint)
828localConnections = lens _localConnections (\es st -> st { _localConnections = es })
901localConnections = lens _localConnections (\es st -> st { _localConnections = es })
903+internalThreads :: Lens LocalState [ThreadId]
904+internalThreads = lens _internalThreads (\ts st -> st { _internalThreads = ts })
830−localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
906+localEndPointAt :: EndPointAddress -> Lens TransportState (Maybe LocalEndPoint)
831−localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
907+localEndPointAt addr = localEndPoints >>> mapLens addr
833pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
909pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
834pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
910pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
@@ -836,5 +912,8 @@ pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
836connectionTo :: EndPointAddress -> Lens LocalState (Maybe RemoteEndPoint)
912connectionTo :: EndPointAddress -> Lens LocalState (Maybe RemoteEndPoint)
837connectionTo addr = localConnections >>> mapLens addr
913connectionTo addr = localConnections >>> mapLens addr
839−remoteRefCount :: Lens RemoteConnection Int
915+remoteOutgoing :: Lens RemoteConnection Int
840−remoteRefCount = lens _remoteRefCount (\rc conn -> conn { _remoteRefCount = rc })
916+remoteOutgoing = lens _remoteOutgoing (\cs conn -> conn { _remoteOutgoing = cs })
918+remoteIncoming :: Lens RemoteConnection IntSet
919+remoteIncoming = lens _remoteIncoming (\cs conn -> conn { _remoteIncoming = cs })