git.iko.soy/distributed-process
on master help sign in
Error handling
2a0ef88Edsko de Vries
network-transport/src/Network/Transport/TCP.hs+21−10
@@ -74,7 +74,7 @@ import Control.Concurrent.MVar ( MVar
74import Control.Category ((>>>))
74import Control.Category ((>>>))
75import Control.Applicative ((<$>))
75import Control.Applicative ((<$>))
76import Control.Monad (forM_, when, unless)
76import Control.Monad (forM_, when, unless)
77−import Control.Exception (IOException, SomeException, handle, throw, try, bracketOnError)
77+import Control.Exception (IOException, SomeException, handle, throw, try, bracketOnError, mask_)
78import Data.IORef (IORef, newIORef, writeIORef, readIORef)
78import Data.IORef (IORef, newIORef, writeIORef, readIORef)
79import Data.ByteString (ByteString)
79import Data.ByteString (ByteString)
80import qualified Data.ByteString as BS (concat)
80import qualified Data.ByteString as BS (concat)
@@ -628,8 +628,9 @@ requestConnectionTo ourEndPoint theirAddress = go
628 RemoteEndPointClosed -> do
628 RemoteEndPointClosed -> do
629 -- EndPointClosed indicates that a concurrent thread was in the
629 -- EndPointClosed indicates that a concurrent thread was in the
630 -- process of closing the TCP connection to the remote endpoint when
630 -- process of closing the TCP connection to the remote endpoint when
631− -- we obtained a reference to it. The remote endpoint will now have
631+ -- we obtained a reference to it. By INV-CLOSE we can assume that the
632− -- been removed from ourState, so we simply try again.
632+ -- remote endpoint will now have been removed from ourState, so we
633+ -- simply try again.
633 go
634 go
634
635
635 RemoteEndPointValid _ -> do
636 RemoteEndPointValid _ -> do
@@ -641,7 +642,6 @@ requestConnectionTo ourEndPoint theirAddress = go
641 failureHandler err = do
642 failureHandler err = do
642 modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
643 modifyMVar_ theirState $ \(RemoteEndPointValid ep) ->
643 return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ ep)
644 return (RemoteEndPointValid . (remoteOutgoing ^: (\x -> x - 1)) $ ep)
644− -- TODO: should we call closeIfUnused here?
645 throw $ TransportError ConnectFailed (show err)
645 throw $ TransportError ConnectFailed (show err)
646
646
647 -- Do the actual connection request. This blocks until the remote
647 -- Do the actual connection request. This blocks until the remote
@@ -827,12 +827,23 @@ closeIfUnused theirEndPoint = modifyMVar_ (remoteState theirEndPoint) $ \st ->
827-- spawned). Returns whether or not a thread was spawned.
827-- spawned). Returns whether or not a thread was spawned.
828forkEndPointThread :: LocalEndPoint -> IO () -> IO Bool
828forkEndPointThread :: LocalEndPoint -> IO () -> IO Bool
829forkEndPointThread ourEndPoint p =
829forkEndPointThread ourEndPoint p =
830+ -- We use an explicit mask_ because we don't want to be interrupted until
831+ -- we have registered the thread. In particular, modifyMVar is not good
832+ -- enough because if we get an asynchronous exception after the fork but
833+ -- before the argument to modifyMVar returns we don't want to simply put
834+ -- the old value of the mvar back.
835+ mask_ $ do
836+ st <- takeMVar ourState
830− modifyMVar ourState $ \st -> case st of
837+ case st of
831− LocalEndPointValid vst -> do
838+ LocalEndPointValid vst -> do
839+ threadRegistered <- newEmptyMVar
832− tid <- forkIO (p >> removeThread)
840+ tid <- forkIO (takeMVar threadRegistered >> p >> removeThread)
833− return (LocalEndPointValid . (internalThreads ^: (tid :)) $ vst, True)
841+ putMVar ourState $ LocalEndPointValid . (internalThreads ^: (tid :)) $ vst
842+ putMVar threadRegistered ()
843+ return True
834− LocalEndPointClosed ->
844+ LocalEndPointClosed -> do
845+ putMVar ourState st
835− return (LocalEndPointClosed, False)
846+ return False
836 where
847 where
837 removeThread :: IO ()
848 removeThread :: IO ()
838 removeThread = do
849 removeThread = do
network-transport/tests/TestTCP.hs+38−2
@@ -507,6 +507,41 @@ testBreakTransport nextPort = do
507
507
508 return ()
508 return ()
509
509
510+-- | Test that a second call to 'connect' might succeed even if the first
511+-- failed. This is a TCP specific test rather than an endpoint specific test
512+-- because we must manually create the endpoint address to match an endpoint we
513+-- have yet to set up
514+testReconnect :: IO N.ServiceName -> IO ()
515+testReconnect nextPort = do
516+ clientDone <- newEmptyMVar
517+ firstAttempt <- newEmptyMVar
518+ endpointCreated <- newEmptyMVar
519+ port <- nextPort
520+ Right transport <- createTransport "127.0.0.1" port
521+
522+ -- Server
523+ forkTry $ do
524+ takeMVar firstAttempt
525+ newEndPoint transport
526+ putMVar endpointCreated ()
527+
528+ -- Client
529+ forkTry $ do
530+ Right endpoint <- newEndPoint transport
531+ let theirAddr = encodeEndPointAddress "127.0.0.1" port 1
532+
533+ Left (TransportError ConnectNotFound _) <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
534+ putMVar firstAttempt ()
535+
536+ takeMVar endpointCreated
537+ Right _ <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
538+
539+ putMVar clientDone ()
540+
541+ takeMVar clientDone
542+
543+
544+
510main :: IO ()
545main :: IO ()
511main = do
546main = do
512 portMVar <- newEmptyMVar
547 portMVar <- newEmptyMVar
@@ -520,7 +555,8 @@ main = do
520 , ("TestUnnecessaryConnect", testUnnecessaryConnect nextPort)
555 , ("TestUnnecessaryConnect", testUnnecessaryConnect nextPort)
521 , ("InvalidAddress", testInvalidAddress nextPort)
556 , ("InvalidAddress", testInvalidAddress nextPort)
522 , ("InvalidConnect", testInvalidConnect nextPort)
557 , ("InvalidConnect", testInvalidConnect nextPort)
523− , ("TestMany", testMany nextPort)
558+ , ("Many", testMany nextPort)
524− , ("TestBreakTransport", testBreakTransport nextPort)
559+ , ("BreakTransport", testBreakTransport nextPort)
560+ , ("Reconnect", testReconnect nextPort)
525 ]
561 ]
526 testTransport (either (Left . show) (Right) <$> nextPort >>= createTransport "127.0.0.1")
562 testTransport (either (Left . show) (Right) <$> nextPort >>= createTransport "127.0.0.1")