git.iko.soy/distributed-process
on master help sign in
Start support for TCP parameters
commitdee71b32834cc0c82b4509599d4dc268b1bc4c37 authorEdsko de Vries <edsko@edsko.net> dateFri 1 Jun 2012, 15:50 (14 years ago) parenta2d5758
3 files changed+113−66
network-transport/src/Network/Transport/Internal/TCP.hs+4−3 network-transport/src/Network/Transport/TCP.hs+85−40 network-transport/tests/TestTCP.hs+24−23
network-transport/src/Network/Transport/Internal/TCP.hs+4−3
@@ -27,7 +27,7 @@ import qualified Network.Socket as N ( HostName
2727 )
2828import qualified Network.Socket.ByteString as NBS (recv)
2929import Control.Concurrent (ThreadId)
30−import Control.Monad (forever)
30+import Control.Monad (forever, when)
3131import Control.Exception (SomeException, catch, bracketOnError, throwIO, mask_)
3232import Control.Applicative ((<$>))
3333import Data.ByteString (ByteString)
@@ -54,17 +54,18 @@ import Data.Int (Int32)
5454forkServer :: N.HostName -- ^ Host
5555 -> N.ServiceName -- ^ Port
5656 -> Int -- ^ Backlog (maximum number of queued connections)
57+ -> Bool -- ^ Set ReuseAddr option?
5758 -> (SomeException -> IO ()) -- ^ Termination handler
5859 -> (N.Socket -> IO ()) -- ^ Request handler
5960 -> IO ThreadId
60−forkServer host port backlog terminationHandler requestHandler = do
61+forkServer host port backlog reuseAddr terminationHandler requestHandler = do
6162 -- Resolve the specified address. By specification, getAddrInfo will never
6263 -- return an empty list (but will throw an exception instead) and will return
6364 -- the "best" address first, whatever that means
6465 addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
6566 bracketOnError (N.socket (N.addrFamily addr) N.Stream N.defaultProtocol)
6667 tryCloseSocket $ \sock -> do
67− N.setSocketOption sock N.ReuseAddr 1
68+ when reuseAddr $ N.setSocketOption sock N.ReuseAddr 1
6869 N.bindSocket sock (N.addrAddress addr)
6970 N.listen sock backlog
7071 mask_ $ forkIOWithUnmask $ \unmask ->
network-transport/src/Network/Transport/TCP.hs+85−40
@@ -12,6 +12,8 @@
1212-- compatibility (see "Network.Socket").
1313module Network.Transport.TCP ( -- * Main API
1414 createTransport
15+ , TCPParameters(..)
16+ , defaultTCPParameters
1517 -- * Internals (exposed for unit tests)
1618 , createTransportExposeInternals
1719 , TransportInternals(..)
@@ -216,9 +218,10 @@ import Data.Foldable (forM_, mapM_)
216218-- ValidRemoteEndPointState).
217219
218220data TCPTransport = TCPTransport
219− { transportHost :: N.HostName
220− , transportPort :: N.ServiceName
221− , transportState :: MVar TransportState
221+ { transportHost :: N.HostName
222+ , transportPort :: N.ServiceName
223+ , transportState :: MVar TransportState
224+ , transportParams :: TCPParameters
222225 }
223226
224227data TransportState =
@@ -381,6 +384,19 @@ data ConnectionRequestResponse =
381384 | ConnectionRequestCrossed
382385 deriving (Enum, Bounded, Show)
383386
387+-- Parameters for setting up the TCP transport
388+data TCPParameters = TCPParameters {
389+ -- | Backlog for 'listen'.
390+ -- Defaults to SOMAXCONN.
391+ tcpBacklog :: Int
392+ -- | Should we set SO_REUSEADDR on the server socket?
393+ -- Defaults to True.
394+ , tcpReuseServerAddr :: Bool
395+ -- | Should we set SO_REUSEADDR on client sockets?
396+ -- Defaults to True.
397+ , tcpReuseClientAddr :: Bool
398+ }
399+
384400-- Internal functionality we expose for unit testing
385401data TransportInternals = TransportInternals
386402 { -- | The ID of the thread that listens for new incoming connections
@@ -398,42 +414,54 @@ data TransportInternals = TransportInternals
398414-- | Create a TCP transport
399415createTransport :: N.HostName
400416 -> N.ServiceName
417+ -> TCPParameters
401418 -> IO (Either IOException Transport)
402−createTransport host port =
403− either Left (Right . fst) <$> createTransportExposeInternals host port
419+createTransport host port params =
420+ either Left (Right . fst) <$> createTransportExposeInternals host port params
404421
405422-- | You should probably not use this function (used for unit testing only)
406423createTransportExposeInternals
407424 :: N.HostName
408425 -> N.ServiceName
426+ -> TCPParameters
409427 -> IO (Either IOException (Transport, TransportInternals))
410−createTransportExposeInternals host port = do
428+createTransportExposeInternals host port params = do
411429 state <- newMVar . TransportValid $ ValidTransportState
412430 { _localEndPoints = Map.empty
413431 , _nextEndPointId = 0
414432 }
415− let transport = TCPTransport { transportState = state
416− , transportHost = host
417− , transportPort = port
433+ let transport = TCPTransport { transportState = state
434+ , transportHost = host
435+ , transportPort = port
436+ , transportParams = params
418437 }
419− tryIO $ do
420− tid <- forkServer host port N.sOMAXCONN
421− (terminationHandler transport)
422− (handleConnectionRequest transport)
423− return
424− ( Transport
425− { newEndPoint = apiNewEndPoint transport
426− , closeTransport = let evs = [ EndPointClosed
427− , throw $ userError "Transport closed"
428− ] in
429− apiCloseTransport transport (Just tid) evs
430− }
431− , TransportInternals
432− { transportThread = tid
433− , socketBetween = internalSocketBetween transport
434− }
435− )
438+ tryIO $ bracketOnError (forkServer
439+ host
440+ port
441+ (tcpBacklog params)
442+ (tcpReuseServerAddr params)
443+ (terminationHandler transport)
444+ (handleConnectionRequest transport))
445+ killThread
446+ (mkTransport transport)
436447 where
448+ mkTransport :: TCPTransport
449+ -> ThreadId
450+ -> IO (Transport, TransportInternals)
451+ mkTransport transport tid = return
452+ ( Transport
453+ { newEndPoint = apiNewEndPoint transport
454+ , closeTransport = let evs = [ EndPointClosed
455+ , throw $ userError "Transport closed"
456+ ] in
457+ apiCloseTransport transport (Just tid) evs
458+ }
459+ , TransportInternals
460+ { transportThread = tid
461+ , socketBetween = internalSocketBetween transport
462+ }
463+ )
464+
437465 terminationHandler :: TCPTransport -> SomeException -> IO ()
438466 terminationHandler transport ex = do
439467 let evs = [ ErrorEvent (TransportError EventTransportFailed (show ex))
@@ -441,6 +469,14 @@ createTransportExposeInternals host port = do
441469 ]
442470 apiCloseTransport transport Nothing evs
443471
472+-- | Default TCP parameters
473+defaultTCPParameters :: TCPParameters
474+defaultTCPParameters = TCPParameters {
475+ tcpBacklog = N.sOMAXCONN
476+ , tcpReuseServerAddr = True
477+ , tcpReuseClientAddr = True
478+ }
479+
444480--------------------------------------------------------------------------------
445481-- API functions --
446482--------------------------------------------------------------------------------
@@ -465,7 +501,7 @@ apiNewEndPoint transport = try $ do
465501 return EndPoint
466502 { receive = readChan (localChannel ourEndPoint)
467503 , address = localAddress ourEndPoint
468− , connect = apiConnect ourEndPoint
504+ , connect = apiConnect (transportParams transport) ourEndPoint
469505 , closeEndPoint = let evs = [ EndPointClosed
470506 , throw $ userError "Endpoint closed"
471507 ] in
@@ -480,18 +516,19 @@ apiNewEndPoint transport = try $ do
480516 TransportError ResolveMulticastGroupUnsupported "Multicast not supported"
481517
482518-- | Connnect to an endpoint
483−apiConnect :: LocalEndPoint -- ^ Local end point
519+apiConnect :: TCPParameters -- ^ Parameters
520+ -> LocalEndPoint -- ^ Local end point
484521 -> EndPointAddress -- ^ Remote address
485522 -> Reliability -- ^ Reliability (ignored)
486523 -> ConnectHints -- ^ Hints
487524 -> IO (Either (TransportError ConnectErrorCode) Connection)
488−apiConnect ourEndPoint theirAddress _reliability hints =
525+apiConnect params ourEndPoint theirAddress _reliability hints =
489526 if localAddress ourEndPoint == theirAddress
490527 then connectToSelf ourEndPoint
491528 else try $ do
492529 resetIfBroken ourEndPoint theirAddress
493530 (theirEndPoint, connId) <-
494− requestConnectionTo ourEndPoint theirAddress hints
531+ requestConnectionTo params ourEndPoint theirAddress hints
495532 -- connAlive can be an IORef rather than an MVar because it is protected
496533 -- by the remoteState MVar. We don't need the overhead of locking twice.
497534 connAlive <- newIORef True
@@ -752,11 +789,12 @@ createLocalEndPoint transport = do
752789-- additionally block until that is resolved.
753790--
754791-- May throw a TransportError ConnectErrorCode exception.
755−requestConnectionTo :: LocalEndPoint
792+requestConnectionTo :: TCPParameters
793+ -> LocalEndPoint
756794 -> EndPointAddress
757795 -> ConnectHints
758796 -> IO (RemoteEndPoint, ConnectionId)
759−requestConnectionTo ourEndPoint theirAddress hints = go
797+requestConnectionTo params ourEndPoint theirAddress hints = go
760798 where
761799 go = do
762800 (theirEndPoint, isNew) <- mapIOException connectFailed $
@@ -765,7 +803,7 @@ requestConnectionTo ourEndPoint theirAddress hints = go
765803 if isNew
766804 then do
767805 void . forkEndPointThread ourEndPoint . handle absorbAllExceptions $
768− setupRemoteEndPoint (ourEndPoint, theirEndPoint) hints
806+ setupRemoteEndPoint params (ourEndPoint, theirEndPoint) hints
769807 go
770808 else do
771809 reply <- mapIOException connectFailed $
@@ -779,9 +817,13 @@ requestConnectionTo ourEndPoint theirAddress hints = go
779817 return ()
780818
781819-- | Set up a remote endpoint
782−setupRemoteEndPoint :: EndPointPair -> ConnectHints -> IO ()
783−setupRemoteEndPoint (ourEndPoint, theirEndPoint) hints = do
784− didAccept <- bracketOnError (socketToEndPoint ourAddress theirAddress hints)
820+setupRemoteEndPoint :: TCPParameters -> EndPointPair -> ConnectHints -> IO ()
821+setupRemoteEndPoint params (ourEndPoint, theirEndPoint) hints = do
822+ didAccept <- bracketOnError (socketToEndPoint
823+ ourAddress
824+ theirAddress
825+ (tcpReuseClientAddr params)
826+ (connectTimeout hints))
785827 onError $ \result -> case result of
786828 Right (sock, ConnectionRequestAccepted) -> do
787829 let vst = ValidRemoteEndPointState
@@ -861,19 +903,22 @@ resolveInit (ourEndPoint, theirEndPoint) newState =
861903-- Maybe throw a TransportError
862904socketToEndPoint :: EndPointAddress -- ^ Our address
863905 -> EndPointAddress -- ^ Their address
864− -> ConnectHints -- ^ Connection hints
906+ -> Bool -- ^ Use SO_REUSEADDR?
907+ -> Maybe Int -- ^ Timeout for connect
865908 -> IO (Either (TransportError ConnectErrorCode)
866909 (N.Socket, ConnectionRequestResponse))
867−socketToEndPoint (EndPointAddress ourAddress) theirAddress hints = try $ do
910+socketToEndPoint (EndPointAddress ourAddress) theirAddress reuseAddr timeout =
911+ try $ do
868912 (host, port, theirEndPointId) <- case decodeEndPointAddress theirAddress of
869913 Nothing -> throwIO (failed . userError $ "Could not parse")
870914 Just dec -> return dec
871915 addr:_ <- mapIOException invalidAddress $
872916 N.getAddrInfo Nothing (Just host) (Just port)
873917 bracketOnError (createSocket addr) tryCloseSocket $ \sock -> do
874− mapIOException failed $ N.setSocketOption sock N.ReuseAddr 1
918+ when reuseAddr $
919+ mapIOException failed $ N.setSocketOption sock N.ReuseAddr 1
875920 mapIOException invalidAddress $
876− timeoutMaybe (connectTimeout hints) timeoutError $
921+ timeoutMaybe timeout timeoutError $
877922 N.connect sock (N.addrAddress addr)
878923 response <- mapIOException failed $ do
879924 sendMany sock (encodeInt32 theirEndPointId : prependLength [ourAddress])
network-transport/tests/TestTCP.hs+24−23
@@ -10,6 +10,7 @@ import Network.Transport.TCP ( createTransport
1010 , createTransportExposeInternals
1111 , TransportInternals(..)
1212 , encodeEndPointAddress
13+ , defaultTCPParameters
1314 )
1415import Data.Int (Int32)
1516import Control.Concurrent (threadDelay, killThread)
@@ -82,7 +83,7 @@ testEarlyDisconnect nextPort = do
8283 server :: MVar EndPointAddress -> MVar EndPointAddress -> MVar () -> IO ()
8384 server serverAddr clientAddr serverDone = do
8485 tlog "Server"
85− Right transport <- nextPort >>= createTransport "127.0.0.1"
86+ Right transport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
8687 Right endpoint <- newEndPoint transport
8788 putMVar serverAddr (address endpoint)
8889 theirAddr <- readMVar clientAddr
@@ -134,7 +135,7 @@ testEarlyDisconnect nextPort = do
134135 putMVar clientAddr ourAddress
135136
136137 -- Listen for incoming messages
137− forkServer "127.0.0.1" clientPort 5 throwIO $ \sock -> do
138+ forkServer "127.0.0.1" clientPort 5 True throwIO $ \sock -> do
138139 -- Initial setup
139140 0 <- recvInt32 sock :: IO Int
140141 _ <- recvWithLength sock
@@ -160,7 +161,7 @@ testEarlyDisconnect nextPort = do
160161 N.sClose sock
161162
162163 -- Connect to the server
163− Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr defaultConnectHints
164+ Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr True Nothing
164165
165166 -- Request a new connection, but don't wait for the response
166167 let reqId = 0 :: Int32
@@ -186,7 +187,7 @@ testEarlyCloseSocket nextPort = do
186187 server :: MVar EndPointAddress -> MVar EndPointAddress -> MVar () -> IO ()
187188 server serverAddr clientAddr serverDone = do
188189 tlog "Server"
189− Right transport <- nextPort >>= createTransport "127.0.0.1"
190+ Right transport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
190191 Right endpoint <- newEndPoint transport
191192 putMVar serverAddr (address endpoint)
192193 theirAddr <- readMVar clientAddr
@@ -245,7 +246,7 @@ testEarlyCloseSocket nextPort = do
245246 putMVar clientAddr ourAddress
246247
247248 -- Listen for incoming messages
248− forkServer "127.0.0.1" clientPort 5 throwIO $ \sock -> do
249+ forkServer "127.0.0.1" clientPort 5 True throwIO $ \sock -> do
249250 -- Initial setup
250251 0 <- recvInt32 sock :: IO Int
251252 _ <- recvWithLength sock
@@ -273,7 +274,7 @@ testEarlyCloseSocket nextPort = do
273274 N.sClose sock
274275
275276 -- Connect to the server
276− Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr defaultConnectHints
277+ Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr True Nothing
277278
278279 -- Request a new connection, but don't wait for the response
279280 let reqId = 0 :: Int32
@@ -287,14 +288,14 @@ testEarlyCloseSocket nextPort = do
287288-- | Test the creation of a transport with an invalid address
288289testInvalidAddress :: IO N.ServiceName -> IO ()
289290testInvalidAddress nextPort = do
290− Left _ <- nextPort >>= createTransport "invalidHostName"
291+ Left _ <- nextPort >>= \port -> createTransport "invalidHostName" port defaultTCPParameters
291292 return ()
292293
293294-- | Test connecting to invalid or non-existing endpoints
294295testInvalidConnect :: IO N.ServiceName -> IO ()
295296testInvalidConnect nextPort = do
296297 port <- nextPort
297− Right transport <- createTransport "127.0.0.1" port
298+ Right transport <- createTransport "127.0.0.1" port defaultTCPParameters
298299 Right endpoint <- newEndPoint transport
299300
300301 -- Syntax error in the endpoint address
@@ -322,7 +323,7 @@ testIgnoreCloseSocket :: IO N.ServiceName -> IO ()
322323testIgnoreCloseSocket nextPort = do
323324 serverAddr <- newEmptyMVar
324325 clientDone <- newEmptyMVar
325− Right transport <- nextPort >>= createTransport "127.0.0.1"
326+ Right transport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
326327
327328 forkTry $ server transport serverAddr
328329 forkTry $ client transport serverAddr clientDone
@@ -360,7 +361,7 @@ testIgnoreCloseSocket nextPort = do
360361 let ourAddress = address endpoint
361362
362363 -- Connect to the server
363− Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr defaultConnectHints
364+ Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr True Nothing
364365
365366 -- Request a new connection
366367 tlog "Requesting connection"
@@ -405,7 +406,7 @@ testBlockAfterCloseSocket nextPort = do
405406 clientAddr <- newEmptyMVar
406407 clientDone <- newEmptyMVar
407408 port <- nextPort
408− Right transport <- createTransport "127.0.0.1" port
409+ Right transport <- createTransport "127.0.0.1" port defaultTCPParameters
409410
410411 forkTry $ server transport serverAddr clientAddr
411412 forkTry $ client transport serverAddr clientAddr clientDone
@@ -440,7 +441,7 @@ testBlockAfterCloseSocket nextPort = do
440441 let ourAddress = address endpoint
441442
442443 -- Connect to the server
443− Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr defaultConnectHints
444+ Right (sock, ConnectionRequestAccepted) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr True Nothing
444445
445446 -- Request a new connection
446447 tlog "Requesting connection"
@@ -482,7 +483,7 @@ testUnnecessaryConnect nextPort numThreads = do
482483 serverAddr <- newEmptyMVar
483484
484485 forkTry $ do
485− Right transport <- nextPort >>= createTransport "127.0.0.1"
486+ Right transport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
486487 Right endpoint <- newEndPoint transport
487488 putMVar serverAddr (address endpoint)
488489
@@ -495,7 +496,7 @@ testUnnecessaryConnect nextPort numThreads = do
495496 dones <- replicateM numThreads $ do
496497 done <- newEmptyMVar
497498 forkTry $ do
498− Right (_, reply) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr defaultConnectHints
499+ Right (_, reply) <- readMVar serverAddr >>= \addr -> socketToEndPoint ourAddress addr True Nothing
499500 case reply of
500501 ConnectionRequestAccepted ->
501502 putMVar gotAccepted ()
@@ -516,11 +517,11 @@ testUnnecessaryConnect nextPort numThreads = do
516517-- | Test that we can create "many" transport instances
517518testMany :: IO N.ServiceName -> IO ()
518519testMany nextPort = do
519− Right masterTransport <- nextPort >>= createTransport "127.0.0.1"
520+ Right masterTransport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
520521 Right masterEndPoint <- newEndPoint masterTransport
521522
522523 replicateM_ 10 $ do
523− mTransport <- nextPort >>= createTransport "127.0.0.1"
524+ mTransport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
524525 case mTransport of
525526 Left ex -> do
526527 putStrLn $ "IOException: " ++ show ex ++ "; errno = " ++ show (ioe_errno ex)
@@ -537,7 +538,7 @@ testMany nextPort = do
537538-- | Test what happens when the transport breaks completely
538539testBreakTransport :: IO N.ServiceName -> IO ()
539540testBreakTransport nextPort = do
540− Right (transport, internals) <- nextPort >>= createTransportExposeInternals "127.0.0.1"
541+ Right (transport, internals) <- nextPort >>= \port -> createTransportExposeInternals "127.0.0.1" port defaultTCPParameters
541542 Right endpoint <- newEndPoint transport
542543
543544 killThread (transportThread internals) -- Uh oh
@@ -567,7 +568,7 @@ testReconnect nextPort = do
567568
568569 counter <- newMVar (0 :: Int)
569570
570− forkServer "127.0.0.1" serverPort 5 throwIO $ \sock -> do
571+ forkServer "127.0.0.1" serverPort 5 True throwIO $ \sock -> do
571572 -- Accept the connection
572573 Right 0 <- tryIO $ (recvInt32 sock :: IO Int)
573574 Right _ <- tryIO $ recvWithLength sock
@@ -596,7 +597,7 @@ testReconnect nextPort = do
596597
597598 -- Client
598599 forkTry $ do
599− Right transport <- nextPort >>= createTransport "127.0.0.1"
600+ Right transport <- nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters
600601 Right endpoint <- newEndPoint transport
601602 let theirAddr = encodeEndPointAddress "127.0.0.1" serverPort 0
602603
@@ -642,7 +643,7 @@ testUnidirectionalError nextPort = do
642643 serverGotPing <- newEmptyMVar
643644
644645 -- Server
645− forkServer "127.0.0.1" serverPort 5 throwIO $ \sock -> do
646+ forkServer "127.0.0.1" serverPort 5 True throwIO $ \sock -> do
646647 -- We accept connections, but when an exception occurs we don't do
647648 -- anything (in particular, we don't close the socket). This is important
648649 -- because when we shutdown one direction of the socket a recv here will
@@ -663,7 +664,7 @@ testUnidirectionalError nextPort = do
663664
664665 -- Client
665666 forkTry $ do
666− Right (transport, internals) <- nextPort >>= createTransportExposeInternals "127.0.0.1"
667+ Right (transport, internals) <- nextPort >>= \port -> createTransportExposeInternals "127.0.0.1" port defaultTCPParameters
667668 Right endpoint <- newEndPoint transport
668669 let theirAddr = encodeEndPointAddress "127.0.0.1" serverPort 0
669670
@@ -718,7 +719,7 @@ testUnidirectionalError nextPort = do
718719
719720testInvalidCloseConnection :: IO N.ServiceName -> IO ()
720721testInvalidCloseConnection nextPort = do
721− Right (transport, internals) <- nextPort >>= createTransportExposeInternals "127.0.0.1"
722+ Right (transport, internals) <- nextPort >>= \port -> createTransportExposeInternals "127.0.0.1" port defaultTCPParameters
722723 serverAddr <- newEmptyMVar
723724 clientDone <- newEmptyMVar
724725 serverDone <- newEmptyMVar
@@ -773,7 +774,7 @@ main = do
773774 , ("InvalidCloseConnection", testInvalidCloseConnection nextPort)
774775 ]
775776 -- Run the generic tests even if the TCP specific tests failed..
776− testTransport (either (Left . show) (Right) <$> nextPort >>= createTransport "127.0.0.1")
777+ testTransport (either (Left . show) (Right) <$> nextPort >>= \port -> createTransport "127.0.0.1" port defaultTCPParameters)
777778 -- ..but if the generic tests pass, still fail if the specific tests did
778779 case tcpResult of
779780 Left err -> throwIO err