git.iko.soy/distributed-process
on master help sign in
Distinguish between closeTransport and failure

Also make forkServer in Internal/TCP.hs more robust

commit72d1f65c8d95fc8194aef6ab71e3b635fae4f772 authorEdsko de Vries <edsko@edsko.net> dateFri 18 May 2012, 13:53 (14 years ago) parentc6f5d78
3 files changed+86−39
network-transport/src/Network/Transport/Internal/TCP.hs+16−12 network-transport/src/Network/Transport/TCP.hs+52−24 network-transport/tests/TestTCP.hs+18−3
network-transport/src/Network/Transport/Internal/TCP.hs+16−12
@@ -25,9 +25,9 @@ import qualified Network.Socket as N ( HostName
2525 , sClose
2626 )
2727import qualified Network.Socket.ByteString as NBS (recv)
28−import Control.Concurrent (forkIO, ThreadId)
28+import Control.Concurrent (ThreadId, forkIOWithUnmask)
2929import Control.Monad (liftM, forever)
30−import Control.Exception (SomeException, handle, bracketOnError, throw)
30+import Control.Exception (SomeException, catch, bracketOnError, throw, mask_)
3131import Data.ByteString (ByteString)
3232import qualified Data.ByteString as BS (length, concat, null)
3333import Data.Int (Int32)
@@ -60,16 +60,20 @@ forkServer host port backlog terminationHandler requestHandler = do
6060 -- return an empty list (but will throw an exception instead) and will return
6161 -- the "best" address first, whatever that means
6262 addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
63− sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
64− -- putStrLn $ "Created server socket " ++ show sock ++ " for address " ++ host ++ ":" ++ port
65− N.setSocketOption sock N.ReuseAddr 1
66− N.bindSocket sock (N.addrAddress addr)
67− N.listen sock backlog
68− -- TODO: when is this socket closed?
69− forkIO . handle terminationHandler . forever $
70− bracketOnError (N.accept sock)
71− (N.sClose . fst)
72− (requestHandler . fst)
63+ bracketOnError (N.socket (N.addrFamily addr) N.Stream N.defaultProtocol)
64+ N.sClose $ \sock -> do
65+ N.setSocketOption sock N.ReuseAddr 1
66+ N.bindSocket sock (N.addrAddress addr)
67+ N.listen sock backlog
68+ mask_ $ forkIOWithUnmask $ \unmask ->
69+ catch (unmask (forever $ acceptRequest sock)) $ \ex -> do
70+ N.sClose sock
71+ terminationHandler ex
72+ where
73+ acceptRequest :: N.Socket -> IO ()
74+ acceptRequest sock = bracketOnError (N.accept sock)
75+ (N.sClose . fst)
76+ (requestHandler . fst)
7377
7478-- | Read a length and then a payload of that length
7579recvWithLength :: N.Socket -> IO [ByteString]
network-transport/src/Network/Transport/TCP.hs+52−24
@@ -15,8 +15,9 @@
1515-- * Output exception on channel after endpoint is closed
1616module Network.Transport.TCP ( -- * Main API
1717 createTransport
18− , -- * TCP specific functionality (exported mostly for testing purposes_
19− EndPointId
18+ -- * Internals (exposed for unit tests)
19+ , createTransportExposeInternals
20+ , EndPointId
2021 , encodeEndPointAddress
2122 , decodeEndPointAddress
2223 , ControlHeader(..)
@@ -319,6 +320,16 @@ data ConnectionRequestResponse =
319320-- TODOs: deal with hints
320321createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
321322createTransport host port = do
323+ result <- createTransportExposeInternals host port
324+ case result of
325+ Left err ->
326+ return $ Left err
327+ Right (transport, _) ->
328+ return $ Right transport
329+
330+-- | You should probably not use this function (used for unit testing only)
331+createTransportExposeInternals :: N.HostName -> N.ServiceName -> IO (Either IOException (Transport, ThreadId))
332+createTransportExposeInternals host port = do
322333 state <- newMVar . TransportValid $ ValidTransportState { _localEndPoints = Map.empty }
323334 let transport = TCPTransport { transportState = state
324335 , transportHost = host
@@ -329,27 +340,45 @@ createTransport host port = do
329340 -- http://tangentsoft.net/wskfaq/advanced.html#backlog
330341 -- http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html
331342 transportThread <- forkServer host port N.sOMAXCONN (terminationHandler transport) (handleConnectionRequest transport)
332− return Transport { newEndPoint = apiNewEndPoint transport
333− , closeTransport = killThread transportThread -- This will invoke the termination handler
334− }
343+ return ( Transport { newEndPoint = apiNewEndPoint transport
344+ , closeTransport = apiCloseTransport transport (Just transportThread) [EndPointClosed]
345+ }
346+ , transportThread
347+ )
335348 where
336349 terminationHandler :: TCPTransport -> SomeException -> IO ()
337− terminationHandler transport _ = do
338− -- TODO: we currently don't make a distinction between endpoint failure and manual closure
339− mTSt <- modifyMVar (transportState transport) $ \st -> case st of
340− TransportValid vst -> return (TransportClosed, Just vst)
341− TransportClosed -> return (TransportClosed, Nothing)
342− case mTSt of
343− Nothing ->
344− -- Transport already closed
345− return ()
346− Just tSt ->
347− mapM_ (apiCloseEndPoint transport) (Map.elems $ tSt ^. localEndPoints)
350+ terminationHandler transport ex = do
351+ let ev = ErrorEvent (TransportError EventTransportFailed (show ex))
352+ apiCloseTransport transport Nothing [ev]
348353
349354--------------------------------------------------------------------------------
350355-- API functions --
351356--------------------------------------------------------------------------------
352357
358+-- | Close the transport
359+apiCloseTransport :: TCPTransport -> Maybe ThreadId -> [Event] -> IO ()
360+apiCloseTransport transport mTransportThread evs = do
361+ mTSt <- modifyMVar (transportState transport) $ \st -> case st of
362+ TransportValid vst -> return (TransportClosed, Just vst)
363+ TransportClosed -> return (TransportClosed, Nothing)
364+ case mTSt of
365+ Nothing ->
366+ -- Transport already closed
367+ return ()
368+ Just tSt -> do
369+ mapM_ (apiCloseEndPoint transport evs) (Map.elems $ tSt ^. localEndPoints)
370+ case mTransportThread of
371+ Just transportThread ->
372+ -- This will invoke the termination handler, which in turn will call
373+ -- apiCloseTransport again, but at that point the transport is
374+ -- already in closed state so at that point we return immediately
375+ killThread transportThread
376+ Nothing ->
377+ -- This will happen is apiCloseTransport was invoked *because* the
378+ -- transport thread died (in which case we don't need to kill it)
379+ return ()
380+
381+
353382-- | Create a new endpoint
354383apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
355384apiNewEndPoint transport = try $ do
@@ -358,7 +387,7 @@ apiNewEndPoint transport = try $ do
358387 { receive = readChan (localChannel ourEndPoint)
359388 , address = localAddress ourEndPoint
360389 , connect = apiConnect ourEndPoint
361− , closeEndPoint = apiCloseEndPoint transport ourEndPoint
390+ , closeEndPoint = apiCloseEndPoint transport [EndPointClosed] ourEndPoint
362391 , newMulticastGroup = return . Left $ newMulticastGroupError
363392 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
364393 }
@@ -442,8 +471,11 @@ apiSend theirEndPoint connId connAlive payload = do
442471 sendFailed = TransportError SendFailed . show
443472
444473-- | Force-close the endpoint
445−apiCloseEndPoint :: TCPTransport -> LocalEndPoint -> IO ()
446−apiCloseEndPoint transport ourEndPoint = do
474+apiCloseEndPoint :: TCPTransport -- ^ Transport
475+ -> [Event] -- ^ Events to output on the endpoint to indicate closure
476+ -> LocalEndPoint -- ^ Local endpoint
477+ -> IO ()
478+apiCloseEndPoint transport evs ourEndPoint = do
447479 -- Remove the reference from the transport state
448480 removeLocalEndPoint transport ourEndPoint
449481 -- Close the local endpoint
@@ -460,11 +492,7 @@ apiCloseEndPoint transport ourEndPoint = do
460492 -- Close all endpoints and kill all threads
461493 forM_ (Map.elems $ vst ^. localConnections) $ tryCloseRemoteSocket
462494 forM_ (vst ^. internalThreads) killThread
463− -- We send a single message to the endpoint that it is closed. Subsequent
464− -- calls will block. We could change this so that all subsequent calls to
465− -- receive return an error, but this would mean checking for some state on
466− -- every call to receive, which is an unnecessary overhead.
467− writeChan (localChannel ourEndPoint) EndPointClosed
495+ forM_ evs $ writeChan (localChannel ourEndPoint)
468496 where
469497 -- Close the remote socket and return the set of all incoming connections
470498 tryCloseRemoteSocket :: RemoteEndPoint -> IO ()
network-transport/tests/TestTCP.hs+18−3
@@ -6,9 +6,9 @@ import Prelude hiding (catch, (>>=), (>>), return, fail)
66import TestTransport (testTransport)
77import TestAuxiliary (forkTry, runTests)
88import Network.Transport
9−import Network.Transport.TCP (createTransport, encodeEndPointAddress)
9+import Network.Transport.TCP (createTransport, createTransportExposeInternals, encodeEndPointAddress)
1010import Data.Int (Int32)
11−import Control.Concurrent (threadDelay)
11+import Control.Concurrent (threadDelay, ThreadId, killThread)
1212import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar, isEmptyMVar)
1313import Control.Monad (replicateM, guard, forM_, replicateM_)
1414import Control.Applicative ((<$>))
@@ -41,7 +41,9 @@ instance Traceable N.Socket where
4141
4242instance Traceable N.AddrInfo where
4343 trace = traceShow
44−
44+
45+instance Traceable ThreadId where
46+ trace = const Nothing
4547
4648-- Test that the server gets a ConnectionClosed message when the client closes
4749-- the socket without sending an explicit control message to the server first
@@ -493,6 +495,18 @@ testMany nextPort = do
493495 Right _ <- connect endpoint (address masterEndPoint) ReliableOrdered defaultConnectHints
494496 return ()
495497
498+-- | Test what happens when the transport breaks completely
499+testBreakTransport :: IO N.ServiceName -> IO ()
500+testBreakTransport nextPort = do
501+ Right (transport, transportThread) <- nextPort >>= createTransportExposeInternals "127.0.0.1"
502+ Right endpoint <- newEndPoint transport
503+
504+ killThread transportThread -- Uh oh
505+
506+ ErrorEvent (TransportError EventTransportFailed _) <- receive endpoint
507+
508+ return ()
509+
496510main :: IO ()
497511main = do
498512 portMVar <- newEmptyMVar
@@ -507,5 +521,6 @@ main = do
507521 , ("InvalidAddress", testInvalidAddress nextPort)
508522 , ("InvalidConnect", testInvalidConnect nextPort)
509523 , ("TestMany", testMany nextPort)
524+ , ("TestBreakTransport", testBreakTransport nextPort)
510525 ]
511526 testTransport (either (Left . show) (Right) <$> nextPort >>= createTransport "127.0.0.1")