git.iko.soy/distributed-process
on master help sign in
Reorganize code a bit
commit3938dfc07e0d2090e1424e42c09df171b2cc415f authorEdsko de Vries <edsko@edsko.net> dateMon 23 Apr 2012, 15:33 (15 years ago) parentdfcf360
6 files changed+209−84
network-transport-2/src/Network/Transport.hs+6−3 network-transport-2/src/Network/Transport/Chan.hs+1−1 network-transport-2/src/Network/Transport/Internal.hs+12−3 network-transport-2/src/Network/Transport/Internal/TCP.hs+82 network-transport-2/src/Network/Transport/TCP.hs+37−75 network-transport-2/tests/TestTCP.hs+71−2
network-transport-2/src/Network/Transport.hs+6−3
@@ -15,7 +15,7 @@ module Network.Transport ( -- * Types
1515 , FailedWith(..)
1616 , NewEndPointErrorCode
1717 , ConnectErrorCode(..)
18− , NewMulticastGroupErrorCode
18+ , NewMulticastGroupErrorCode(..)
1919 , ResolveMulticastGroupErrorCode(..)
2020 ) where
2121
@@ -47,11 +47,14 @@ data ConnectErrorCode =
4747 ConnectInvalidAddress -- ^ Could not parse the address
4848
4949-- | Failure during the creation of a new multicast group
50−data NewMulticastGroupErrorCode
50+data NewMulticastGroupErrorCode =
51+ NewMulticastGroupUnsupported
5152
5253-- | Failure during the resolution of a multicast group
5354data ResolveMulticastGroupErrorCode =
54− MulticastGroupNotFound
55+ ResolveMulticastGroupNotFound
56+ | ResolveMulticastGroupUnsupported
57+
5558
5659data FailedWith error = FailedWith error String
5760
network-transport-2/src/Network/Transport/Chan.hs+1−1
@@ -117,5 +117,5 @@ chanResolveMulticastGroup :: MVar TransportState
117117chanResolveMulticastGroup state endpoint addr = do
118118 group <- (^. (multigroups >>> mapLens addr)) <$> readMVar state
119119 case group of
120− Nothing -> return . Left $ FailedWith MulticastGroupNotFound ("Group " ++ show addr ++ " not found")
120+ Nothing -> return . Left $ FailedWith ResolveMulticastGroupNotFound ("Group " ++ show addr ++ " not found")
121121 Just mvar -> return . Right $ chanMulticastGroup state endpoint addr mvar
network-transport-2/src/Network/Transport/Internal.hs+12−3
@@ -6,6 +6,7 @@ module Network.Transport.Internal ( -- * Encoders/decoders
66 , decodeInt16
77 -- * Miscellaneous abstractions
88 , maybeToErrorT
9+ , maybeTToErrorT
910 ) where
1011
1112import Data.Int (Int16, Int32)
@@ -17,8 +18,8 @@ import qualified Data.ByteString as BS (length)
1718import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
1819import Control.Monad (mzero)
1920import Control.Monad.IO.Class (liftIO)
20−import Control.Monad.Error (ErrorT, Error, throwError)
21−import Control.Monad.Trans.Maybe (MaybeT)
21+import Control.Monad.Error (ErrorT, Error, throwError, lift)
22+import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
2223
2324foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
2425foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
@@ -55,7 +56,15 @@ decodeInt16 bs = liftIO $ do
5556 w16 <- peekByteOff p 0
5657 return (fromIntegral (ntohs w16))
5758
58−-- | Convert maybe to an ErrorT value
59+-- | Convert 'Maybe' to an ErrorT value
5960maybeToErrorT :: (Monad m, Error a) => a -> Maybe b -> ErrorT a m b
6061maybeToErrorT err Nothing = throwError err
6162maybeToErrorT _ (Just x) = return x
63+
64+-- | Convert 'MaybeT' to an 'ErrorT'
65+maybeTToErrorT :: (Monad m, Error a) => a -> MaybeT m b -> ErrorT a m b
66+maybeTToErrorT err valueT = do
67+ mval <- lift $ runMaybeT valueT
68+ case mval of
69+ Nothing -> throwError err
70+ Just val -> return val
network-transport-2/src/Network/Transport/Internal/TCP.hsnew file+82
@@ -0,0 +1,82 @@
1+-- | Utility functions for TCP sockets
2+module Network.Transport.Internal.TCP ( forkServer
3+ , sendWithLength
4+ , recvWithLength
5+ , recvExact
6+ ) where
7+
8+import Network.Transport.Internal ( encodeInt32
9+ , decodeInt32
10+ )
11+import qualified Network.Socket as N ( HostName
12+ , ServiceName
13+ , Socket
14+ , SocketType(Stream)
15+ , SocketOption(ReuseAddr)
16+ , getAddrInfo
17+ , defaultHints
18+ , socket
19+ , bindSocket
20+ , listen
21+ , addrFamily
22+ , addrAddress
23+ , defaultProtocol
24+ , setSocketOption
25+ )
26+import qualified Network.Socket.ByteString as NBS (sendMany, recv)
27+import Control.Concurrent (forkIO, ThreadId)
28+import Control.Monad (mzero)
29+import Control.Monad.IO.Class (liftIO)
30+import Control.Monad.Trans.Maybe (MaybeT)
31+import Data.ByteString (ByteString)
32+import qualified Data.ByteString as BS (length, concat, null)
33+import Data.Int (Int32)
34+
35+-- | Start a server at the specified address
36+forkServer :: N.HostName -> N.ServiceName -> (N.Socket -> IO ()) -> IO ThreadId
37+forkServer host port server = do
38+ -- Resolve the specified address. By specification, getAddrInfo will never
39+ -- return an empty list (but will throw an exception instead) and will return
40+ -- the "best" address first, whatever that means
41+ addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
42+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
43+ N.setSocketOption sock N.ReuseAddr 1
44+ N.bindSocket sock (N.addrAddress addr)
45+ N.listen sock 5
46+ forkIO $ server sock
47+
48+-- | Send a bunch of bytestrings prepended with their length
49+sendWithLength :: N.Socket -- ^ Socket to send on
50+ -> Maybe ByteString -- ^ Optional header to send before the length
51+ -> [ByteString] -- ^ Payload
52+ -> IO ()
53+sendWithLength sock header payload = do
54+ lengthBs <- encodeInt32 (fromIntegral . sum . map BS.length $ payload)
55+ let msg = maybe id (:) header $ lengthBs : payload
56+ NBS.sendMany sock msg
57+
58+-- | Read a length and then a payload of that length
59+recvWithLength :: N.Socket -> MaybeT IO [ByteString]
60+recvWithLength sock = do
61+ msgLengthBs <- recvExact sock 4
62+ decodeInt32 (BS.concat msgLengthBs) >>= recvExact sock
63+
64+-- | Read an exact number of bytes from a socket
65+--
66+-- Returns 'Nothing' if the socket closes prematurely or the length is non-positive
67+recvExact :: N.Socket -- ^ Socket to read from
68+ -> Int32 -- ^ Number of bytes to read
69+ -> MaybeT IO [ByteString]
70+recvExact _ len | len <= 0 = mzero
71+recvExact sock len = do
72+ (socketClosed, input) <- liftIO $ go [] len
73+ if socketClosed then mzero else return input
74+ where
75+ -- Returns input read and whether the socket closed prematurely
76+ go :: [ByteString] -> Int32 -> IO (Bool, [ByteString])
77+ go acc 0 = return (False, reverse acc)
78+ go acc l = do
79+ bs <- NBS.recv sock (fromIntegral l `min` 4096)
80+ if BS.null bs
81+ then return (True, reverse acc)
82+ else go (bs : acc) (l - (fromIntegral $ BS.length bs))
network-transport-2/src/Network/Transport/TCP.hs+37−75
@@ -1,12 +1,21 @@
11-- | TCP transport
2−module Network.Transport.TCP where
2+module Network.Transport.TCP ( -- * Main API
3+ createTransport
4+ , -- TCP specific functionality
5+ EndPointIx
6+ , decodeEndPointAddress
7+ ) where
38
49import Network.Transport
10+import Network.Transport.Internal.TCP ( forkServer
11+ , recvExact
12+ , recvWithLength
13+ , sendWithLength
14+ )
515import Network.Transport.Internal ( encodeInt16
616 , decodeInt16
7− , encodeInt32
8− , decodeInt32
917 , maybeToErrorT
18+ , maybeTToErrorT
1019 )
1120import qualified Network.Socket as N ( HostName
1221 , ServiceName
@@ -14,10 +23,7 @@ import qualified Network.Socket as N ( HostName
1423 , SocketType(Stream)
1524 , SocketOption(ReuseAddr)
1625 , getAddrInfo
17− , defaultHints
1826 , socket
19− , bindSocket
20− , listen
2127 , addrFamily
2228 , addrAddress
2329 , defaultProtocol
@@ -26,22 +32,21 @@ import qualified Network.Socket as N ( HostName
2632 , sClose
2733 , accept
2834 )
29−import qualified Network.Socket.ByteString as NBS (sendMany, recv)
30−import Control.Concurrent (forkIO, ThreadId)
35+import qualified Network.Socket.ByteString as NBS (sendMany)
36+import Control.Concurrent (forkIO)
3137import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
3238import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
3339import Control.Category ((>>>))
3440import Control.Applicative ((<*>), (*>), (<$>), pure)
35−import Control.Monad (forever, mzero)
41+import Control.Monad (forever)
3642import Control.Monad.Error (ErrorT, liftIO, runErrorT)
3743import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
3844import Data.ByteString (ByteString)
39−import qualified Data.ByteString as BS (length, concat, null)
45+import qualified Data.ByteString as BS (concat)
4046import qualified Data.ByteString.Char8 as BSC (pack, unpack)
4147import Data.IntMap (IntMap)
4248import qualified Data.IntMap as IntMap (empty, size)
4349import Data.Lens.Lazy (Lens, lens, intMapLens, (^.), (^=), (^+=))
44−import Data.Int (Int32)
4550import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
4651
4752data TransportState = TransportState { _endPoints :: IntMap (Chan Event, MVar EndPointState) }
@@ -55,7 +60,7 @@ endPoints = lens _endPoints (\es st -> st { _endPoints = es })
5560nextConnectionIx :: Lens EndPointState ConnectionIx
5661nextConnectionIx = lens _nextConnectionIx (\cix st -> st { _nextConnectionIx = cix })
5762
58−endPointAt :: Int -> Lens TransportState (Maybe (Chan Event, MVar EndPointState))
63+endPointAt :: EndPointIx -> Lens TransportState (Maybe (Chan Event, MVar EndPointState))
5964endPointAt ix = endPoints >>> intMapLens ix
6065
6166-- | Create a TCP transport
@@ -74,19 +79,6 @@ createTransport host port = do
7479 forkServer host port (transportServer state)
7580 return Transport { newEndPoint = tcpNewEndPoint state host port }
7681
77−-- | Start a server at the specified address
78−forkServer :: N.HostName -> N.ServiceName -> (N.Socket -> IO ()) -> IO ThreadId
79−forkServer host port server = do
80− -- Resolve the specified address. By specification, getAddrInfo will never
81− -- return an empty list (but will throw an exception instead) and will return
82− -- the "best" address first, whatever that means
83− addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
84− sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
85− N.setSocketOption sock N.ReuseAddr 1
86− N.bindSocket sock (N.addrAddress addr)
87− N.listen sock 5
88− forkIO $ server sock
89−
9082-- | The transport server handles incoming connections for all endpoints
9183--
9284-- TODO: lots of error checking
@@ -129,7 +121,7 @@ endpointServer sock theirAddress chan state = forever $ do
129121 writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
130122
131123 -- Read a message and output it on the endPoint's channel
132− readMessage :: Int -> MaybeT IO ()
124+ readMessage :: ConnectionIx -> MaybeT IO ()
133125 readMessage connIx = do
134126 payload <- recvWithLength sock
135127 liftIO $ writeChan chan (Received connIx payload)
@@ -145,24 +137,29 @@ tcpNewEndPoint transportState host port = do
145137 return . Right $ EndPoint { receive = readChan chan
146138 , address = addr
147139 , connect = tcpConnect addr
148− , newMulticastGroup = undefined
149− , resolveMulticastGroup = undefined
140+ , newMulticastGroup = return . Left $ newMulticastGroupError
141+ , resolveMulticastGroup = \_ -> return . Left $ resolveMulticastGroupError
150142 }
143+ where
144+ newMulticastGroupError = FailedWith NewMulticastGroupUnsupported "TCP does not support multicast"
145+ resolveMulticastGroupError = FailedWith ResolveMulticastGroupUnsupported "TCP does not support multicast"
146+
151147
152148-- | Connnect to an endpoint
153149tcpConnect :: EndPointAddress -> EndPointAddress -> Reliability -> IO (Either (FailedWith ConnectErrorCode) Connection)
154150tcpConnect myAddress theirAddress _ = runErrorT $ do
155− sock <- socketForEndPoint myAddress theirAddress
156− connIx <- liftIO $ requestNewConnection sock
157− return $ Connection { send = sendWithLength sock (Just connIx)
158− , close = N.sClose sock
159− }
151+ sock <- socketForEndPoint myAddress theirAddress
152+ -- TODO: test this failure
153+ connIx <- maybeTToErrorT (FailedWith undefined undefined) $ requestNewConnection sock
154+ return $ Connection { send = sendWithLength sock (Just connIx)
155+ , close = N.sClose sock
156+ }
160157
161158-- | Request a new connection
162−requestNewConnection :: N.Socket -> IO ByteString
159+requestNewConnection :: N.Socket -> MaybeT IO ByteString
163160requestNewConnection sock = do
164− pure <$> encodeInt16 0 >>= NBS.sendMany sock
165− NBS.recv sock 2
161+ liftIO $ pure <$> encodeInt16 0 >>= NBS.sendMany sock
162+ BS.concat <$> recvExact sock 2
166163
167164-- | Find a socket to the specified endpoint
168165--
@@ -172,7 +169,7 @@ requestNewConnection sock = do
172169socketForEndPoint :: EndPointAddress -- ^ Our address
173170 -> EndPointAddress -- ^ Their address
174171 -> ErrorT (FailedWith ConnectErrorCode) IO N.Socket
175−socketForEndPoint (EndPointAddress myAddress) (EndPointAddress theirAddress) = do
172+socketForEndPoint (EndPointAddress myAddress) theirAddress = do
176173 (host, port, endPointIx) <- maybeToErrorT invalidAddress (decodeEndPointAddress theirAddress)
177174 liftIO $ do
178175 -- Connect to the destination transport
@@ -188,46 +185,11 @@ socketForEndPoint (EndPointAddress myAddress) (EndPointAddress theirAddress) = d
188185 where
189186 invalidAddress = FailedWith ConnectInvalidAddress "Invalid address"
190187
191−-- | Send a bunch of bytestrings prepended with their length
192−sendWithLength :: N.Socket -- ^ Socket to send on
193− -> Maybe ByteString -- ^ Optional header to send before the length
194− -> [ByteString] -- ^ Payload
195− -> IO ()
196−sendWithLength sock header payload = do
197− lengthBs <- encodeInt32 (fromIntegral . sum . map BS.length $ payload)
198− let msg = maybe id (:) header $ lengthBs : payload
199− NBS.sendMany sock msg
200−
201−-- | Read a length and then a payload of that length
202−-- TODO: error handling
203−recvWithLength :: N.Socket -> MaybeT IO [ByteString]
204−recvWithLength sock = do
205− msgLengthBs <- recvExact sock 4
206− decodeInt32 (BS.concat msgLengthBs) >>= recvExact sock
207−
208188-- | Decode end point address
209189-- TODO: This uses regular expression parsing, which is nice, but unnecessary
210−decodeEndPointAddress :: ByteString -> Maybe (N.HostName, N.ServiceName, Int)
211−decodeEndPointAddress bs = match endPointAddressRE (BSC.unpack bs)
190+decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointIx)
191+decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack bs)
212192 where
213− endPointAddressRE :: RE Char (N.HostName, N.ServiceName, Int)
193+ endPointAddressRE :: RE Char (N.HostName, N.ServiceName, EndPointIx)
214194 endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
215195
216−-- | Read an exact number of bytes from a socket
217−--
218−-- Returns 'Nothing' if the socket closes prematurely.
219−recvExact :: N.Socket -- ^ Socket to read from
220− -> Int32 -- ^ Number of bytes to read
221− -> MaybeT IO [ByteString]
222−recvExact sock len = do
223− (socketClosed, input) <- liftIO $ go [] len
224− if socketClosed then mzero else return input
225− where
226− -- Returns input read and whether the socket closed prematurely
227− go :: [ByteString] -> Int32 -> IO (Bool, [ByteString])
228− go acc 0 = return (False, reverse acc)
229− go acc l = do
230− bs <- NBS.recv sock (fromIntegral l `min` 4096)
231− if BS.null bs
232− then return (True, reverse acc)
233− else go (bs : acc) (l - (fromIntegral $ BS.length bs))
network-transport-2/tests/TestTCP.hs+71−2
@@ -1,10 +1,79 @@
11module Main where
22
3−import TestTransport
4−import Network.Transport.TCP
3+import Network.Transport
4+import Network.Transport.Internal (encodeInt16)
5+import Network.Transport.Internal.TCP (sendWithLength)
6+import TestTransport (testTransport)
7+import Network.Transport.TCP (createTransport, decodeEndPointAddress)
58import System.Exit (exitFailure, exitSuccess)
9+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar, readMVar)
10+import Control.Concurrent (forkIO)
11+import Control.Applicative ((<$>), pure)
12+import Data.Maybe (fromJust)
13+import qualified Network.Socket as N ( getAddrInfo
14+ , socket
15+ , connect
16+ , addrFamily
17+ , addrAddress
18+ , SocketType(Stream)
19+ , SocketOption(ReuseAddr)
20+ , defaultProtocol
21+ , setSocketOption
22+ , sClose
23+ )
24+import qualified Network.Socket.ByteString as NBS (sendMany)
25+
26+testEarlyDisconnect :: IO ()
27+testEarlyDisconnect = do
28+ serverAddr <- newEmptyMVar
29+ serverDone <- newEmptyMVar
30+ transport <- createTransport "127.0.0.1" "8080"
631
32+ -- Server
33+ forkIO $ do
34+ Right endpoint <- newEndPoint transport
35+ putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
36+
37+ -- Wait for an incoming connection
38+ ConnectionOpened _ _ _ <- receive endpoint
39+
40+ -- Wait for the client to disconnect
41+ ConnectionClosed _ <- receive endpoint
42+
43+ putMVar serverDone ()
44+
45+ -- Client
46+ --
47+ -- We manually connect to the server to request a new connection,
48+ -- but then close the socket before the server gets the chance to
49+ -- respond
50+ forkIO $ do
51+ Right endpoint <- newEndPoint transport
52+ let EndPointAddress myAddress = address endpoint
53+
54+ -- Connect to the server
55+ addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8080")
56+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
57+ N.setSocketOption sock N.ReuseAddr 1
58+ N.connect sock (N.addrAddress addr)
59+
60+ -- Send number of the destination endpoint and our own address
61+ (_, _, endPointIx) <- readMVar serverAddr
62+ endPointBS <- encodeInt16 (fromIntegral endPointIx)
63+ sendWithLength sock (Just endPointBS) [myAddress]
64+
65+ -- Request a new connection
66+ pure <$> encodeInt16 0 >>= NBS.sendMany sock
67+
68+ -- At this point the server will try to reply with the connection ID,
69+ -- but we close the connection instead
70+ N.sClose sock
71+
72+ -- Wait for the server to finish
73+ takeMVar serverDone
74+
775main :: IO ()
876main = do
977 success <- createTransport "127.0.0.1" "8080" >>= testTransport
1078 if success then exitSuccess else exitFailure
79+ -- testEarlyDisconnect