git.iko.soy/distributed-process
on master help sign in
Send ConnectionClosed when connection is broken
commitbf6c7a37bfa08b62e5d6c0a73d87a4e18776a733 authorEdsko de Vries <edsko@edsko.net> dateTue 24 Apr 2012, 12:22 (15 years ago) parent39d3f1e
6 files changed+72−55
network-transport-2/src/Network/Transport/Chan.hs+5−5 network-transport-2/src/Network/Transport/Internal.hs+8−8 network-transport-2/src/Network/Transport/Internal/TCP.hs+17−15 network-transport-2/src/Network/Transport/TCP.hs+36−22 network-transport-2/tests/TestTCP.hs+5−4 network-transport-2/tests/TestTransport.hs+1−1
network-transport-2/src/Network/Transport/Chan.hs+5−5
@@ -45,10 +45,10 @@ multigroupAt addr = multigroups >>> at addr
4545-- (threads can, and should, create their own endpoints though).
4646createTransport :: IO Transport
4747createTransport = do
48− state <- newMVar $ State { _channels = Map.empty
49− , _connections = Map.empty
50− , _multigroups = Map.empty
51− }
48+ state <- newMVar State { _channels = Map.empty
49+ , _connections = Map.empty
50+ , _multigroups = Map.empty
51+ }
5252 return Transport { newEndPoint = chanNewEndPoint state }
5353
5454-- Create a new end point
@@ -106,7 +106,7 @@ chanMulticastGroup state endpoint addr group =
106106 cs <- readMVar group
107107 forM_ cs $ \ch -> writeChan ch (ReceivedMulticast addr payload)
108108 , multicastSubscribe = modifyMVar_ group $ return . (endpoint :)
109− , multicastUnsubscribe = modifyMVar_ group $ return . (filter (/= endpoint))
109+ , multicastUnsubscribe = modifyMVar_ group $ return . filter (/= endpoint)
110110 , multicastClose = return ()
111111 }
112112
network-transport-2/src/Network/Transport/Internal.hs+8−8
@@ -16,8 +16,8 @@ import Foreign.ForeignPtr (withForeignPtr)
1616import Data.ByteString (ByteString)
1717import qualified Data.ByteString as BS (length)
1818import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
19−import Control.Monad (mzero)
20−import Control.Monad.IO.Class (liftIO)
19+import Control.Monad (mzero, MonadPlus)
20+import Control.Monad.IO.Class (MonadIO, liftIO)
2121import Control.Monad.Error (ErrorT, Error, throwError, lift)
2222import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
2323
@@ -27,13 +27,13 @@ foreign import ccall unsafe "htons" htons :: CShort -> CShort
2727foreign import ccall unsafe "ntohs" ntohs :: CShort -> CShort
2828
2929-- | Serialize 32-bit to network byte order
30−encodeInt32 :: Int32 -> IO ByteString
31−encodeInt32 i32 =
30+encodeInt32 :: (MonadIO m) => Int32 -> m ByteString
31+encodeInt32 i32 = liftIO $
3232 BSI.create 4 $ \p ->
3333 pokeByteOff p 0 (htonl (fromIntegral i32))
3434
3535-- | Deserialize 32-bit from network byte order
36−decodeInt32 :: ByteString -> MaybeT IO Int32
36+decodeInt32 :: (MonadIO m, MonadPlus m) => ByteString -> m Int32
3737decodeInt32 bs | BS.length bs /= 4 = mzero
3838decodeInt32 bs = liftIO $ do
3939 let (fp, _, _) = BSI.toForeignPtr bs
@@ -42,13 +42,13 @@ decodeInt32 bs = liftIO $ do
4242 return (fromIntegral (ntohl w32))
4343
4444-- | Serialize 16-bit to network byte order
45−encodeInt16 :: Int16 -> IO ByteString
46−encodeInt16 i16 =
45+encodeInt16 :: (MonadIO m) => Int16 -> m ByteString
46+encodeInt16 i16 = liftIO $
4747 BSI.create 2 $ \p ->
4848 pokeByteOff p 0 (htons (fromIntegral i16))
4949
5050-- | Deserialize 16-bit from network byte order
51−decodeInt16 :: ByteString -> MaybeT IO Int16
51+decodeInt16 :: (MonadIO m, MonadPlus m) => ByteString -> m Int16
5252decodeInt16 bs | BS.length bs /= 2 = mzero
5353decodeInt16 bs = liftIO $ do
5454 let (fp, _, _) = BSI.toForeignPtr bs
network-transport-2/src/Network/Transport/Internal/TCP.hs+17−15
@@ -29,11 +29,9 @@ import qualified Network.Socket as N ( HostName
2929 )
3030import qualified Network.Socket.ByteString as NBS (sendMany, recv)
3131import Control.Concurrent (forkIO, ThreadId)
32−import Control.Monad (mzero)
33−import Control.Monad.IO.Class (liftIO)
34−import Control.Monad.Trans.Maybe (MaybeT(MaybeT))
32+import Control.Monad (mzero, MonadPlus, unless)
33+import Control.Monad.IO.Class (MonadIO, liftIO)
3534import Control.Exception (catch, IOException)
36−import Control.Applicative ((<$>), pure)
3735import Data.ByteString (ByteString)
3836import qualified Data.ByteString as BS (length, concat, null)
3937import Data.Int (Int32)
@@ -56,7 +54,7 @@ forkServer host port server = do
5654-- | Connect to another host
5755--
5856-- TODO: deal with errors
59−connectTo :: N.HostName -> N.ServiceName -> MaybeT IO N.Socket
57+connectTo :: (MonadIO m) => N.HostName -> N.ServiceName -> m N.Socket
6058connectTo host port = liftIO $ do
6159 addr:_ <- N.getAddrInfo Nothing (Just host) (Just port)
6260 sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
@@ -65,17 +63,18 @@ connectTo host port = liftIO $ do
6563 return sock
6664
6765-- | Send a bunch of bytestrings prepended with their length
68−sendWithLength :: N.Socket -- ^ Socket to send on
66+sendWithLength :: (MonadIO m, MonadPlus m)
67+ => N.Socket -- ^ Socket to send on
6968 -> Maybe ByteString -- ^ Optional header to send before the length
7069 -> [ByteString] -- ^ Payload
71− -> MaybeT IO ()
70+ -> m ()
7271sendWithLength sock header payload = do
7372 lengthBs <- liftIO $ encodeInt32 (fromIntegral . sum . map BS.length $ payload)
7473 let msg = maybe id (:) header $ lengthBs : payload
7574 sendMany sock msg
7675
7776-- | Read a length and then a payload of that length
78−recvWithLength :: N.Socket -> MaybeT IO [ByteString]
77+recvWithLength :: (MonadIO m, MonadPlus m) => N.Socket -> m [ByteString]
7978recvWithLength sock = do
8079 msgLengthBs <- recvExact sock 4
8180 decodeInt32 (BS.concat msgLengthBs) >>= recvExact sock
@@ -83,18 +82,21 @@ recvWithLength sock = do
8382-- | Wrapper around 'Network.Socket.ByteString.sendMany'
8483--
8584-- Returns Nothing when an I/O exception is raised during the send
86−sendMany :: N.Socket -> [ByteString] -> MaybeT IO ()
87−sendMany sock msg = MaybeT $ catch (pure <$> NBS.sendMany sock msg) (handleIOException)
85+sendMany :: (MonadIO m, MonadPlus m) => N.Socket -> [ByteString] -> m ()
86+sendMany sock msg = do
87+ success <- liftIO $ catch (NBS.sendMany sock msg >> return True) handleIOException
88+ unless success mzero
8889 where
89− handleIOException :: IOException -> IO (Maybe ())
90− handleIOException _ = return mzero
90+ handleIOException :: IOException -> IO Bool
91+ handleIOException _ = return False
9192
9293-- | Read an exact number of bytes from a socket
9394--
9495-- Returns 'Nothing' if the socket closes prematurely or the length is non-positive
95−recvExact :: N.Socket -- ^ Socket to read from
96+recvExact :: (MonadIO m, MonadPlus m)
97+ => N.Socket -- ^ Socket to read from
9698 -> Int32 -- ^ Number of bytes to read
97− -> MaybeT IO [ByteString]
99+ -> m [ByteString]
98100recvExact _ len | len <= 0 = mzero
99101recvExact sock len = do
100102 (socketClosed, input) <- liftIO $ go [] len
@@ -107,4 +109,4 @@ recvExact sock len = do
107109 bs <- NBS.recv sock (fromIntegral l `min` 4096)
108110 if BS.null bs
109111 then return (True, reverse acc)
110− else go (bs : acc) (l - (fromIntegral $ BS.length bs))
112+ else go (bs : acc) (l - fromIntegral (BS.length bs))
network-transport-2/src/Network/Transport/TCP.hs+36−22
@@ -35,9 +35,11 @@ import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
3535import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
3636import Control.Category ((>>>))
3737import Control.Applicative ((<*>), (*>), (<$>), pure)
38−import Control.Monad (forever)
38+import Control.Monad (forever, forM_)
3939import Control.Monad.Error (ErrorT, liftIO, runErrorT)
4040import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
41+import Control.Monad.Trans.Writer (WriterT, execWriterT)
42+import Control.Monad.Writer.Class (tell)
4143import Data.ByteString (ByteString)
4244import qualified Data.ByteString as BS (concat)
4345import qualified Data.ByteString.Char8 as BSC (pack, unpack)
@@ -72,7 +74,7 @@ endPointAt ix = endPoints >>> intMapLens ix
7274-- * Deal with all exceptions that may occur
7375createTransport :: N.HostName -> N.ServiceName -> IO Transport
7476createTransport host port = do
75− state <- newMVar $ TransportState { _endPoints = IntMap.empty }
77+ state <- newMVar TransportState { _endPoints = IntMap.empty }
7678 forkServer host port (transportServer state)
7779 return Transport { newEndPoint = tcpNewEndPoint state host port }
7880
@@ -89,38 +91,49 @@ transportServer state sock = forever $ do
8991-- Returns when the client closes the socket and on invalid input.
9092handleClient :: MVar TransportState -> N.Socket -> IO ()
9193handleClient state sock = do
92− runMaybeT $ do
94+ mendpoint <- runMaybeT $ do
9395 -- Our endpoint the other side is interested in
9496 endPointBS <- recvExact sock 2
95− endPointIx <- decodeInt16 (BS.concat $ endPointBS)
97+ endPointIx <- decodeInt16 (BS.concat endPointBS)
9698 -- Full endpoint address of the other side
9799 theirAddress <- BS.concat <$> recvWithLength sock
98− -- Start handling incoming messages
100+ -- Find our endpoint
99101 (endPointCh, endPointSt) <- MaybeT $ (^. endPointAt (fromIntegral endPointIx)) <$> readMVar state
100− endpointServer sock (EndPointAddress theirAddress) endPointCh endPointSt
101− return ()
102+ return (endPointCh, endPointSt, theirAddress)
103+ case mendpoint of
104+ Just (endPointCh, endPointSt, theirAddress) -> do
105+ openConnections <- endpointServer sock (EndPointAddress theirAddress) endPointCh endPointSt
106+ forM_ openConnections $ \cix -> writeChan endPointCh (ConnectionClosed cix)
107+ Nothing ->
108+ -- Invalid request
109+ return ()
102110
103−-- | Handle requests for one endpoint. Returns only if an error occurs
104−endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> MaybeT IO ()
105−endpointServer sock theirAddress chan state = forever $ do
111+-- | Handle requests for one endpoint. Returns only if an error occurs,
112+-- in which case it returns the connections that are still open (normally,
113+-- this should be an empty list).
114+endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> IO [ConnectionIx]
115+endpointServer sock theirAddress chan state = execWriterT . runMaybeT . forever $ do
106116 connBS <- recvExact sock 2
107117 connIx <- fromIntegral <$> decodeInt16 (BS.concat connBS)
108118 if connIx == 0
109119 then createNewConnection
110120 else readMessage connIx
111121 where
122+ -- A brief note on the types:
123+ -- These functions may fail (connection error): hence MaybeT
124+ -- We also need to keep track of which connections are still open: hence WriterT
125+
112126 -- Create a new connection
113− createNewConnection :: MaybeT IO ()
127+ createNewConnection :: MaybeT (WriterT [ConnectionIx] IO) ()
114128 createNewConnection = do
115− (newIx, newBs) <- liftIO $ do
116− newIx <- modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
117− newBs <- encodeInt16 (fromIntegral newIx)
118− return (newIx, newBs)
129+ newIx <- liftIO $ modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
130+ newBs <- encodeInt16 (fromIntegral newIx)
119131 sendMany sock [newBs]
120132 liftIO $ writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
133+ tell [newIx]
121134
122135 -- Read a message and output it on the endPoint's channel
123− readMessage :: ConnectionIx -> MaybeT IO ()
136+ readMessage :: ConnectionIx -> MaybeT (WriterT [ConnectionIx] IO) ()
124137 readMessage connIx = do
125138 payload <- recvWithLength sock
126139 liftIO $ writeChan chan (Received connIx payload)
@@ -129,7 +142,7 @@ endpointServer sock theirAddress chan state = forever $ do
129142tcpNewEndPoint :: MVar TransportState -> N.HostName -> N.ServiceName -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
130143tcpNewEndPoint transportState host port = do
131144 chan <- newChan
132− endPointState <- newMVar $ EndPointState { _nextConnectionIx = 1 } -- Connection id 0 is reserved for requesting new connections
145+ endPointState <- newMVar EndPointState { _nextConnectionIx = 1 } -- Connection id 0 is reserved for requesting new connections
133146 endPointIx <- modifyMVar transportState $ \st -> do let ix = IntMap.size (st ^. endPoints)
134147 return ((endPointAt ix ^= Just (chan, endPointState)) $ st, ix)
135148 let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show endPointIx
@@ -149,15 +162,16 @@ tcpConnect :: EndPointAddress -> EndPointAddress -> Reliability -> IO (Either (F
149162tcpConnect myAddress theirAddress _ = runErrorT $ do
150163 sock <- socketForEndPoint myAddress theirAddress
151164 connIx <- maybeTToErrorT undefined $ requestNewConnection sock
152− return $ Connection { send = \msg -> runErrorT $ maybeTToErrorT (FailedWith SendFailed "Send failed") $
153− sendWithLength sock (Just connIx) msg
154− , close = N.sClose sock
155− }
165+ return Connection { send = runErrorT .
166+ maybeTToErrorT (FailedWith SendFailed "Send failed") .
167+ sendWithLength sock (Just connIx)
168+ , close = N.sClose sock
169+ }
156170
157171-- | Request a new connection
158172requestNewConnection :: N.Socket -> MaybeT IO ByteString
159173requestNewConnection sock = do
160− (liftIO $ pure <$> encodeInt16 0) >>= sendMany sock
174+ liftIO (pure <$> encodeInt16 0) >>= sendMany sock
161175 BS.concat <$> recvExact sock 2
162176
163177-- | Find a socket to the specified endpoint
network-transport-2/tests/TestTCP.hs+5−4
@@ -27,7 +27,7 @@ testEarlyDisconnect :: IO ()
2727testEarlyDisconnect = do
2828 serverAddr <- newEmptyMVar
2929 serverDone <- newEmptyMVar
30− transport <- createTransport "127.0.0.1" "8080"
30+ transport <- createTransport "127.0.0.1" "8081"
3131
3232 -- Server
3333 forkIO $ do
@@ -58,7 +58,7 @@ testEarlyDisconnect = do
5858 let EndPointAddress myAddress = address endpoint
5959
6060 -- Connect to the server
61− addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8080")
61+ addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8081")
6262 sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
6363 N.setSocketOption sock N.ReuseAddr 1
6464 N.connect sock (N.addrAddress addr)
@@ -77,7 +77,8 @@ testEarlyDisconnect = do
7777 decodeInt16 connBs
7878 putStrLn $ "Client got connection ID " ++ show connIx
7979
80− -- Close the socket
80+ -- Close the socket without closing the connection explicitly
81+ -- The server should still receive a ConnectionClosed message
8182 N.sClose sock
8283
8384 -- Wait for the server to finish
@@ -85,6 +86,6 @@ testEarlyDisconnect = do
8586
8687main :: IO ()
8788main = do
89+ testEarlyDisconnect
8890 success <- createTransport "127.0.0.1" "8080" >>= testTransport
8991 if success then exitSuccess else exitFailure
90− -- testEarlyDisconnect
network-transport-2/tests/TestTransport.hs+1−1
@@ -64,7 +64,7 @@ ping endpoint server numPings msg = do
6464 case event of
6565 Received _ [reply] | reply == msg ->
6666 return ()
67− _ -> do
67+ _ ->
6868 error $ "Unexpected event " ++ show event
6969
7070 -- Close the connection