git.iko.soy/distributed-process
on master help sign in
Better bookkeeping

We need to keep more information so that we can implement closeEndPoint and closeTransport (that's not yet done though).

commit401984b4325b4d236fff240d1b2c1f70557101bd authorEdsko de Vries <edsko@edsko.net> dateWed 9 May 2012, 17:46 (14 years ago) parent41668d1
6 files changed+256−158
network-transport-2/src/Network/Transport.hs+7−6 network-transport-2/src/Network/Transport/Internal/TCP.hs+1−1 network-transport-2/src/Network/Transport/TCP.hs+190−111 network-transport-2/tests/TestAuxiliary.hs+13−4 network-transport-2/tests/TestTCP.hs+10−9 network-transport-2/tests/TestTransport.hs+35−27
network-transport-2/src/Network/Transport.hs+7−6
@@ -16,6 +16,7 @@ module Network.Transport ( -- * Types
1616 , NewMulticastGroupErrorCode(..)
1717 , ResolveMulticastGroupErrorCode(..)
1818 , SendErrorCode(..)
19+ , EventErrorCode(..)
1920 ) where
2021
2122import Data.ByteString (ByteString)
@@ -69,7 +70,7 @@ data Event =
6970 -- | Received multicast
7071 | ReceivedMulticast MulticastAddress [ByteString]
7172 -- | Error that caused a bunch of connections to close (possibly none)
72− | ErrorEvent ErrorEventErrorCode [ConnectionId]
73+ | ErrorEvent EventErrorCode [ConnectionId]
7374 deriving Show
7475
7576-- | Connection IDs enable receivers to distinguish one connection from another.
@@ -142,12 +143,12 @@ data ResolveMulticastGroupErrorCode =
142143
143144-- | Failure during sending a message
144145data SendErrorCode =
145− SendConnectionClosed -- ^ Connection was closed manually or because of an error
146− | SendFailed -- ^ Send failed for some other reason
146+ SendConnectionClosed -- ^ Connection was closed manually or because of an error
147+ | SendFailed -- ^ Send failed for some other reason
147148 deriving Show
148149
149150-- | Error codes used when reporting errors to endpoints (through receive)
150−data ErrorEventErrorCode =
151− ErrorEventEndPointClosed -- ^ Endpoint was closed (manually or because of an error)
152− | TransportFatalError -- ^ Transport-wide fatal error
151+data EventErrorCode =
152+ EventErrorEndPointClosed -- ^ Endpoint was closed (manually or because of an error)
153+ | EventErrorTransportFailure -- ^ Transport-wide fatal error
153154 deriving Show
network-transport-2/src/Network/Transport/Internal/TCP.hs+1−1
@@ -26,7 +26,7 @@ import qualified Network.Socket as N ( HostName
2626import qualified Network.Socket.ByteString as NBS (recv)
2727import Control.Concurrent (forkIO, ThreadId)
2828import Control.Monad (mzero, MonadPlus, liftM, forever)
29−import Control.Exception (SomeException, try, handle)
29+import Control.Exception (SomeException, handle)
3030import Data.ByteString (ByteString)
3131import qualified Data.ByteString as BS (length, concat, null)
3232import Data.Int (Int32)
network-transport-2/src/Network/Transport/TCP.hs+190−111
@@ -46,7 +46,7 @@ import qualified Network.Socket as N ( HostName
4646 , connect
4747 )
4848import Network.Socket.ByteString (sendMany)
49−import Control.Concurrent (forkIO, ThreadId, killThread)
49+import Control.Concurrent (forkIO, ThreadId, killThread, myThreadId)
5050import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
5151import Control.Concurrent.MVar ( MVar
5252 , newMVar
@@ -57,25 +57,27 @@ import Control.Concurrent.MVar ( MVar
5757 , putMVar
5858 , newEmptyMVar
5959 , withMVar
60+ , tryTakeMVar
6061 )
6162import Control.Category ((>>>))
6263import Control.Applicative ((<*>), (*>), (<$>))
63−import Control.Monad (forM_, void, when)
64+import Control.Monad (forM_, void, when, mzero, unless)
6465import Control.Monad.Error (ErrorT(ErrorT), runErrorT, throwError)
66+import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
6567import Control.Monad.IO.Class (liftIO)
6668import Control.Exception (IOException, SomeException)
67−import Data.IORef (IORef, newIORef, writeIORef, readIORef, modifyIORef)
69+import Data.IORef (IORef, newIORef, writeIORef, readIORef)
6870import Data.ByteString (ByteString)
6971import qualified Data.ByteString as BS (concat)
7072import qualified Data.ByteString.Char8 as BSC (pack, unpack)
7173import Data.Int (Int32)
7274import Data.IntMap (IntMap)
73−import qualified Data.IntMap as IntMap (empty, size)
75+import qualified Data.IntMap as IntMap (empty)
7476import 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)
7678import Data.Map (Map)
77−import qualified Data.Map as Map (empty)
78−import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
79+import qualified Data.Map as Map (empty, elems, size)
80+import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=), (^%=))
7981import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
8082import System.IO (hPutStrLn, stderr)
8183
@@ -183,18 +185,18 @@ data LocalEndPoint = LocalEndPoint { localAddress :: EndPointAddress
183185 }
184186data RemoteEndPoint = RemoteEndPoint { remoteAddress :: EndPointAddress
185187 , remoteState :: MVar RemoteState
186− , remoteThread :: ThreadId
187188 }
188189
189190-- We use underscores for fields that we might update (using lenses).
190191
191−data TransportState = TransportState { _localEndPoints :: IntMap LocalEndPoint
192+data TransportState = TransportState { _localEndPoints :: Map EndPointAddress LocalEndPoint
192193 , _acceptNewConnections :: Bool
193194 }
194195data LocalState = LocalState { _nextConnectionId :: ConnectionId
195196 , _pendingCtrlRequests :: IntMap (MVar [ByteString])
196197 , _nextCtrlRequestId :: ControlRequestId
197198 , _localConnections :: Map EndPointAddress RemoteEndPoint
199+ , _internalThreads :: [ThreadId]
198200 }
199201
200202-- A remote endpoint has incoming and outgoing connections, and when the total
@@ -238,9 +240,10 @@ data LocalState = LocalState { _nextConnectionId :: ConnectionId
238240
239241data RemoteState = RemoteEndPointInvalid (FailedWith ConnectErrorCode)
240242 | RemoteEndPointValid RemoteConnection
241− | RemoteEndPointClosing RemoteConnection (MVar ())
243+ | RemoteEndPointClosing (MVar ()) RemoteConnection
242244 | RemoteEndPointClosed
243−data RemoteConnection = RemoteConnection { _remoteRefCount :: !Int
245+data RemoteConnection = RemoteConnection { _remoteOutgoing :: !Int
246+ , _remoteIncoming :: IntSet
244247 , remoteSocket :: N.Socket
245248 , sendOn :: [ByteString] -> IO ()
246249 }
@@ -273,7 +276,7 @@ data ConnectionRequestResponse =
273276-- TODOs: deal with hints
274277createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
275278createTransport host port = do
276− state <- newMVar TransportState { _localEndPoints = IntMap.empty
279+ state <- newMVar TransportState { _localEndPoints = Map.empty
277280 , _acceptNewConnections = True
278281 }
279282 let transport = TCPTransport { transportState = state
@@ -308,9 +311,10 @@ apiCloseTransport transportThread = do
308311apiNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
309312apiNewEndPoint transport = do
310313 ourEndPoint <- createLocalEndPoint transport
311− return . Right $ EndPoint { receive = readChan (localChannel ourEndPoint)
312− , address = localAddress ourEndPoint
313− , connect = apiConnect ourEndPoint
314+ return . Right $ EndPoint { receive = readChan (localChannel ourEndPoint)
315+ , address = localAddress ourEndPoint
316+ , connect = apiConnect ourEndPoint
317+ , closeEndPoint = apiCloseEndPoint transport ourEndPoint
314318 , newMulticastGroup = return . Left $ newMulticastGroupError
315319 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
316320 }
@@ -345,14 +349,16 @@ apiConnect ourEndPoint theirAddress _ = runErrorT $ do
345349-- TODO: We ignore errors during a close. Is that right?
346350apiClose :: RemoteEndPoint -> ConnectionId -> IORef Bool -> IO ()
347351apiClose theirEndPoint connId connAlive = void . tryIO $ do
348− wasAlive <- withMVar (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) -> do
352+ modifyMVar_ (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) -> do
349353 alive <- readIORef connAlive
350− when alive $ do
351− sendOn remoteConn [encodeInt32 CloseConnection, encodeInt32 connId]
352− writeIORef connAlive False
353− return alive
354− -- We don't want to call decreaseRemoteRefCount while we still hold the lock
355− when wasAlive $ decreaseRemoteRefCount theirEndPoint
354+ if alive
355+ then do
356+ sendOn remoteConn [encodeInt32 CloseConnection, encodeInt32 connId]
357+ writeIORef connAlive False
358+ return (RemoteEndPointValid . (remoteOutgoing ^+= (-1)) $ remoteConn)
359+ else
360+ return (RemoteEndPointValid remoteConn)
361+ closeIfUnused theirEndPoint
356362
357363-- | Send data across a connection
358364--
@@ -369,6 +375,37 @@ apiSend theirEndPoint connId connAlive payload = do
369375 else
370376 throwError $ FailedWith SendConnectionClosed "Connection closed"
371377
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)
397+ where
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
403+ Nothing -> mzero
404+ Just (RemoteEndPointInvalid _) -> mzero
405+ Just (RemoteEndPointValid conn) -> return $ remoteSocket conn
406+ Just (RemoteEndPointClosing _ conn) -> return $ remoteSocket conn
407+ Just RemoteEndPointClosed -> mzero
408+
372409-- | Special case of 'apiConnect': connect an endpoint to itself
373410connectToSelf :: LocalEndPoint -> IO (Either (FailedWith ConnectErrorCode) Connection)
374411connectToSelf ourEndPoint = do
@@ -411,17 +448,16 @@ createLocalEndPoint transport = do
411448 , _pendingCtrlRequests = IntMap.empty
412449 , _nextCtrlRequestId = 0
413450 , _localConnections = Map.empty
451+ , _internalThreads = []
414452 }
415− let host = transportHost transport
416− let port = transportPort transport
417453 modifyMVar (transportState transport) $ \st -> do
418− let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
419− let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show ix
420− let localEndPoint = LocalEndPoint { localAddress = addr
421− , localChannel = chan
422− , localState = state
454+ let ix = fromIntegral $ Map.size (st ^. localEndPoints)
455+ let addr = encodeEndPointAddress transport ix
456+ let localEndPoint = LocalEndPoint { localAddress = addr
457+ , localChannel = chan
458+ , localState = state
423459 }
424− return ((localEndPointAt ix ^= Just localEndPoint) $ st, localEndPoint)
460+ return ((localEndPointAt addr ^= Just localEndPoint) $ st, localEndPoint)
425461
426462-- | Request a connection to a remote endpoint
427463--
@@ -435,17 +471,22 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
435471 where
436472 go = do
437473 let ourState = localState ourEndPoint
438− theirEndPoint <- modifyMVar ourState $ \st ->
474+ (theirEndPoint, isNew) <- modifyMVar ourState $ \st ->
439475 case st ^. connectionTo theirAddress of
440476 Just theirEndPoint ->
441− return (st, theirEndPoint)
477+ return (st, (theirEndPoint, False))
442478 Nothing -> do
443479 theirState <- newEmptyMVar
444480 let theirEndPoint = RemoteEndPoint { remoteAddress = theirAddress
445481 , remoteState = theirState
446482 }
447− forkIO $ connectToRemoteEndPoint (ourEndPoint, theirEndPoint)
448− return (connectionTo theirAddress ^= Just theirEndPoint $ st, theirEndPoint)
483+ return (connectionTo theirAddress ^= Just theirEndPoint $ st, (theirEndPoint, True))
484+
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)
449490
450491 -- Before we initiate the new connection request we want to make sure
451492 -- that refcount on the endpoint is incremented so that a concurrent
@@ -456,7 +497,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
456497 endPointStateSnapshot <- modifyMVar theirState $ \st ->
457498 case st of
458499 RemoteEndPointValid ep ->
459− return (RemoteEndPointValid . (remoteRefCount ^+= 1) $ ep, st)
500+ return (RemoteEndPointValid . (remoteOutgoing ^+= 1) $ ep, st)
460501 _ ->
461502 return (st, st)
462503
@@ -471,7 +512,7 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
471512 RemoteEndPointInvalid err -> do
472513 return . Left $ err
473514
474− RemoteEndPointClosing _ resolved -> do
515+ RemoteEndPointClosing resolved _ -> do
475516 -- If the remote endpoint is closing, then we need to block until
476517 -- this is resolved and we then try again
477518 readMVar resolved
@@ -498,12 +539,13 @@ requestConnectionTo ourEndPoint theirAddress = ErrorT go
498539 -- valid state at this point.
499540 let failed err = do
500541 modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
501− return (RemoteEndPointValid . (remoteRefCount ^+= (-1)) $ ep)
542+ return (RemoteEndPointValid . (remoteOutgoing ^+= (-1)) $ ep)
543+ -- TODO: should we call closeIfUnused here?
502544 return . Left $ FailedWith ConnectFailed err
503545
504546 case mcid of
505547 Left err -> failed (show err)
506− Right Nothing -> failed ("Invalid integer")
548+ Right Nothing -> failed "Invalid integer"
507549 Right (Just cid) -> return . Right $ (theirEndPoint, cid)
508550
509551-- | Establish a connection to a remote endpoint
@@ -520,11 +562,12 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
520562 case result of
521563 Right (ConnectionRequestAccepted, sock) -> do
522564 let remoteConn = RemoteConnection { remoteSocket = sock
523− , _remoteRefCount = 0
565+ , _remoteOutgoing = 0
566+ , _remoteIncoming = IntSet.empty
524567 , sendOn = sendMany sock
525568 }
526569 putMVar theirState (RemoteEndPointValid remoteConn)
527− void . forkIO $ handleIncomingMessages (ourEndPoint, theirEndPoint)
570+ handleIncomingMessages (ourEndPoint, theirEndPoint)
528571 Right (ConnectionRequestEndPointInvalid, sock) -> do
529572 -- We remove the endpoint from our local state again because the next
530573 -- call to 'connect' might give a different result. Threads that were
@@ -569,6 +612,11 @@ connectToRemoteEndPoint (ourEndPoint, theirEndPoint) = do
569612 ourAddress :: ByteString
570613 EndPointAddress ourAddress = localAddress ourEndPoint
571614
615+-- | Encode end point address
616+encodeEndPointAddress :: TCPTransport -> EndPointId -> EndPointAddress
617+encodeEndPointAddress transport ix = EndPointAddress . BSC.pack $
618+ transportHost transport ++ ":" ++ transportPort transport ++ ":" ++ show ix
619+
572620-- | Decode end point address
573621-- TODO: This uses regular expression parsing, which is nice, but unnecessary
574622decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
@@ -581,7 +629,7 @@ decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack
581629--
582630-- RELY: Remote endpoint must be in valid state.
583631-- GUARANTEE: Will not change the state of the remote endpoint.
584−doRemoteRequest :: EndPointPair -> ControlHeader -> IO ([ByteString])
632+doRemoteRequest :: EndPointPair -> ControlHeader -> IO [ByteString]
585633doRemoteRequest (ourEndPoint, theirEndPoint) header = do
586634 reply <- newEmptyMVar
587635 reqId <- modifyMVar (localState ourEndPoint) $ \st -> do
@@ -591,22 +639,32 @@ doRemoteRequest (ourEndPoint, theirEndPoint) header = do
591639 sendOn remoteConn [encodeInt32 header, encodeInt32 reqId]
592640 takeMVar reply
593641
594−-- | Decrease the remote refcount of a remote endpoint. When the refcount
595−-- reaches zero, initiate socket closure.
596−--
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 ()
601−decreaseRemoteRefCount theirEndPoint = do
602− modifyMVar_ (remoteState theirEndPoint) $ \(RemoteEndPointValid remoteConn) ->
603− if remoteConn ^. remoteRefCount == 1
604− then do
605− resolved <- newEmptyMVar
606− sendOn remoteConn [encodeInt32 CloseSocket]
607− return (RemoteEndPointClosing remoteConn resolved)
608− else do
609− return (RemoteEndPointValid . (remoteRefCount ^+= (-1)) $ remoteConn)
642+-- | Check if the remote endpoint is unused, and if so, send a CloseSocket request
643+closeIfUnused :: RemoteEndPoint -> IO ()
644+closeIfUnused theirEndPoint = modifyMVar_ (remoteState theirEndPoint) $ \st ->
645+ case st of
646+ RemoteEndPointValid remoteConn -> do
647+ if remoteConn ^. remoteOutgoing == 0 && IntSet.null (remoteConn ^. remoteIncoming)
648+ then do
649+ resolved <- newEmptyMVar
650+ sendOn remoteConn [encodeInt32 CloseSocket]
651+ return (RemoteEndPointClosing resolved remoteConn)
652+ else
653+ return st
654+ _ -> do
655+ return st
656+
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)
663+ where
664+ removeThread :: IO ()
665+ removeThread = do
666+ tid <- myThreadId
667+ modifyMVar_ (localState ourEndPoint) $ return . (internalThreads ^%= filter (/= tid))
610668
611669--------------------------------------------------------------------------------
612670-- Incoming requests --
@@ -616,17 +674,17 @@ decreaseRemoteRefCount theirEndPoint = do
616674-- establish a TCP connection with us)
617675handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
618676handleConnectionRequest transport sock = do
619− let tState = transportState transport
620677 request <- tryIO $ do
621678 ourEndPointId <- recvInt32 sock
622679 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
623− ourEndPoint <- (^. localEndPointAt ourEndPointId) <$> readMVar tState
680+ let ourAddress = encodeEndPointAddress transport ourEndPointId
681+ ourEndPoint <- (^. localEndPointAt ourAddress) <$> readMVar (transportState transport)
624682 return (theirAddress, ourEndPoint)
625683 case request of
626684 Right (_, Nothing) -> do
627685 sendMany sock [encodeInt32 ConnectionRequestEndPointInvalid]
628686 N.sClose sock
629− Right (theirAddress, Just ourEndPoint) -> do
687+ Right (theirAddress, Just ourEndPoint) -> forkEndPointThread ourEndPoint $ do
630688 (crossed, theirEndPoint) <- modifyMVar (localState ourEndPoint) $ \st ->
631689 case st ^. connectionTo theirAddress of
632690 Nothing -> do
@@ -643,14 +701,14 @@ handleConnectionRequest transport sock = do
643701 N.sClose sock
644702 else do
645703 let remoteConn = RemoteConnection { remoteSocket = sock
646− , _remoteRefCount = 0
704+ , _remoteOutgoing = 0
705+ , _remoteIncoming = IntSet.empty
647706 , sendOn = sendMany sock
648707 }
708+ -- TODO: this putMVar might block
649709 putMVar (remoteState theirEndPoint) (RemoteEndPointValid remoteConn)
650710 sendMany sock [encodeInt32 ConnectionRequestAccepted]
651− -- TODO: we should store the tid as part of the endpoint state
652− tid <- forkIO $ handleIncomingMessages (ourEndPoint, theirEndPoint)
653− return ()
711+ handleIncomingMessages (ourEndPoint, theirEndPoint)
654712 Left _ -> do
655713 -- Invalid request
656714 N.sClose sock
@@ -665,59 +723,72 @@ handleConnectionRequest transport sock = do
665723-- GUARANTEE: May change the remote endpoint to RemoteEndPointClosed state.
666724handleIncomingMessages :: EndPointPair -> IO ()
667725handleIncomingMessages (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
671−
672726 -- For efficiency sake we get the socket once and for all
673727 sock <- withMVar theirState $ \st ->
674728 case st of
675729 RemoteEndPointValid ep ->
676730 return (remoteSocket ep)
677− RemoteEndPointClosing ep _ ->
731+ RemoteEndPointClosing _ ep ->
678732 return (remoteSocket ep)
679733 _ ->
680734 error "handleIncomingMessages RELY violation"
681735
682− tryIO $ go sock openConnections
683− unclosedConnections <- readIORef openConnections
684− forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $
685− writeChan ourChannel . ConnectionClosed
736+ tryIO $ go sock
737+
738+ -- Normally, there would be no unclosed connections, but if the client
739+ -- exits prematurely there might be
740+ unclosedConnections <- modifyMVar theirState $ \st ->
741+ case st of
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)
750+
751+ -- TODO: should we give a single ErrorEvent instead?
752+ forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $ \cid -> do
753+ writeChan ourChannel $ ConnectionClosed cid
754+
755+ -- TODO: if the endpoint was not in closing state, should we make it so?
686756 where
687757 -- Dispatch
688− go :: N.Socket -> IORef IntSet -> IO ()
689− go sock openConnections = do
758+ go :: N.Socket -> IO ()
759+ go sock = do
690760 connId <- recvInt32 sock
691761 if connId >= firstNonReservedConnectionId
692762 then do
693763 readMessage sock connId
694− go sock openConnections
764+ go sock
695765 else do
696766 case tryToEnum (fromIntegral connId) of
697767 Just RequestConnectionId -> do
698− recvInt32 sock >>= createNewConnection openConnections
699− go sock openConnections
768+ recvInt32 sock >>= createNewConnection
769+ go sock
700770 Just ControlResponse -> do
701771 recvInt32 sock >>= readControlResponse sock
702− go sock openConnections
772+ go sock
703773 Just CloseConnection -> do
704− recvInt32 sock >>= closeConnection openConnections
705− go sock openConnections
774+ recvInt32 sock >>= closeConnection
775+ go sock
706776 Just CloseSocket -> do
707− closeSocket sock
708− go sock openConnections
777+ didClose <- closeSocket sock
778+ unless didClose $ go sock
709779 Nothing ->
710780 -- Invalid control request, exit
711781 hPutStrLn stderr "Warning: invalid control request"
712782
713783 -- Create a new connection
714− createNewConnection :: IORef IntSet -> ControlRequestId -> IO ()
715− createNewConnection openConnections reqId = do
716− modifyMVar_ theirState $ \st ->
717− case st of
718− RemoteEndPointValid ep ->
719− return (RemoteEndPointValid . (remoteRefCount ^+= 1) $ ep)
720− RemoteEndPointClosing ep resolved -> do
784+ createNewConnection :: ControlRequestId -> IO ()
785+ createNewConnection reqId = do
786+ newId <- getNextConnectionId ourEndPoint
787+ modifyMVar_ theirState $ \st -> do
788+ remoteConn <- case st of
789+ RemoteEndPointValid remoteConn ->
790+ return (remoteIncoming ^%= IntSet.insert (fromIntegral newId) $ remoteConn)
791+ RemoteEndPointClosing resolved remoteConn -> do
721792 -- If the endpoint is in closing state that means we send a
722793 -- CloseSocket request to the remote endpoint. If the remote
723794 -- endpoint replies with the request to create a new connection, it
@@ -725,19 +796,17 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
725796 -- ours. Either way, at this point we simply restore the endpoint
726797 -- to RemoteEndPointValid
727798 putMVar resolved ()
728− return (RemoteEndPointValid . (remoteRefCount ^= 1) $ ep)
799+ return (remoteIncoming ^= IntSet.singleton newId $ remoteConn)
729800 _ ->
730801 error "handleIncomingMessages RELY violation"
731− newId <- getNextConnectionId ourEndPoint
732− withMVar theirState $ \(RemoteEndPointValid remoteConn) ->
733802 sendOn remoteConn ( encodeInt32 ControlResponse
734803 : encodeInt32 reqId
735804 : prependLength [encodeInt32 newId]
736805 )
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)
737809 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))
741810
742811 -- Read a control response
743812 readControlResponse :: N.Socket -> ControlRequestId -> IO ()
@@ -753,22 +822,24 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
753822 putMVar mvar response
754823
755824 -- Close a connection
756− closeConnection :: IORef IntSet -> ConnectionId -> IO ()
757− closeConnection openConnections cid = do
825+ closeConnection :: ConnectionId -> IO ()
826+ closeConnection cid = do
827+ -- TODO: we should check that this connection is in fact open
758828 writeChan ourChannel (ConnectionClosed cid)
759− decreaseRemoteRefCount theirEndPoint
760− modifyIORef openConnections (IntSet.delete (fromIntegral cid))
829+ modifyMVar_ theirState $ \(RemoteEndPointValid remoteConn) ->
830+ return (RemoteEndPointValid . (remoteIncoming ^%= IntSet.delete cid) $ remoteConn)
831+ closeIfUnused theirEndPoint
761832
762833 -- Close the socket (if we don't have any outgoing connections)
763834 closeSocket :: N.Socket -> IO Bool
764835 closeSocket sock = do
765836 didClose <- modifyMVar theirState $ \st ->
766837 case st of
767− RemoteEndPointValid remoteConn | remoteConn ^. remoteRefCount == 0 -> do
838+ RemoteEndPointValid remoteConn | remoteConn ^. remoteOutgoing == 0 -> do
768839 sendOn remoteConn [encodeInt32 CloseSocket]
769840 N.sClose sock
770841 return (RemoteEndPointClosed, True)
771− RemoteEndPointClosing _ resolved -> do
842+ RemoteEndPointClosing resolved _ -> do
772843 -- If the socket is already in closing state, then we have already
773844 -- sent a CloseSocket request; hence, we don't need to do it again
774845 -- at this point
@@ -785,10 +856,12 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
785856 readMessage sock connId = recvWithLength sock >>= writeChan ourChannel . Received connId
786857
787858 -- Arguments
788− ourChannel = localChannel ourEndPoint
789− ourState = localState ourEndPoint
790− theirState = remoteState theirEndPoint
791− theirAddr = remoteAddress theirEndPoint
859+ ourChannel = localChannel ourEndPoint
860+ ourState = localState ourEndPoint
861+ theirState = remoteState theirEndPoint
862+ theirAddr = remoteAddress theirEndPoint
863+
864+
792865
793866-- | Get the next connection ID
794867getNextConnectionId :: LocalEndPoint -> IO ConnectionId
@@ -809,7 +882,7 @@ firstNonReservedConnectionId = 1024
809882-- Lens definitions --
810883--------------------------------------------------------------------------------
811884
812−localEndPoints :: Lens TransportState (IntMap LocalEndPoint)
885+localEndPoints :: Lens TransportState (Map EndPointAddress LocalEndPoint)
813886localEndPoints = lens _localEndPoints (\es st -> st { _localEndPoints = es })
814887
815888acceptNewConnections :: Lens TransportState Bool
@@ -827,8 +900,11 @@ nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = c
827900localConnections :: Lens LocalState (Map EndPointAddress RemoteEndPoint)
828901localConnections = lens _localConnections (\es st -> st { _localConnections = es })
829902
830−localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
831−localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
903+internalThreads :: Lens LocalState [ThreadId]
904+internalThreads = lens _internalThreads (\ts st -> st { _internalThreads = ts })
905+
906+localEndPointAt :: EndPointAddress -> Lens TransportState (Maybe LocalEndPoint)
907+localEndPointAt addr = localEndPoints >>> mapLens addr
832908
833909pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
834910pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
@@ -836,5 +912,8 @@ pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
836912connectionTo :: EndPointAddress -> Lens LocalState (Maybe RemoteEndPoint)
837913connectionTo addr = localConnections >>> mapLens addr
838914
839−remoteRefCount :: Lens RemoteConnection Int
840−remoteRefCount = lens _remoteRefCount (\rc conn -> conn { _remoteRefCount = rc })
915+remoteOutgoing :: Lens RemoteConnection Int
916+remoteOutgoing = lens _remoteOutgoing (\cs conn -> conn { _remoteOutgoing = cs })
917+
918+remoteIncoming :: Lens RemoteConnection IntSet
919+remoteIncoming = lens _remoteIncoming (\cs conn -> conn { _remoteIncoming = cs })
network-transport-2/tests/TestAuxiliary.hs+13−4
@@ -1,7 +1,8 @@
1−module TestAuxiliary (runTest, forkTry) where
1+module TestAuxiliary (runTest, runTests, forkTry) where
22
33import Prelude hiding (catch)
44import Control.Concurrent (myThreadId, forkIO, ThreadId, throwTo)
5+import Control.Monad (liftM2, unless)
56import Control.Exception (SomeException, try, catch)
67import System.Timeout (timeout)
78import System.IO (stdout, hFlush)
@@ -19,7 +20,7 @@ forkTry p = do
1920 forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
2021
2122-- | Run the given test, catching timeouts and exceptions
22−runTest :: String -> IO () -> IO ()
23+runTest :: String -> IO () -> IO Bool
2324runTest description test = do
2425 putStr $ "Running " ++ show description ++ ": "
2526 hFlush stdout
@@ -29,15 +30,23 @@ runTest description test = do
2930 Right Nothing -> failed $ "(timeout)"
3031 Right (Just ()) -> ok
3132 where
32− failed :: String -> IO ()
33+ failed :: String -> IO Bool
3334 failed err = do
3435 setSGR [SetColor Foreground Vivid Red]
3536 putStr "failed "
3637 setSGR [Reset]
3738 putStrLn err
39+ return False
3840
39− ok :: IO ()
41+ ok :: IO Bool
4042 ok = do
4143 setSGR [SetColor Foreground Vivid Green]
4244 putStrLn "ok"
4345 setSGR [Reset]
46+ return True
47+
48+-- | Run a bunch of tests and throw an exception if any fails
49+runTests :: [(String, IO ())] -> IO ()
50+runTests tests = do
51+ success <- foldr (liftM2 (&&) . uncurry runTest) (return True) $ tests
52+ unless success $ fail "Some tests failed"
network-transport-2/tests/TestTCP.hs+10−9
@@ -1,8 +1,8 @@
11module Main where
22
33import Prelude hiding (catch)
4−import TestTransport
5−import TestAuxiliary (forkTry, runTest)
4+import TestTransport (testTransport)
5+import TestAuxiliary (forkTry, runTests)
66import Network.Transport
77import Network.Transport.TCP (createTransport)
88import Data.Int (Int32)
@@ -184,11 +184,11 @@ testIgnoreCloseSocket = do
184184 tlog "Ignoring it, requesting another connection"
185185 let reqId' = 1 :: Int32
186186 sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId']
187− replicateM 4 $ recvInt32 sock :: IO [Int32]
187+ response' <- replicateM 4 $ recvInt32 sock :: IO [Int32]
188188
189189 -- Close it again
190190 tlog "Closing connection"
191− sendMany sock [encodeInt32 CloseConnection, encodeInt32 (response !! 3)]
191+ sendMany sock [encodeInt32 CloseConnection, encodeInt32 (response' !! 3)]
192192
193193 -- We now get a CloseSocket again, and this time we heed it
194194 tlog "Waiting for second CloseSocket request"
@@ -289,10 +289,11 @@ testBlockAfterCloseSocket = do
289289
290290main :: IO ()
291291main = do
292− runTest "EarlyDisconnect" testEarlyDisconnect
293− runTest "InvalidAddress" testInvalidAddress
294− runTest "InvalidConnect" testInvalidConnect
295− runTest "IgnoreCloseSocket" testIgnoreCloseSocket
296− runTest "BlockAfterCloseSocket" testBlockAfterCloseSocket
292+ runTests [ ("EarlyDisconnect", testEarlyDisconnect)
293+ , ("InvalidAddress", testInvalidAddress)
294+ , ("InvalidConnect", testInvalidConnect)
295+ , ("IgnoreCloseSocket", testIgnoreCloseSocket)
296+ , ("BlockAfterCloseSocket", testBlockAfterCloseSocket)
297+ ]
297298 Right transport <- createTransport "127.0.0.1" "8080"
298299 testTransport transport
network-transport-2/tests/TestTransport.hs+35−27
@@ -1,11 +1,9 @@
11module TestTransport where
22
33import Prelude hiding (catch)
4−import TestAuxiliary (forkTry, runTest)
4+import TestAuxiliary (forkTry, runTests)
55import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar, readMVar)
66import Control.Monad (replicateM, replicateM_, when, guard, forM_)
7−import Control.Monad.Reader (ReaderT, runReaderT, ask)
8−import Control.Monad.IO.Class (liftIO)
97import Control.Exception (evaluate, throw, catch, SomeException)
108import Control.Applicative ((<$>))
119import Network.Transport
@@ -16,7 +14,7 @@ import Data.ByteString.Char8 (pack)
1614import Data.Map (Map)
1715import qualified Data.Map as Map (empty, insert, delete, findWithDefault, adjust, null, toList, map)
1816
19−-- Server that echoes messages straight back to the origin endpoint.
17+-- | Server that echoes messages straight back to the origin endpoint.
2018echoServer :: EndPoint -> IO ()
2119echoServer endpoint = do
2220 go Map.empty
@@ -26,18 +24,23 @@ echoServer endpoint = do
2624 event <- receive endpoint
2725 case event of
2826 ConnectionOpened cid rel addr -> do
27+ tlog $ "Opened new conncetion " ++ show cid
2928 Right conn <- connect endpoint addr rel
3029 go (Map.insert cid conn cs)
3130 Received cid payload -> do
3231 send (Map.findWithDefault (error $ "Received: Invalid cid " ++ show cid) cid cs) payload
3332 go cs
3433 ConnectionClosed cid -> do
34+ tlog $ "Close connection " ++ show cid
3535 close (Map.findWithDefault (error $ "ConnectionClosed: Invalid cid " ++ show cid) cid cs)
3636 go (Map.delete cid cs)
3737 ReceivedMulticast _ _ ->
3838 -- Ignore
3939 go cs
40+ ErrorEvent _ _ ->
41+ fail (show event)
4042
43+-- | Wait for an event, throw an exception if the given predicate returns false
4144expect :: EndPoint -> (Event -> (Bool, a)) -> IO a
4245expect endpoint predicate = do
4346 event <- receive endpoint
@@ -49,10 +52,12 @@ expect endpoint predicate = do
4952 fail "Unexpected event"
5053 Right (True, a) -> return a
5154
55+-- | Like 'expect' but without a return value
5256expect' :: EndPoint -> (Event -> Bool) -> IO ()
5357expect' endpoint predicate =
5458 expect endpoint (\event -> (predicate event, ()))
5559
60+-- | Ping client used in a few tests
5661ping :: EndPoint -> EndPointAddress -> Int -> ByteString -> IO ()
5762ping endpoint server numPings msg = do
5863 -- Open connection to the server
@@ -80,7 +85,7 @@ ping endpoint server numPings msg = do
8085 -- Done
8186 tlog "Ping client done"
8287
83−-- Basic ping test
88+-- | Basic ping test
8489testPingPong :: Transport -> Int -> IO ()
8590testPingPong transport numPings = do
8691 tlog "Starting ping pong test"
@@ -96,7 +101,7 @@ testPingPong transport numPings = do
96101
97102 takeMVar result
98103
99−-- Test that endpoints don't get confused
104+-- | Test that endpoints don't get confused
100105testEndPoints :: Transport -> Int -> IO ()
101106testEndPoints transport numPings = do
102107 server <- spawn transport echoServer
@@ -150,7 +155,7 @@ testConnections transport numPings = do
150155
151156 takeMVar result
152157
153−-- Test that closing one connection does not close the other
158+-- | Test that closing one connection does not close the other
154159testCloseOneConnection :: Transport -> Int -> IO ()
155160testCloseOneConnection transport numPings = do
156161 server <- spawn transport echoServer
@@ -190,7 +195,7 @@ testCloseOneConnection transport numPings = do
190195
191196 takeMVar result
192197
193−-- Test that if A connects to B and B connects to A, B can still send to A after
198+-- | Test that if A connects to B and B connects to A, B can still send to A after
194199-- A closes its connection to B (for instance, in the TCP transport, the socket pair
195200-- connecting A and B should not yet be closed).
196201testCloseOneDirection :: Transport -> Int -> IO ()
@@ -274,6 +279,7 @@ testCloseOneDirection transport numPings = do
274279
275280 mapM_ takeMVar [doneA, doneB]
276281
282+-- | Collect a given number of events and order them by connection ID
277283collect :: EndPoint -> Int -> IO [(ConnectionId, [[ByteString]])]
278284collect endPoint numEvents = go numEvents Map.empty Map.empty
279285 where
@@ -292,8 +298,10 @@ collect endPoint numEvents = go numEvents Map.empty Map.empty
292298 go (n - 1) (Map.adjust (msg :) cid open) closed
293299 ReceivedMulticast _ _ ->
294300 fail "Unexpected multicast"
301+ ErrorEvent _ _ ->
302+ fail "Unexpected error"
295303
296−-- Open connection, close it, then reopen it
304+-- | Open connection, close it, then reopen it
297305-- (In the TCP transport this means the socket will be closed, then reopened)
298306--
299307-- Note that B cannot expect to receive all of A's messages on the first connection
@@ -349,7 +357,7 @@ testCloseReopen transport numPings = do
349357
350358 takeMVar doneB
351359
352−-- Test lots of parallel connection attempts
360+-- | Test lots of parallel connection attempts
353361testParallelConnects :: Transport -> Int -> IO ()
354362testParallelConnects transport numPings = do
355363 server <- spawn transport echoServer
@@ -373,7 +381,7 @@ testParallelConnects transport numPings = do
373381
374382 takeMVar done
375383
376−-- Test that sending on a closed connection gives an error
384+-- | Test that sending on a closed connection gives an error
377385testSendAfterClose :: Transport -> Int -> IO ()
378386testSendAfterClose transport _ = do
379387 server <- spawn transport echoServer
@@ -401,7 +409,7 @@ testSendAfterClose transport _ = do
401409
402410 takeMVar clientDone
403411
404−-- Test that closing the same connection twice has no effect
412+-- | Test that closing the same connection twice has no effect
405413testCloseTwice :: Transport -> Int -> IO ()
406414testCloseTwice transport _ = do
407415 server <- spawn transport echoServer
@@ -433,6 +441,7 @@ testCloseTwice transport _ = do
433441
434442 takeMVar clientDone
435443
444+-- | Test that we can connect an endpoint to itself
436445testConnectToSelf :: Transport -> Int -> IO ()
437446testConnectToSelf transport numPings = do
438447 done <- newEmptyMVar
@@ -471,21 +480,20 @@ testConnectToSelf transport numPings = do
471480
472481 takeMVar done
473482
474−runTransportTest :: String -> (Transport -> Int -> IO ()) -> ReaderT (Transport, Int) IO ()
475−runTransportTest description test = do
476− (transport, numPings) <- ask
477− liftIO $ runTest description (test transport numPings)
478483
479484-- Transport tests
480485testTransport :: Transport -> IO ()
481−testTransport transport = flip runReaderT (transport, 10000) $ do
482− runTransportTest "PingPong" testPingPong
483− runTransportTest "EndPoints" testEndPoints
484− runTransportTest "Connections" testConnections
485− runTransportTest "CloseOneConnection" testCloseOneConnection
486− runTransportTest "CloseOneDirection" testCloseOneDirection
487− runTransportTest "CloseReopen" testCloseReopen
488− runTransportTest "ParallelConnects" testParallelConnects
489− runTransportTest "SendAfterClose" testSendAfterClose
490− runTransportTest "CloseTwice" testCloseTwice
491− runTransportTest "ConnectToSelf" testConnectToSelf
486+testTransport transport = runTests
487+ [ ("PingPong", testPingPong transport numPings)
488+ , ("EndPoints", testEndPoints transport numPings)
489+ , ("Connections", testConnections transport numPings)
490+ , ("CloseOneConnection", testCloseOneConnection transport numPings)
491+ , ("CloseOneDirection", testCloseOneDirection transport numPings)
492+ , ("CloseReopen", testCloseReopen transport numPings)
493+ , ("ParallelConnects", testParallelConnects transport numPings)
494+ , ("SendAfterClose", testSendAfterClose transport numPings)
495+ , ("CloseTwice", testCloseTwice transport numPings)
496+ , ("ConnectToSelf", testConnectToSelf transport numPings)
497+ ]
498+ where
499+ numPings = 10000