@@ -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])