git.iko.soy/distributed-process
on master help sign in
More error handling
c067c47Edsko de Vries
network-transport-2/network-transport.cabal+2−2
@@ -18,7 +18,7 @@ Library
18 transformers
18 transformers
19 Exposed-modules: Network.Transport,
19 Exposed-modules: Network.Transport,
20 Network.Transport.Chan
20 Network.Transport.Chan
21− extensions: EmptyDataDecls, ForeignFunctionInterface, CPP
21+ extensions: EmptyDataDecls, ForeignFunctionInterface, FlexibleContexts
22 ghc-options: -Wall -fno-warn-unused-do-bind
22 ghc-options: -Wall -fno-warn-unused-do-bind
23 HS-Source-Dirs: src
23 HS-Source-Dirs: src
24
24
@@ -33,7 +33,7 @@ Test-Suite TestTCP
33 regex-applicative,
33 regex-applicative,
34 mtl,
34 mtl,
35 transformers
35 transformers
36− extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, CPP
36+ extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts
37 ghc-options: -Wall -fno-warn-unused-do-bind
37 ghc-options: -Wall -fno-warn-unused-do-bind
38 HS-Source-Dirs: tests src
38 HS-Source-Dirs: tests src
39
39
network-transport-2/src/Network/Transport.hs+8−1
@@ -46,22 +46,29 @@ data NewEndPointErrorCode
46
46
47-- | Connection failure
47-- | Connection failure
48data ConnectErrorCode =
48data ConnectErrorCode =
49− ConnectInvalidAddress -- ^ Could not parse the address
49+ ConnectInvalidAddress -- ^ Could not parse or resolve the address
50+ | ConnectInsufficientResources -- ^ Insufficient resources (for instance, no more socketss available)
51+ | ConnectFailed -- ^ Failed for other reasons
52+ deriving Show
50
53
51-- | Failure during the creation of a new multicast group
54-- | Failure during the creation of a new multicast group
52data NewMulticastGroupErrorCode =
55data NewMulticastGroupErrorCode =
53 NewMulticastGroupUnsupported
56 NewMulticastGroupUnsupported
57+ deriving Show
54
58
55-- | Failure during the resolution of a multicast group
59-- | Failure during the resolution of a multicast group
56data ResolveMulticastGroupErrorCode =
60data ResolveMulticastGroupErrorCode =
57 ResolveMulticastGroupNotFound
61 ResolveMulticastGroupNotFound
58 | ResolveMulticastGroupUnsupported
62 | ResolveMulticastGroupUnsupported
63+ deriving Show
59
64
60-- | Failure during sending a message
65-- | Failure during sending a message
61data SendErrorCode =
66data SendErrorCode =
62 SendFailed
67 SendFailed
68+ deriving Show
63
69
64data FailedWith error = FailedWith error String
70data FailedWith error = FailedWith error String
71+ deriving Show
65
72
66instance Error (FailedWith error) where
73instance Error (FailedWith error) where
67 strMsg = FailedWith undefined
74 strMsg = FailedWith undefined
network-transport-2/src/Network/Transport/Internal.hs+40−31
@@ -4,22 +4,23 @@ module Network.Transport.Internal ( -- * Encoders/decoders
4 , decodeInt32
4 , decodeInt32
5 , encodeInt16
5 , encodeInt16
6 , decodeInt16
6 , decodeInt16
7+ , prependLength
7 -- * Miscellaneous abstractions
8 -- * Miscellaneous abstractions
8 , failWith
9 , failWith
9− , failWithT
10+ , failWithIO
10 ) where
11 ) where
11
12
12−import Data.Int (Int16, Int32)
13+import Prelude hiding (catch)
13import Foreign.Storable (pokeByteOff, peekByteOff)
14import Foreign.Storable (pokeByteOff, peekByteOff)
14import Foreign.C (CInt(..), CShort(..))
15import Foreign.C (CInt(..), CShort(..))
15import Foreign.ForeignPtr (withForeignPtr)
16import Foreign.ForeignPtr (withForeignPtr)
16import Data.ByteString (ByteString)
17import Data.ByteString (ByteString)
17import qualified Data.ByteString as BS (length)
18import qualified Data.ByteString as BS (length)
18−import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
19+import qualified Data.ByteString.Internal as BSI (unsafeCreate, toForeignPtr, inlinePerformIO)
19−import Control.Monad (mzero, MonadPlus)
20+import Control.Applicative ((<$>))
20import Control.Monad.IO.Class (MonadIO, liftIO)
21import Control.Monad.IO.Class (MonadIO, liftIO)
21−import Control.Monad.Error (ErrorT, Error, throwError, lift)
22+import Control.Monad.Error (MonadError, throwError)
22−import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
23+import Control.Exception (IOException, catch)
23
24
24foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
25foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
25foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
26foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
@@ -27,44 +28,52 @@ foreign import ccall unsafe "htons" htons :: CShort -> CShort
27foreign import ccall unsafe "ntohs" ntohs :: CShort -> CShort
28foreign import ccall unsafe "ntohs" ntohs :: CShort -> CShort
28
29
29-- | Serialize 32-bit to network byte order
30-- | Serialize 32-bit to network byte order
30−encodeInt32 :: (MonadIO m) => Int32 -> m ByteString
31+encodeInt32 :: Enum a => a -> ByteString
31−encodeInt32 i32 = liftIO $
32+encodeInt32 i32 =
32− BSI.create 4 $ \p ->
33+ BSI.unsafeCreate 4 $ \p ->
33− pokeByteOff p 0 (htonl (fromIntegral i32))
34+ pokeByteOff p 0 (htonl . fromIntegral . fromEnum $ i32)
34
35
35-- | Deserialize 32-bit from network byte order
36-- | Deserialize 32-bit from network byte order
36−decodeInt32 :: (MonadIO m, MonadPlus m) => ByteString -> m Int32
37+decodeInt32 :: Enum a => ByteString -> Maybe a
37−decodeInt32 bs | BS.length bs /= 4 = mzero
38+decodeInt32 bs | BS.length bs /= 4 = Nothing
38−decodeInt32 bs = liftIO $ do
39+decodeInt32 bs = Just . BSI.inlinePerformIO $ do
39 let (fp, _, _) = BSI.toForeignPtr bs
40 let (fp, _, _) = BSI.toForeignPtr bs
40 withForeignPtr fp $ \p -> do
41 withForeignPtr fp $ \p -> do
41 w32 <- peekByteOff p 0
42 w32 <- peekByteOff p 0
42− return (fromIntegral (ntohl w32))
43+ return (toEnum . fromIntegral . ntohl $ w32)
43
44
44-- | Serialize 16-bit to network byte order
45-- | Serialize 16-bit to network byte order
45−encodeInt16 :: (MonadIO m) => Int16 -> m ByteString
46+encodeInt16 :: Enum a => a -> ByteString
46−encodeInt16 i16 = liftIO $
47+encodeInt16 i16 =
47− BSI.create 2 $ \p ->
48+ BSI.unsafeCreate 2 $ \p ->
48− pokeByteOff p 0 (htons (fromIntegral i16))
49+ pokeByteOff p 0 (htons .fromIntegral . fromEnum $ i16)
49
50
50-- | Deserialize 16-bit from network byte order
51-- | Deserialize 16-bit from network byte order
51−decodeInt16 :: (MonadIO m, MonadPlus m) => ByteString -> m Int16
52+decodeInt16 :: Enum a => ByteString -> Maybe a
52−decodeInt16 bs | BS.length bs /= 2 = mzero
53+decodeInt16 bs | BS.length bs /= 2 = Nothing
53−decodeInt16 bs = liftIO $ do
54+decodeInt16 bs = Just . BSI.inlinePerformIO $ do
54 let (fp, _, _) = BSI.toForeignPtr bs
55 let (fp, _, _) = BSI.toForeignPtr bs
55 withForeignPtr fp $ \p -> do
56 withForeignPtr fp $ \p -> do
56 w16 <- peekByteOff p 0
57 w16 <- peekByteOff p 0
57− return (fromIntegral (ntohs w16))
58+ return (toEnum . fromIntegral . ntohs $ w16)
58
59
59−-- | Convert 'Maybe' to an ErrorT value
60+-- | Prepend a list of bytestrings with their total length
61+prependLength :: [ByteString] -> [ByteString]
62+prependLength bss = encodeInt32 (sum . map BS.length $ bss) : bss
63+
64+-- | Convert a "failing operation" into one that fails with a specific error
60−failWith :: (Monad m, Error a) => a -> Maybe b -> ErrorT a m b
65+failWith :: (MonadError e m) => e -> Maybe a -> m a
61failWith err Nothing = throwError err
66failWith err Nothing = throwError err
62failWith _ (Just x) = return x
67failWith _ (Just x) = return x
63
68
64−-- | Convert 'MaybeT' to an 'ErrorT'
69+-- | Try the specific I/O operation, and fail with the given error
65−failWithT :: (Monad m, Error a) => a -> MaybeT m b -> ErrorT a m b
70+-- when an exception occurs
66−failWithT err valueT = do
71+failWithIO :: (MonadIO m, MonadError e m) => (IOException -> e) -> IO a -> m a
67− mval <- lift $ runMaybeT valueT
72+failWithIO f io = do
73+ ma <- liftIO $ catch (Right <$> io) handleIOException
68− case mval of
74+ case ma of
69− Nothing -> throwError err
75+ Left err -> throwError (f err)
70− Just val -> return val
76+ Right a -> return a
77+ where
78+ handleIOException :: IOException -> IO (Either IOException a)
79+ handleIOException = return . Left
network-transport-2/src/Network/Transport/Internal/TCP.hs+19−67
@@ -1,22 +1,13 @@
1-- | Utility functions for TCP sockets
1-- | Utility functions for TCP sockets
2module Network.Transport.Internal.TCP ( forkServer
2module Network.Transport.Internal.TCP ( forkServer
3− , connectTo
4− , sendWithLength
5 , recvWithLength
3 , recvWithLength
6 , sendMany
4 , sendMany
7 , recvExact
5 , recvExact
8− , recvInt16
9 , recvInt32
6 , recvInt32
10− , sendInt16
11− , sendInt32
12 ) where
7 ) where
13
8
14import Prelude hiding (catch)
9import Prelude hiding (catch)
15−import Network.Transport.Internal ( encodeInt32
10+import Network.Transport.Internal (decodeInt32, failWithIO)
16− , decodeInt32
17− , encodeInt16
18− , decodeInt16
19− )
20import qualified Network.Socket as N ( HostName
11import qualified Network.Socket as N ( HostName
21 , ServiceName
12 , ServiceName
22 , Socket
13 , Socket
@@ -31,23 +22,21 @@ import qualified Network.Socket as N ( HostName
31 , addrAddress
22 , addrAddress
32 , defaultProtocol
23 , defaultProtocol
33 , setSocketOption
24 , setSocketOption
34− , connect
35 )
25 )
36import qualified Network.Socket.ByteString as NBS (sendMany, recv)
26import qualified Network.Socket.ByteString as NBS (sendMany, recv)
37import Control.Concurrent (forkIO, ThreadId)
27import Control.Concurrent (forkIO, ThreadId)
38−import Control.Monad (mzero, MonadPlus, unless)
28+import Control.Monad (mzero, MonadPlus)
39import Control.Monad.IO.Class (MonadIO, liftIO)
29import Control.Monad.IO.Class (MonadIO, liftIO)
30+import Control.Monad.Error (MonadError)
40import Control.Exception (catch, IOException)
31import Control.Exception (catch, IOException)
41−import Control.Applicative (pure)
42import Data.ByteString (ByteString)
32import Data.ByteString (ByteString)
43import qualified Data.ByteString as BS (length, concat, null, empty)
33import qualified Data.ByteString as BS (length, concat, null, empty)
44−import Data.Int (Int16, Int32)
34+import Data.Int (Int32)
45
35
46-- | Start a server at the specified address
36-- | Start a server at the specified address
47−--
37+forkServer :: (MonadIO m, MonadError IOException m)
48−-- TODO: deal with errors
49−forkServer :: N.HostName -> N.ServiceName -> (N.Socket -> IO ()) -> IO ThreadId
38+ => N.HostName -> N.ServiceName -> (N.Socket -> IO ()) -> m ThreadId
50−forkServer host port server = do
39+forkServer host port server = failWithIO id $ do
51 -- Resolve the specified address. By specification, getAddrInfo will never
40 -- Resolve the specified address. By specification, getAddrInfo will never
52 -- return an empty list (but will throw an exception instead) and will return
41 -- return an empty list (but will throw an exception instead) and will return
53 -- the "best" address first, whatever that means
42 -- the "best" address first, whatever that means
@@ -58,44 +47,23 @@ forkServer host port server = do
58 N.listen sock 5
47 N.listen sock 5
59 forkIO $ server sock
48 forkIO $ server sock
60
49
61−-- | Connect to another host
62−--
63−-- TODO: deal with errors
64−connectTo :: (MonadIO m) => N.HostName -> N.ServiceName -> m N.Socket
65−connectTo host port = liftIO $ do
66− addr:_ <- N.getAddrInfo Nothing (Just host) (Just port)
67− sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
68− N.setSocketOption sock N.ReuseAddr 1
69− N.connect sock (N.addrAddress addr)
70− return sock
71−
72−-- | Send a bunch of bytestrings prepended with their length
73−sendWithLength :: (MonadIO m, MonadPlus m)
74− => N.Socket -- ^ Socket to send on
75− -> Maybe ByteString -- ^ Optional header to send before the length
76− -> [ByteString] -- ^ Payload
77− -> m ()
78−sendWithLength sock header payload = do
79− lengthBs <- liftIO $ encodeInt32 (fromIntegral . sum . map BS.length $ payload)
80− let msg = maybe id (:) header $ lengthBs : payload
81− sendMany sock msg
82−
83-- | Read a length and then a payload of that length
50-- | Read a length and then a payload of that length
84recvWithLength :: (MonadIO m, MonadPlus m) => N.Socket -> m [ByteString]
51recvWithLength :: (MonadIO m, MonadPlus m) => N.Socket -> m [ByteString]
85−recvWithLength sock = do
52+recvWithLength sock = recvInt32 sock >>= recvExact sock
86− msgLengthBs <- recvExact sock 4
53+
54+-- | Receive a 32-bit integer
55+recvInt32 :: (Enum a, MonadIO m, MonadPlus m) => N.Socket -> m a
56+recvInt32 sock = do
87− decodeInt32 (BS.concat msgLengthBs) >>= recvExact sock
57+ mi <- recvExact sock 4 >>= return . decodeInt32 . BS.concat
58+ case mi of
59+ Nothing -> mzero
60+ Just i -> return i
88
61
89-- | Wrapper around 'Network.Socket.ByteString.sendMany'
62-- | Wrapper around 'Network.Socket.ByteString.sendMany'
90--
63--
91−-- Returns Nothing when an I/O exception is raised during the send
64+-- Fails when an I/O exception is raised during the send
92−sendMany :: (MonadIO m, MonadPlus m) => N.Socket -> [ByteString] -> m ()
65+sendMany :: (MonadIO m, MonadError IOException m) => N.Socket -> [ByteString] -> m ()
93−sendMany sock msg = do
66+sendMany sock msg = failWithIO id $ NBS.sendMany sock msg
94− success <- liftIO $ catch (NBS.sendMany sock msg >> return True) handleIOException
95− unless success mzero
96− where
97− handleIOException :: IOException -> IO Bool
98− handleIOException _ = return False
99
67
100-- | Read an exact number of bytes from a socket
68-- | Read an exact number of bytes from a socket
101--
69--
@@ -121,19 +89,3 @@ recvExact sock len = do
121 -- We treat an I/O exception the same way as a socket closure
89 -- We treat an I/O exception the same way as a socket closure
122 handleIOException :: IOException -> IO ByteString
90 handleIOException :: IOException -> IO ByteString
123 handleIOException _ = return BS.empty
91 handleIOException _ = return BS.empty
124−
125−-- | Receive a 16-bit integer
126−recvInt16 :: (MonadIO m, MonadPlus m) => N.Socket -> m Int16
127−recvInt16 sock = recvExact sock 2 >>= decodeInt16 . BS.concat
128−
129−-- | Receive a 32-bit integer
130−recvInt32 :: (MonadIO m, MonadPlus m) => N.Socket -> m Int32
131−recvInt32 sock = recvExact sock 4 >>= decodeInt32 . BS.concat
132−
133−-- | Send a 16-bit integer
134−sendInt16 :: (MonadIO m, MonadPlus m) => N.Socket -> Int16 -> m ()
135−sendInt16 sock n = encodeInt16 n >>= sendMany sock . pure
136−
137−-- | Send a 32-bit integer
138−sendInt32 :: (MonadIO m, MonadPlus m) => N.Socket -> Int32 -> m ()
139−sendInt32 sock n = encodeInt32 n >>= sendMany sock . pure
network-transport-2/src/Network/Transport/TCP.hs+136−64
@@ -3,6 +3,7 @@
3-- TODOs:
3-- TODOs:
4-- * Various errors are still left as "undefined"
4-- * Various errors are still left as "undefined"
5-- * Many possible errors are not yet dealt with
5-- * Many possible errors are not yet dealt with
6+
6module Network.Transport.TCP ( -- * Main API
7module Network.Transport.TCP ( -- * Main API
7 createTransport
8 createTransport
8 , -- TCP specific functionality
9 , -- TCP specific functionality
@@ -12,38 +13,55 @@ module Network.Transport.TCP ( -- * Main API
12
13
13import Network.Transport
14import Network.Transport
14import Network.Transport.Internal.TCP ( forkServer
15import Network.Transport.Internal.TCP ( forkServer
15− , connectTo
16 , recvWithLength
16 , recvWithLength
17− , sendWithLength
17+ , sendMany
18− , recvInt16
19 , recvInt32
18 , recvInt32
20− , sendInt32
21 )
19 )
22−import Network.Transport.Internal ( encodeInt16
20+import Network.Transport.Internal ( encodeInt32
23− , encodeInt32
21+ , decodeInt32
22+ , prependLength
24 , failWith
23 , failWith
25− , failWithT
24+ , failWithIO
26 )
25 )
27import qualified Network.Socket as N ( HostName
26import qualified Network.Socket as N ( HostName
28 , ServiceName
27 , ServiceName
29 , Socket
28 , Socket
30 , sClose
29 , sClose
31 , accept
30 , accept
31+ , getAddrInfo
32+ , socket
33+ , addrFamily
34+ , addrAddress
35+ , SocketType(Stream)
36+ , defaultProtocol
37+ , setSocketOption
38+ , SocketOption(ReuseAddr)
39+ , connect
32 )
40 )
33import Control.Concurrent (forkIO)
41import Control.Concurrent (forkIO)
34import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
42import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
35−import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, modifyMVar_, readMVar, takeMVar, putMVar)
43+import Control.Concurrent.MVar ( MVar
44+ , newMVar
45+ , modifyMVar
46+ , modifyMVar_
47+ , readMVar
48+ , takeMVar
49+ , putMVar
50+ , newEmptyMVar
51+ )
36import Control.Category ((>>>))
52import Control.Category ((>>>))
37import Control.Applicative ((<*>), (*>), (<$>))
53import Control.Applicative ((<*>), (*>), (<$>))
38import Control.Monad (forever, forM_, void)
54import Control.Monad (forever, forM_, void)
39−import Control.Monad.Error (ErrorT, liftIO, runErrorT)
55+import Control.Monad.Error (MonadError, ErrorT, liftIO, runErrorT, throwError)
40import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
56import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
41import Control.Monad.Trans.Writer (WriterT, execWriterT)
57import Control.Monad.Trans.Writer (WriterT, execWriterT)
42import Control.Monad.Writer.Class (tell)
58import Control.Monad.Writer.Class (tell)
43import Control.Monad.IO.Class (MonadIO)
59import Control.Monad.IO.Class (MonadIO)
60+import Control.Exception (IOException)
61+import Data.ByteString (ByteString)
44import qualified Data.ByteString as BS (concat)
62import qualified Data.ByteString as BS (concat)
45import qualified Data.ByteString.Char8 as BSC (pack, unpack)
63import qualified Data.ByteString.Char8 as BSC (pack, unpack)
46−import Data.Int (Int16)
64+import Data.Int (Int32)
47import Data.IntMap (IntMap)
65import Data.IntMap (IntMap)
48import qualified Data.IntMap as IntMap (empty, size)
66import qualified Data.IntMap as IntMap (empty, size)
49import Data.Map (Map)
67import Data.Map (Map)
@@ -64,15 +82,14 @@ data LocalEndPoint = LocalEndPoint { _localAddress :: EndPointAddress
64 }
82 }
65data RemoteEndPoint = RemoteEndPoint { _remoteAddress :: EndPointAddress
83data RemoteEndPoint = RemoteEndPoint { _remoteAddress :: EndPointAddress
66 , _remoteSocket :: N.Socket
84 , _remoteSocket :: N.Socket
67− , _remoteConnectionIds :: Chan ConnectionId
68 , _remoteLock :: MVar ()
85 , _remoteLock :: MVar ()
69 }
86 }
70data EndPointState = EndPointState { _nextConnectionId :: ConnectionId
87data EndPointState = EndPointState { _nextConnectionId :: ConnectionId
88+ , _pendingCtrlRequests :: IntMap (MVar [ByteString])
89+ , _nextCtrlRequestId :: ControlRequestId
71 }
90 }
72−type EndPointId = Int16
91+type EndPointId = Int32
73−
92+type ControlRequestId = Int32
74−#define REQUEST_CONNECTION_ID 0
75−#define RESPOND_CONNECTION_ID 1
76
93
77-- | Create a TCP transport
94-- | Create a TCP transport
78--
95--
@@ -81,10 +98,8 @@ type EndPointId = Int16
81-- connects to A two socket pairs will be created that will both be used
98-- connects to A two socket pairs will be created that will both be used
82-- unidirectionally, at degraded performance).
99-- unidirectionally, at degraded performance).
83--
100--
84−-- TODOs:
101+-- TODOs: deal with hints
85−-- * Perhaps we should allow to use something different other than 'defaultHints'
86−-- * Deal with all exceptions that may occur
87−createTransport :: N.HostName -> N.ServiceName -> IO Transport
102+createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
88createTransport host port = do
103createTransport host port = do
89 state <- newMVar TransportState { _localEndPoints = IntMap.empty
104 state <- newMVar TransportState { _localEndPoints = IntMap.empty
90 , _remoteEndPoints = Map.empty
105 , _remoteEndPoints = Map.empty
@@ -93,8 +108,9 @@ createTransport host port = do
93 , _transportHost = host
108 , _transportHost = host
94 , _transportPort = port
109 , _transportPort = port
95 }
110 }
111+ runErrorT $ do
96− forkServer host port (transportServer transport)
112+ forkServer host port (transportServer transport)
97− return Transport { newEndPoint = tcpNewEndPoint transport }
113+ return Transport { newEndPoint = tcpNewEndPoint transport }
98
114
99-- | The transport server handles incoming connections for all endpoints
115-- | The transport server handles incoming connections for all endpoints
100--
116--
@@ -104,39 +120,50 @@ transportServer transport sock = forever $ do
104 (clientSock, _) <- N.accept sock
120 (clientSock, _) <- N.accept sock
105 handleConnectionRequest transport clientSock
121 handleConnectionRequest transport clientSock
106
122
123+-- | Set up a new TCP connection (at the request of a remote endpoint)
107handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
124handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
108handleConnectionRequest transport sock = do
125handleConnectionRequest transport sock = do
109 request <- runMaybeT $ do
126 request <- runMaybeT $ do
110− ourEndPointId <- recvInt16 sock
127+ ourEndPointId <- recvInt32 sock
111 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
128 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
112 ourEndPoint <- MaybeT $ (^. localEndPointAt ourEndPointId) <$> readMVar (transport ^. transportState)
129 ourEndPoint <- MaybeT $ (^. localEndPointAt ourEndPointId) <$> readMVar (transport ^. transportState)
113 return (ourEndPoint, theirAddress)
130 return (ourEndPoint, theirAddress)
114 case request of
131 case request of
115 Just (ourEndPoint, theirAddress) ->
132 Just (ourEndPoint, theirAddress) ->
133+ -- We ignore the return value of 'forkRemoteEndPoint' here because _they_ are
134+ -- connecting to _us_. 'forkRemoteEndPoint' will have stored the remote endpoint
135+ -- in the transport state, however, in case _we_ later connect to _them_.
116 void $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
136 void $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
117 Nothing ->
137 Nothing ->
118 return ()
138 return ()
119
139
140+-- | Set up a new remote endpoint (either because we connected to a
141+-- new remote endpoint, or because a remote endpoint connected to us)
120forkRemoteEndPoint :: TCPTransport
142forkRemoteEndPoint :: TCPTransport
121 -> LocalEndPoint
143 -> LocalEndPoint
122 -> EndPointAddress
144 -> EndPointAddress
123 -> N.Socket
145 -> N.Socket
124 -> IO RemoteEndPoint
146 -> IO RemoteEndPoint
125−forkRemoteEndPoint transport ourEndPoint theirAddress theirSock = do
147+forkRemoteEndPoint transport ourEndPoint theirAddress sock = do
126− theirConnectionIds <- newChan
127− theirLock <- newMVar ()
148+ lock <- newMVar ()
128− let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
149+ let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
129− , _remoteSocket = theirSock
150+ , _remoteSocket = sock
130− , _remoteConnectionIds = theirConnectionIds
131− , _remoteLock = theirLock
151+ , _remoteLock = lock
132 }
152 }
133 modifyMVar_ (transport ^. transportState) $ return . (remoteEndPointAt theirAddress ^= Just theirEndPoint)
153 modifyMVar_ (transport ^. transportState) $ return . (remoteEndPointAt theirAddress ^= Just theirEndPoint)
134 forkIO $ do
154 forkIO $ do
135 unclosedConnections <- handleIncomingMessages ourEndPoint theirEndPoint
155 unclosedConnections <- handleIncomingMessages ourEndPoint theirEndPoint
136 forM_ unclosedConnections $ \cix -> writeChan (ourEndPoint ^. localChannel) (ConnectionClosed cix)
156 forM_ unclosedConnections $ \cix -> writeChan (ourEndPoint ^. localChannel) (ConnectionClosed cix)
137 -- TODO: should we close the socket here? (forkRemoteEndPoint gets called from multiple places)
157 -- TODO: should we close the socket here? (forkRemoteEndPoint gets called from multiple places)
158+ -- TODO: and should we remove it from the transaction state?
138 return theirEndPoint
159 return theirEndPoint
139
160
161+-- Control headers
162+data ControlHeaders =
163+ RequestConnectionId -- ^ Request a new connection ID from the remote endpoint
164+ | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
165+ deriving Enum
166+
140-- | Handle requests from a remote endpoint.
167-- | Handle requests from a remote endpoint.
141--
168--
142-- Returns only if the remote party closes the socket or if an error occurs.
169-- Returns only if the remote party closes the socket or if an error occurs.
@@ -145,37 +172,45 @@ forkRemoteEndPoint transport ourEndPoint theirAddress theirSock = do
145handleIncomingMessages :: LocalEndPoint -> RemoteEndPoint -> IO [ConnectionId]
172handleIncomingMessages :: LocalEndPoint -> RemoteEndPoint -> IO [ConnectionId]
146handleIncomingMessages ourEndPoint theirEndPoint = execWriterT . runMaybeT . forever $ do
173handleIncomingMessages ourEndPoint theirEndPoint = execWriterT . runMaybeT . forever $ do
147 connId <- recvInt32 sock
174 connId <- recvInt32 sock
175+ if connId >= firstNonReservedConnectionId
176+ then readMessage connId
177+ else do
178+ reqId <- recvInt32 sock
148− case connId of
179+ case toEnum (fromIntegral connId) of
149− REQUEST_CONNECTION_ID -> createNewConnection
180+ RequestConnectionId -> createNewConnection reqId
150− RESPOND_CONNECTION_ID -> readNewConnectionId
181+ ControlResponse -> readControlResponse reqId
151− _ -> readMessage connId
152 where
182 where
153 -- A brief note on the types:
183 -- A brief note on the types:
154 -- These functions may fail (connection error): hence MaybeT
184 -- These functions may fail (connection error): hence MaybeT
155 -- We also need to keep track of which connections are still open: hence WriterT
185 -- We also need to keep track of which connections are still open: hence WriterT
156
186
157 -- Create a new connection
187 -- Create a new connection
158− createNewConnection :: MaybeT (WriterT [ConnectionId] IO) ()
188+ createNewConnection :: ControlRequestId -> MaybeT (WriterT [ConnectionId] IO) ()
159− createNewConnection = do
189+ createNewConnection reqId = do
160 newId <- liftIO $ getNextConnectionId ourEndPoint
190 newId <- liftIO $ getNextConnectionId ourEndPoint
161 withRemoteLock theirEndPoint $ do
191 withRemoteLock theirEndPoint $ do
162− sendInt32 sock RESPOND_CONNECTION_ID
192+ sendMany sock (encodeInt32 ControlResponse : encodeInt32 reqId : prependLength [encodeInt32 newId])
163− sendInt32 sock newId
164 liftIO $ writeChan ourChannel (ConnectionOpened newId ReliableOrdered (theirEndPoint ^. remoteAddress))
193 liftIO $ writeChan ourChannel (ConnectionOpened newId ReliableOrdered (theirEndPoint ^. remoteAddress))
165 -- We add the new connection ID to the list of open connections only once the
194 -- We add the new connection ID to the list of open connections only once the
166 -- endpoint has been notified of the new connection (sendInt32 may fail)
195 -- endpoint has been notified of the new connection (sendInt32 may fail)
167 tell [newId]
196 tell [newId]
168
197
198+ -- Read a control response
199+ readControlResponse :: ControlRequestId -> MaybeT (WriterT [ConnectionId] IO) ()
200+ readControlResponse reqId = do
201+ response <- recvWithLength sock
202+ mmvar <- liftIO $ (^. pendingCtrlRequestsAt reqId) <$> readMVar ourState
203+ case mmvar of
204+ Nothing -> return () -- Invalid request ID. TODO: We just ignore it?
205+ Just mvar -> liftIO $ putMVar mvar response
206+
169 -- Read a message and output it on the endPoint's channel
207 -- Read a message and output it on the endPoint's channel
170 readMessage :: ConnectionId -> MaybeT (WriterT [ConnectionId] IO) ()
208 readMessage :: ConnectionId -> MaybeT (WriterT [ConnectionId] IO) ()
171 readMessage connId = recvWithLength sock >>= liftIO . writeChan ourChannel . Received connId
209 readMessage connId = recvWithLength sock >>= liftIO . writeChan ourChannel . Received connId
172
210
173− -- Read a new connection ID
174− readNewConnectionId :: MaybeT (WriterT [ConnectionId] IO) ()
175− readNewConnectionId = recvInt32 sock >>= liftIO . writeChan (theirEndPoint ^. remoteConnectionIds)
176−
177 -- Breakdown of the arguments
211 -- Breakdown of the arguments
178 ourChannel = ourEndPoint ^. localChannel
212 ourChannel = ourEndPoint ^. localChannel
213+ ourState = ourEndPoint ^. localState
179 sock = theirEndPoint ^. remoteSocket
214 sock = theirEndPoint ^. remoteSocket
180
215
181-- | Get the next connection ID
216-- | Get the next connection ID
@@ -189,7 +224,10 @@ tcpNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) E
189tcpNewEndPoint transport = do
224tcpNewEndPoint transport = do
190 endPoint <- modifyMVar (transport ^. transportState) $ \st -> do
225 endPoint <- modifyMVar (transport ^. transportState) $ \st -> do
191 chan <- newChan
226 chan <- newChan
192− state <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId }
227+ state <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId
228+ , _pendingCtrlRequests = IntMap.empty
229+ , _nextCtrlRequestId = 0
230+ }
193 let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
231 let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
194 let host = transport ^. transportHost
232 let host = transport ^. transportHost
195 let port = transport ^. transportPort
233 let port = transport ^. transportPort
@@ -220,22 +258,41 @@ tcpConnect :: TCPTransport -- ^ Transport
220 -> Reliability -- ^ Reliability (ignored)
258 -> Reliability -- ^ Reliability (ignored)
221 -> IO (Either (FailedWith ConnectErrorCode) Connection)
259 -> IO (Either (FailedWith ConnectErrorCode) Connection)
222tcpConnect transport ourEndPoint theirAddress _ = runErrorT $ do
260tcpConnect transport ourEndPoint theirAddress _ = runErrorT $ do
223− theirEndPoint <- remoteEndPoint transport ourEndPoint theirAddress
261+ theirEndPoint <- remoteEndPoint transport ourEndPoint theirAddress
224− connId <- failWithT undefined $ requestNewConnection theirEndPoint
262+ connId <- requestNewConnection ourEndPoint theirEndPoint
225− connBs <- encodeInt32 connId
226− let sock = theirEndPoint ^. remoteSocket
263+ let sock = theirEndPoint ^. remoteSocket
227− return Connection { send = runErrorT .
264+ return Connection { send = runErrorT .
228− failWithT (FailedWith SendFailed "Send failed") .
265+ failWithIO (FailedWith SendFailed . show) .
229− withRemoteLock theirEndPoint .
266+ withRemoteLock theirEndPoint .
230− sendWithLength sock (Just connBs)
267+ sendMany sock .
268+ (\payload -> encodeInt32 connId : prependLength payload)
269+ , close = -- TODO: we should not close the TCP connection until all
270+ -- lightweight connections have been closed
231− , close = N.sClose sock
271+ N.sClose sock
232− }
272+ }
233
273
234-- | Request a new connection
274-- | Request a new connection
275+requestNewConnection :: (MonadIO m, MonadError (FailedWith ConnectErrorCode) m)
276+ => LocalEndPoint
235−requestNewConnection :: RemoteEndPoint -> MaybeT IO ConnectionId
277+ -> RemoteEndPoint
278+ -> m ConnectionId
236−requestNewConnection endPoint = do
279+requestNewConnection ourEndPoint theirEndPoint = do
237− withRemoteLock endPoint $ sendInt32 (endPoint ^. remoteSocket) 0
280+ mcid <- failWithIO (failed . show) $ do
238− liftIO $ readChan (endPoint ^. remoteConnectionIds)
281+ reply <- newEmptyMVar
282+ reqId <- modifyMVar state $ \st -> do
283+ let reqId = st ^. nextCtrlRequestId
284+ return ((nextCtrlRequestId ^+= 1) . (pendingCtrlRequestsAt reqId ^= Just reply) $ st, reqId)
285+ withRemoteLock theirEndPoint $ sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId]
286+ response <- decodeInt32 . BS.concat <$> takeMVar reply
287+ modifyMVar_ state $ return . (pendingCtrlRequestsAt reqId ^= Nothing)
288+ return response
289+ case mcid of
290+ Nothing -> throwError (failed "Invalid integer")
291+ Just cid -> return cid
292+ where
293+ sock = theirEndPoint ^. remoteSocket
294+ failed = FailedWith ConnectFailed
295+ state = ourEndPoint ^. localState
239
296
240-- | Find a remote endpoint
297-- | Find a remote endpoint
241--
298--
@@ -250,14 +307,23 @@ remoteEndPoint transport ourEndPoint theirAddress = do
250 Just endpoint ->
307 Just endpoint ->
251 return endpoint
308 return endpoint
252 Nothing -> do
309 Nothing -> do
310+ -- Create a new connection
253 -- liftIO $ putStrLn $ "Creating socket from " ++ show ourAddress ++ " to " ++ show theirAddress
311 -- liftIO $ putStrLn $ "Creating socket from " ++ show ourAddress ++ " to " ++ show theirAddress
254− (host, port, endPointId) <- failWith invalidAddress (decodeEndPointAddress theirAddress)
312+ (host, port, endPointId) <- failWith (invalidAddress "Could not parse") $
255− sock <- failWithT undefined (connectTo host port)
313+ (decodeEndPointAddress theirAddress)
256− endPointBS <- encodeInt16 endPointId
314+ addr:_ <- failWithIO (invalidAddress . show) $
257− failWithT undefined $ sendWithLength sock (Just endPointBS) [ourAddress]
315+ N.getAddrInfo Nothing (Just host) (Just port)
316+ sock <- failWithIO (insufficientResources . show) $
317+ N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
318+ liftIO $ N.setSocketOption sock N.ReuseAddr 1
319+ failWithIO (failed . show) $ N.connect sock (N.addrAddress addr)
320+ -- Send the endpoint ID we're interested in and our own address
321+ failWithIO (failed . show) $ sendMany sock (encodeInt32 endPointId : prependLength [ourAddress])
258 liftIO $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
322 liftIO $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
259 where
323 where
260− invalidAddress = FailedWith ConnectInvalidAddress "Invalid address"
324+ invalidAddress = FailedWith ConnectInvalidAddress
325+ insufficientResources = FailedWith ConnectInsufficientResources
326+ failed = FailedWith ConnectFailed
261 EndPointAddress ourAddress = ourEndPoint ^. localAddress
327 EndPointAddress ourAddress = ourEndPoint ^. localAddress
262
328
263-- | We must avoid scrambling concurrent sends to the same endpoint
329-- | We must avoid scrambling concurrent sends to the same endpoint
@@ -313,17 +379,23 @@ remoteAddress = lens _remoteAddress (\addr ep -> ep { _remoteAddress = addr })
313remoteSocket :: Lens RemoteEndPoint N.Socket
379remoteSocket :: Lens RemoteEndPoint N.Socket
314remoteSocket = lens _remoteSocket (\sock ep -> ep { _remoteSocket = sock })
380remoteSocket = lens _remoteSocket (\sock ep -> ep { _remoteSocket = sock })
315
381
316−remoteConnectionIds :: Lens RemoteEndPoint (Chan ConnectionId)
317−remoteConnectionIds = lens _remoteConnectionIds (\ch ep -> ep { _remoteConnectionIds = ch })
318−
319remoteLock :: Lens RemoteEndPoint (MVar ())
382remoteLock :: Lens RemoteEndPoint (MVar ())
320remoteLock = lens _remoteLock (\lock ep -> ep { _remoteLock = lock })
383remoteLock = lens _remoteLock (\lock ep -> ep { _remoteLock = lock })
321
384
322nextConnectionId :: Lens EndPointState ConnectionId
385nextConnectionId :: Lens EndPointState ConnectionId
323nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
386nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
324
387
388+pendingCtrlRequests :: Lens EndPointState (IntMap (MVar [ByteString]))
389+pendingCtrlRequests = lens _pendingCtrlRequests (\rep st -> st { _pendingCtrlRequests = rep })
390+
391+nextCtrlRequestId :: Lens EndPointState ControlRequestId
392+nextCtrlRequestId = lens _nextCtrlRequestId (\cid st -> st { _nextCtrlRequestId = cid })
393+
325localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
394localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
326localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
395localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
327
396
328remoteEndPointAt :: EndPointAddress -> Lens TransportState (Maybe RemoteEndPoint)
397remoteEndPointAt :: EndPointAddress -> Lens TransportState (Maybe RemoteEndPoint)
329remoteEndPointAt addr = remoteEndPoints >>> mapLens addr
398remoteEndPointAt addr = remoteEndPoints >>> mapLens addr
399+
400+pendingCtrlRequestsAt :: ControlRequestId -> Lens EndPointState (Maybe (MVar [ByteString]))
401+pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
network-transport-2/tests/TestInMemory.hs+1−4
@@ -2,9 +2,6 @@ module Main where
2
2
3import TestTransport
3import TestTransport
4import Network.Transport.Chan
4import Network.Transport.Chan
5−import System.Exit (exitFailure, exitSuccess)
6
5
7main :: IO ()
6main :: IO ()
8−main = do
9− success <- createTransport >>= testTransport
7+main = createTransport >>= testTransport
10− if success then exitSuccess else exitFailure
network-transport-2/tests/TestTCP.hs+44−12
@@ -1,14 +1,10 @@
1module Main where
1module Main where
2
2
3import Network.Transport
3import Network.Transport
4−import Network.Transport.Internal (encodeInt16, decodeInt16)
4+import Network.Transport.Internal (encodeInt32, decodeInt32, prependLength)
5−import Network.Transport.Internal.TCP ( sendWithLength
5+import Network.Transport.Internal.TCP (recvExact, sendMany)
6− , recvExact
7− , sendInt32
8− )
9import Network.Transport.TCP (createTransport, decodeEndPointAddress, EndPointId)
6import Network.Transport.TCP (createTransport, decodeEndPointAddress, EndPointId)
10import TestTransport (testTransport)
7import TestTransport (testTransport)
11−import System.Exit (exitFailure, exitSuccess)
12import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar)
8import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar)
13import Control.Concurrent (forkIO)
9import Control.Concurrent (forkIO)
14import Control.Monad.Trans.Maybe (runMaybeT)
10import Control.Monad.Trans.Maybe (runMaybeT)
@@ -29,9 +25,11 @@ import qualified Network.Socket as N ( getAddrInfo
29
25
30testEarlyDisconnect :: IO ()
26testEarlyDisconnect :: IO ()
31testEarlyDisconnect = do
27testEarlyDisconnect = do
28+ return ()
29+{-
32 serverAddr <- newEmptyMVar
30 serverAddr <- newEmptyMVar
33 serverDone <- newEmptyMVar
31 serverDone <- newEmptyMVar
34− transport <- createTransport "127.0.0.1" "8081"
32+ Right transport <- createTransport "127.0.0.1" "8081"
35
33
36 forkIO $ server transport serverAddr serverDone
34 forkIO $ server transport serverAddr serverDone
37 forkIO $ client transport serverAddr
35 forkIO $ client transport serverAddr
@@ -58,11 +56,10 @@ testEarlyDisconnect = do
58 N.connect sock (N.addrAddress addr)
56 N.connect sock (N.addrAddress addr)
59
57
60 (_, _, endPointIx) <- readMVar serverAddr
58 (_, _, endPointIx) <- readMVar serverAddr
61− endPointBS <- encodeInt16 (fromIntegral endPointIx)
59+ runMaybeT $ sendMany sock (encodeInt32 endPointIx : prependLength [myAddress])
62− runMaybeT $ sendWithLength sock (Just endPointBS) [myAddress]
63
60
64 -- Request a new connection
61 -- Request a new connection
65− connIx <- runMaybeT $ do
62+ runMaybeT $ do
66 sendInt32 sock 0
63 sendInt32 sock 0
67 [connBs] <- recvExact sock 2
64 [connBs] <- recvExact sock 2
68 decodeInt16 connBs
65 decodeInt16 connBs
@@ -70,9 +67,44 @@ testEarlyDisconnect = do
70 -- Close the socket without closing the connection explicitly
67 -- Close the socket without closing the connection explicitly
71 -- The server should still receive a ConnectionClosed message
68 -- The server should still receive a ConnectionClosed message
72 N.sClose sock
69 N.sClose sock
70+-}
71+
72+testInvalidAddress :: IO ()
73+testInvalidAddress = do
74+ Left err <- createTransport "invalidHostName" "8082"
75+ putStrLn $ "Got expected error: " ++ show err
76+
77+testInvalidConnect :: IO ()
78+testInvalidConnect = do
79+ Right transport <- createTransport "127.0.0.1" "8083"
80+ Right endpoint <- newEndPoint transport
81+
82+ -- Syntax error in the endpoint address
83+ Left (FailedWith ConnectInvalidAddress err1) <-
84+ connect endpoint (EndPointAddress "InvalidAddress") ReliableOrdered
85+ putStrLn $ "Got expected error: " ++ show err1
86+
87+ -- Syntax connect, but invalid hostname (TCP address lookup failure)
88+ Left (FailedWith ConnectInvalidAddress err2) <-
89+ connect endpoint (EndPointAddress "invalidHost:port:0") ReliableOrdered
90+ putStrLn $ "Got expected error: " ++ show err2
91+
92+ -- TCP address correct, but nobody home at that address
93+ Left (FailedWith ConnectFailed err3) <-
94+ connect endpoint (EndPointAddress "127.0.0.1:9000:0") ReliableOrdered
95+ putStrLn $ "Got expected error: " ++ show err3
96+
97+ -- Valid TCP address but invalid endpoint number
98+ {-
99+ Left (FailedWith ConnectFailed err4) <-
100+ connect endpoint (EndPointAddress "127.0.0.1:8083:1") ReliableOrdered
101+ putStrLn $ "Got expected error: " ++ show err4
102+ -}
73
103
74main :: IO ()
104main :: IO ()
75main = do
105main = do
76 testEarlyDisconnect
106 testEarlyDisconnect
107+ testInvalidAddress
108+ testInvalidConnect
77− success <- createTransport "127.0.0.1" "8080" >>= testTransport
109+ Right transport <- createTransport "127.0.0.1" "8080"
78− if success then exitSuccess else exitFailure
110+ testTransport transport
network-transport-2/tests/TestTransport.hs+13−18
@@ -2,8 +2,7 @@ module TestTransport where
2
2
3import Control.Concurrent (forkIO)
3import Control.Concurrent (forkIO)
4-- import Control.Concurrent (myThreadId)
4-- import Control.Concurrent (myThreadId)
5−import Control.Monad (liftM2, replicateM, replicateM_, when)
5+import Control.Monad (replicateM, replicateM_, when)
6−import Control.Applicative ((<$>))
7import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
6import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
8import Network.Transport
7import Network.Transport
9import Data.ByteString (ByteString)
8import Data.ByteString (ByteString)
@@ -11,8 +10,6 @@ import Data.ByteString.Char8 ()
11import Data.Map (Map)
10import Data.Map (Map)
12import qualified Data.Map as Map (empty, insert, (!), delete)
11import qualified Data.Map as Map (empty, insert, (!), delete)
13
12
14−type Test = IO Bool
15−
16-- Logging (for debugging)
13-- Logging (for debugging)
17tlog :: String -> IO ()
14tlog :: String -> IO ()
18tlog _ = return ()
15tlog _ = return ()
@@ -72,7 +69,7 @@ ping endpoint server numPings msg = do
72 close conn
69 close conn
73
70
74-- Basic ping test
71-- Basic ping test
75−testPingPong :: Transport -> Int -> Test
72+testPingPong :: Transport -> Int -> IO ()
76testPingPong transport numPings = do
73testPingPong transport numPings = do
77 tlog "Starting ping pong test"
74 tlog "Starting ping pong test"
78 server <- spawn transport echoServer
75 server <- spawn transport echoServer
@@ -84,12 +81,12 @@ testPingPong transport numPings = do
84 Right endpoint <- newEndPoint transport
81 Right endpoint <- newEndPoint transport
85 ping endpoint server numPings "ping"
82 ping endpoint server numPings "ping"
86 putStrLn $ "client did " ++ show numPings ++ " pings"
83 putStrLn $ "client did " ++ show numPings ++ " pings"
87− putMVar result True
84+ putMVar result ()
88
85
89 takeMVar result
86 takeMVar result
90
87
91-- Test that endpoints don't get confused
88-- Test that endpoints don't get confused
92−testEndPoints :: Transport -> Int -> Test
89+testEndPoints :: Transport -> Int -> IO ()
93testEndPoints transport numPings = do
90testEndPoints transport numPings = do
94 server <- spawn transport echoServer
91 server <- spawn transport echoServer
95 [resultA, resultB] <- replicateM 2 newEmptyMVar
92 [resultA, resultB] <- replicateM 2 newEmptyMVar
@@ -99,19 +96,19 @@ testEndPoints transport numPings = do
99 Right endpoint <- newEndPoint transport
96 Right endpoint <- newEndPoint transport
100 ping endpoint server numPings "pingA"
97 ping endpoint server numPings "pingA"
101 putStrLn $ "client A did " ++ show numPings ++ " pings"
98 putStrLn $ "client A did " ++ show numPings ++ " pings"
102− putMVar resultA True
99+ putMVar resultA ()
103
100
104 -- Client B
101 -- Client B
105 forkIO $ do
102 forkIO $ do
106 Right endpoint <- newEndPoint transport
103 Right endpoint <- newEndPoint transport
107 ping endpoint server numPings "pingB"
104 ping endpoint server numPings "pingB"
108 putStrLn $ "client B did " ++ show numPings ++ " pings"
105 putStrLn $ "client B did " ++ show numPings ++ " pings"
109− putMVar resultB True
106+ putMVar resultB ()
110
107
111− and <$> mapM takeMVar [resultA, resultB]
108+ mapM_ takeMVar [resultA, resultB]
112
109
113-- Test that connections don't get confused
110-- Test that connections don't get confused
114−testConnections :: Transport -> Int -> Test
111+testConnections :: Transport -> Int -> IO ()
115testConnections transport numPings = do
112testConnections transport numPings = do
116 server <- spawn transport echoServer
113 server <- spawn transport echoServer
117 result <- newEmptyMVar
114 result <- newEmptyMVar
@@ -138,7 +135,7 @@ testConnections transport numPings = do
138 putStrLn $ "client B did " ++ show numPings ++ " pings"
135 putStrLn $ "client B did " ++ show numPings ++ " pings"
139
136
140 -- Verify server responses
137 -- Verify server responses
141− let verifyResponse 0 = putMVar result True
138+ let verifyResponse 0 = putMVar result ()
142 verifyResponse n = do
139 verifyResponse n = do
143 event <- receive endpoint
140 event <- receive endpoint
144 case event of
141 case event of
@@ -153,10 +150,8 @@ testConnections transport numPings = do
153 takeMVar result
150 takeMVar result
154
151
155-- Transport tests
152-- Transport tests
156−testTransport :: Transport -> IO Bool
153+testTransport :: Transport -> IO ()
157testTransport transport = do
154testTransport transport = do
158− tlog "Starting transport tests"
159− foldl (liftM2 (&&)) (return True) [ testPingPong transport 10000
155+ testPingPong transport 10000
160− , testEndPoints transport 10000
156+ testEndPoints transport 10000
161− , testConnections transport 10000
157+ testConnections transport 10000
162− ]