git.iko.soy/distributed-process
on master help sign in
Don't reuse endpoint IDs. More error handling.

In particular, catch exception when self-connecting to a closed endpoint.

commite9af4609865c42ed0cf7403f1e1135b484428537 authorEdsko de Vries <edsko@edsko.net> dateMon 21 May 2012, 12:02 (14 years ago) parent72d1f65
2 files changed+75−30
network-transport/src/Network/Transport/TCP.hs+29−17 network-transport/tests/TestTransport.hs+46−13
network-transport/src/Network/Transport/TCP.hs+29−17
@@ -85,7 +85,7 @@ import qualified Data.IntMap as IntMap (empty)
8585import Data.IntSet (IntSet)
8686import qualified Data.IntSet as IntSet (empty, insert, elems, singleton, null, delete)
8787import Data.Map (Map)
88−import qualified Data.Map as Map (empty, elems, size)
88+import qualified Data.Map as Map (empty, elems)
8989import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
9090import qualified Data.Accessor.Container as DAC (mapMaybe, intMapMaybe)
9191import System.IO (hPutStrLn, stderr)
@@ -209,6 +209,7 @@ data TransportState =
209209
210210data ValidTransportState = ValidTransportState
211211 { _localEndPoints :: Map EndPointAddress LocalEndPoint
212+ , _nextEndPointId :: EndPointId
212213 }
213214
214215data LocalEndPoint = LocalEndPoint
@@ -330,7 +331,10 @@ createTransport host port = do
330331-- | You should probably not use this function (used for unit testing only)
331332createTransportExposeInternals :: N.HostName -> N.ServiceName -> IO (Either IOException (Transport, ThreadId))
332333createTransportExposeInternals host port = do
333− state <- newMVar . TransportValid $ ValidTransportState { _localEndPoints = Map.empty }
334+ state <- newMVar . TransportValid $ ValidTransportState
335+ { _localEndPoints = Map.empty
336+ , _nextEndPointId = 0
337+ }
334338 let transport = TCPTransport { transportState = state
335339 , transportHost = host
336340 , transportPort = port
@@ -378,7 +382,6 @@ apiCloseTransport transport mTransportThread evs = do
378382 -- transport thread died (in which case we don't need to kill it)
379383 return ()
380384
381−
382385-- | Create a new endpoint
383386apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
384387apiNewEndPoint transport = try $ do
@@ -418,8 +421,6 @@ apiConnect ourEndPoint theirAddress _reliability _hints = try $ do
418421-- GUARANTEE: If the connection is alive on entry then the remote endpoint will
419422-- either be RemoteEndPointValid or RemoteEndPointClosing. Otherwise, the state
420423-- of the remote endpoint will not be changed.
421−--
422−-- TODO: We ignore errors during a close. Is that right?
423424apiClose :: RemoteEndPoint -> ConnectionId -> IORef Bool -> IO ()
424425apiClose theirEndPoint connId connAlive = void . tryIO $ do
425426 modifyMVar_ (remoteState theirEndPoint) $ \st -> case st of
@@ -428,6 +429,7 @@ apiClose theirEndPoint connId connAlive = void . tryIO $ do
428429 if alive
429430 then do
430431 writeIORef connAlive False
432+ -- Possible exception caught top-level
431433 sendOn vst [encodeInt32 CloseConnection, encodeInt32 connId]
432434 return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ vst)
433435 else
@@ -454,7 +456,7 @@ apiSend theirEndPoint connId connAlive payload = do
454456 then mapExceptionIO sendFailed $ sendOn vst (encodeInt32 connId : prependLength payload)
455457 else throw $ TransportError SendClosed "Connection closed"
456458 RemoteEndPointClosed -> do
457− return (Left $ TransportError SendClosed "Connection lost")
459+ return . Left $ TransportError SendClosed "Connection lost"
458460 RemoteEndPointClosing _ _ ->
459461 -- The only way for the endpoint to be in closing state, while a
460462 -- connection is still active, is for the application to call 'close'
@@ -463,7 +465,7 @@ apiSend theirEndPoint connId connAlive payload = do
463465 -- yet. Even if the remote endpoint comes back with "please don't
464466 -- close", this still means that our *outgoing* connection has been
465467 -- closed
466− return (Left $ TransportError SendClosed "Connection lost")
468+ return . Left $ TransportError SendClosed "Connection lost"
467469 RemoteEndPointInvalid _ ->
468470 error "apiSend RELY violation"
469471 where
@@ -520,12 +522,15 @@ connectToSelf :: LocalEndPoint -> IO (Either (TransportError ConnectErrorCode) C
520522connectToSelf ourEndPoint = do
521523 -- Here connAlive must an MVar because it is not protected by another lock
522524 connAlive <- newMVar True
523− -- TODO: catch exception
524− connId <- getNextConnectionId ourEndPoint
525− writeChan ourChan (ConnectionOpened connId ReliableOrdered (localAddress ourEndPoint))
526− return . Right $ Connection { send = selfSend connAlive connId
527− , close = selfClose connAlive connId
528− }
525+ mConnId <- tryIO (getNextConnectionId ourEndPoint)
526+ case mConnId of
527+ Left err ->
528+ return . Left $ TransportError ConnectNotFound (show err)
529+ Right connId -> do
530+ writeChan ourChan (ConnectionOpened connId ReliableOrdered (localAddress ourEndPoint))
531+ return . Right $ Connection { send = selfSend connAlive connId
532+ , close = selfClose connAlive connId
533+ }
529534 where
530535 ourChan :: Chan Event
531536 ourChan = localChannel ourEndPoint
@@ -565,13 +570,13 @@ createLocalEndPoint transport = do
565570 }
566571 modifyMVar (transportState transport) $ \st -> case st of
567572 TransportValid vst -> do
568− let ix = fromIntegral $ Map.size (vst ^. localEndPoints)
573+ let ix = vst ^. nextEndPointId
569574 let addr = encodeEndPointAddress (transportHost transport) (transportPort transport) ix
570575 let localEndPoint = LocalEndPoint { localAddress = addr
571576 , localChannel = chan
572577 , localState = state
573578 }
574− return (TransportValid . (localEndPointAt addr ^= Just localEndPoint) $ vst, localEndPoint)
579+ return (TransportValid . (localEndPointAt addr ^= Just localEndPoint) . (nextEndPointId ^= ix + 1) $ vst, localEndPoint)
575580 TransportClosed ->
576581 throw (TransportError NewEndPointFailed "Transport closed")
577582
@@ -644,8 +649,10 @@ requestConnectionTo ourEndPoint theirAddress = go
644649 -- point)
645650 reply <- handle failureHandler $ doRemoteRequest (ourEndPoint, theirEndPoint) RequestConnectionId
646651 case decodeInt32 . BS.concat $ reply of
647− Nothing -> failureHandler (userError "Invalid integer")
648− Just cid -> return (theirEndPoint, cid)
652+ Nothing ->
653+ failureHandler (userError "Invalid integer")
654+ Just cid -> do
655+ return (theirEndPoint, cid)
649656
650657 -- If this is a new endpoint, fork a thread to listen for incoming
651658 -- connections. We don't want to do this while we hold the lock, because
@@ -979,6 +986,8 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
979986 -- Create a new connection
980987 createNewConnection :: ControlRequestId -> IO ()
981988 createNewConnection reqId = do
989+ -- getNextConnectionId throws an exception if ourEndPoint is closed; but
990+ -- if this endpoint is closed, this thread will soon die anyway
982991 newId <- getNextConnectionId ourEndPoint
983992 modifyMVar_ theirState $ \st -> do
984993 vst <- case st of
@@ -1135,6 +1144,9 @@ firstNonReservedConnectionId = 1024
11351144localEndPoints :: Accessor ValidTransportState (Map EndPointAddress LocalEndPoint)
11361145localEndPoints = accessor _localEndPoints (\es st -> st { _localEndPoints = es })
11371146
1147+nextEndPointId :: Accessor ValidTransportState EndPointId
1148+nextEndPointId = accessor _nextEndPointId (\eid st -> st { _nextEndPointId = eid })
1149+
11381150pendingCtrlRequests :: Accessor ValidLocalEndPointState (IntMap (MVar [ByteString]))
11391151pendingCtrlRequests = accessor _pendingCtrlRequests (\rep st -> st { _pendingCtrlRequests = rep })
11401152
network-transport/tests/TestTransport.hs+46−13
@@ -16,6 +16,7 @@ import Data.Map (Map)
1616import qualified Data.Map as Map (empty, insert, delete, findWithDefault, adjust, null, toList, map)
1717import Data.String (fromString)
1818import Traced
19+import Control.Concurrent (threadDelay)
1920
2021-- | We overload connect to always pass the default hints
2122connect :: EndPoint -> EndPointAddress -> Reliability -> IO (Either (TransportError ConnectErrorCode) Connection)
@@ -702,25 +703,57 @@ testCloseTransport newTransport = do
702703 putMVar clientDone ()
703704
704705 mapM_ takeMVar [serverDone, clientDone]
706+
707+-- | Remote node attempts to connect to a closed local endpoint
708+testConnectClosedEndPoint :: Transport -> IO ()
709+testConnectClosedEndPoint transport = do
710+ serverAddr <- newEmptyMVar
711+ serverClosed <- newEmptyMVar
712+ clientDone <- newEmptyMVar
705713
714+ -- Server
715+ forkTry $ do
716+ Right endpoint <- newEndPoint transport
717+ putMVar serverAddr (address endpoint)
718+
719+ closeEndPoint endpoint
720+ putMVar serverClosed ()
721+
722+ -- Client
723+ forkTry $ do
724+ Right endpoint <- newEndPoint transport
725+ readMVar serverClosed
726+
727+ -- Connect to a remote closed endpoint
728+ Left (TransportError ConnectNotFound _) <- readMVar serverAddr >>= \addr -> connect endpoint addr ReliableOrdered
729+
730+ -- Self-connect to a closed endpoint
731+ closeEndPoint endpoint
732+ Left (TransportError ConnectNotFound _) <- connect endpoint (address endpoint) ReliableOrdered
733+
734+ putMVar clientDone ()
735+
736+ takeMVar clientDone
737+
706738-- Transport tests
707739testTransport :: IO (Either String Transport) -> IO ()
708740testTransport newTransport = do
709741 Right transport <- newTransport
710742 runTests
711− [ ("PingPong", testPingPong transport numPings)
712− , ("EndPoints", testEndPoints transport numPings)
713− , ("Connections", testConnections transport numPings)
714− , ("CloseOneConnection", testCloseOneConnection transport numPings)
715− , ("CloseOneDirection", testCloseOneDirection transport numPings)
716− , ("CloseReopen", testCloseReopen transport numPings)
717− , ("ParallelConnects", testParallelConnects transport numPings)
718− , ("SendAfterClose", testSendAfterClose transport 100)
719− , ("CloseTwice", testCloseTwice transport 100)
720− , ("ConnectToSelf", testConnectToSelf transport numPings)
721− , ("ConnectToSelfTwice", testConnectToSelfTwice transport numPings)
722− , ("CloseEndPoint", testCloseEndPoint transport numPings)
723− , ("CloseTransport", testCloseTransport newTransport)
743+ [ ("PingPong", testPingPong transport numPings)
744+ , ("EndPoints", testEndPoints transport numPings)
745+ , ("Connections", testConnections transport numPings)
746+ , ("CloseOneConnection", testCloseOneConnection transport numPings)
747+ , ("CloseOneDirection", testCloseOneDirection transport numPings)
748+ , ("CloseReopen", testCloseReopen transport numPings)
749+ , ("ParallelConnects", testParallelConnects transport numPings)
750+ , ("SendAfterClose", testSendAfterClose transport 100)
751+ , ("CloseTwice", testCloseTwice transport 100)
752+ , ("ConnectToSelf", testConnectToSelf transport numPings)
753+ , ("ConnectToSelfTwice", testConnectToSelfTwice transport numPings)
754+ , ("CloseEndPoint", testCloseEndPoint transport numPings)
755+ , ("CloseTransport", testCloseTransport newTransport)
756+ , ("ConnectClosedEndPoint", testConnectClosedEndPoint transport)
724757 ]
725758 where
726759 numPings = 10000 :: Int