@@ -1,12 +1,25 @@
1−-- | TCP transport
1+-- | TCP implementation of the transport layer.
3+-- The TCP implementation guarantees that only a single TCP connection (socket)
4+-- will be used between endpoints, provided that the addresses specified are
5+-- canonical. If /A/ connects to /B/ and reports its address as
6+-- @192.168.0.1:8080@ and /B/ subsequently connects tries to connect to /A/ as
7+-- @client1.local:http-alt@ then the transport layer will not realize that the
8+-- TCP connection can be reused.
3−module Network.Transport.TCP ( -- * Main API
9+module Network.Transport.TCP {- ( -- * Main API
4 createTransport
10 createTransport
5− , -- TCP specific functionality
11+ , -- * TCP specific functionality
6 EndPointId
12 EndPointId
7 , decodeEndPointAddress
13 , decodeEndPointAddress
8 , ControlHeader(..)
14 , ControlHeader(..)
11import Network.Transport
24import Network.Transport
12import Network.Transport.Internal.TCP ( forkServer
25import Network.Transport.Internal.TCP ( forkServer
@@ -19,13 +32,11 @@ import Network.Transport.Internal ( encodeInt32
19 , failWith
32 , failWith
20 , failWithIO
33 , failWithIO
24import qualified Network.Socket as N ( HostName
36import qualified Network.Socket as N ( HostName
25 , ServiceName
37 , ServiceName
29 , getAddrInfo
40 , getAddrInfo
31 , addrFamily
42 , addrFamily
@@ -36,7 +47,7 @@ import qualified Network.Socket as N ( HostName
36 , SocketOption(ReuseAddr)
47 , SocketOption(ReuseAddr)
39−import Network.Socket.ByteString (sendMany)
50+import Network.Socket.ByteString (sendMany)
40import Control.Concurrent (forkIO)
51import Control.Concurrent (forkIO)
41import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
52import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
42import Control.Concurrent.MVar ( MVar
53import Control.Concurrent.MVar ( MVar
@@ -50,9 +61,9 @@ import Control.Concurrent.MVar ( MVar
51import Control.Category ((>>>))
62import Control.Category ((>>>))
52import Control.Applicative ((<*>), (*>), (<$>))
63import Control.Applicative ((<*>), (*>), (<$>))
53−import Control.Monad (forever, forM_, void)
64+import Control.Monad (forM_, void, unless)
54−import Control.Monad.Error (MonadError, ErrorT, liftIO, runErrorT, throwError)
65+import Control.Monad.Error (MonadError, ErrorT(ErrorT), runErrorT, throwError)
55−import Control.Monad.IO.Class (MonadIO)
66+import Control.Monad.IO.Class (liftIO)
56import Control.Exception (IOException, bracket_)
67import Control.Exception (IOException, bracket_)
57import Data.ByteString (ByteString)
68import Data.ByteString (ByteString)
58import qualified Data.ByteString as BS (concat)
69import qualified Data.ByteString as BS (concat)
@@ -65,163 +76,445 @@ import qualified Data.IntSet as IntSet (empty, insert, delete, elems)
65import Data.Map (Map)
76import Data.Map (Map)
66import qualified Data.Map as Map (empty)
77import qualified Data.Map as Map (empty)
67import Data.IORef (newIORef, modifyIORef, readIORef)
78import Data.IORef (newIORef, modifyIORef, readIORef)
68−import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=), (^%=), (^%%=))
79+import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
69import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
80import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
84+-- The TCP transport maps multiple logical connections between A and B
85+-- (in either direction) to a single TCP connection:
87+-- > +-------+ +-------+
88+-- > | A |==========================| B |
89+-- > | |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~\ |
90+-- > | Q |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~Q |
91+-- > | \~~~|~~~~~~~~~~~~~~~~~~~~~~~~~<| |
92+-- > | |==========================| |
93+-- > +-------+ +-------+
97+-- Let /A/, /B/ be two endpoints without any connections. When /A/ wants to
98+-- connect to /B/, it locally records that it is trying to connect to /B/ and
99+-- sends a request to /B/. As part of the request /A/ sends its own endpoint
102+-- When /B/ receives the connection request it first checks if it had not
103+-- already initiated a connection request to /A/. If it had not, then it will
104+-- acknowledge the connection request and record that it has a TCP connection to
107+-- The tricky case arises when /A/ sends a connection request to /B/ and /B/
108+-- finds that it had already sent a connection request to /A/. In this case /B/
109+-- will accept the connection request from /A/ if /A/s endpoint address is
110+-- smaller (lexicographically) than /B/s, and reject it otherwise.
112+-- Upon a rejection the /A/ thread that initiated the request just needs to wait
113+-- until the /A/ thread that is dealing with /B/s connection request completes.
117+-- The TCP connection is created as soon as the first logical connection
118+-- from A to B (or B to A) is established. At this point a thread (@#@)
119+-- is spawned that listens for incoming connections from B:
121+-- > +-------+ +-------+
122+-- > | A |==========================| B |
123+-- > | |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~\ |
126+-- > | |==========================| |
127+-- > +-------+ +-------+
129+-- The question is when the TCP connection can be closed again.
130+-- Conceptually, we want to do reference counting: when there are no
131+-- logical connections left between A and B we want to close the socket
132+-- (possibly after some timeout).
134+-- However, A and B need to agree that the refcount has reached zero.
135+-- It might happen that B sends a connection request over the existing
136+-- socket at the same time that A closes its logical connection to B and
137+-- closes the socket. This will cause a failure in B (which will have to
138+-- retry) which is not caused by a network failure, which is
139+-- unfortunate. (Note that the connection request from B might succeed
140+-- even if A closes the socket.)
142+-- Instead, A will first ask B if it's okay to close the connection. If
143+-- B has already initiated a logical connection to A then it can simply
144+-- ignore this message, because any message other than 'OkayToClose'
145+-- will imply to A that the connection should not yet be closed. On the
146+-- other hand, B must guarantee that once it does send an 'OkayToClose'
147+-- message it will not reuse the socket anymore.
149+--------------------------------------------------------------------------------
150+-- Internal datatypes --
151+--------------------------------------------------------------------------------
71data TCPTransport = TCPTransport { _transportHost :: N.HostName
153data TCPTransport = TCPTransport { _transportHost :: N.HostName
72 , _transportPort :: N.ServiceName
154 , _transportPort :: N.ServiceName
73− , _transportState :: MVar TransportState
155+ , _transportState :: MVar TransportState
75−data TransportState = TransportState { _localEndPoints :: IntMap LocalEndPoint
76− , _remoteEndPoints :: Map EndPointAddress (RemoteEndPoint, RefCount)
157+data TransportState = TransportState { _localEndPoints :: IntMap LocalEndPoint }
78data LocalEndPoint = LocalEndPoint { _localAddress :: EndPointAddress
158data LocalEndPoint = LocalEndPoint { _localAddress :: EndPointAddress
79 , _localChannel :: Chan Event
159 , _localChannel :: Chan Event
80− , _localState :: MVar EndPointState
160+ , _localState :: MVar LocalState
162+data LocalState = LocalState { _nextConnectionId :: ConnectionId
163+ , _pendingCtrlRequests :: IntMap (MVar [ByteString])
164+ , _nextCtrlRequestId :: ControlRequestId
165+ , _localConnections :: Map EndPointAddress (Maybe Closing, MVar RemoteState)
82data RemoteEndPoint = RemoteEndPoint { _remoteAddress :: EndPointAddress
167data RemoteEndPoint = RemoteEndPoint { _remoteAddress :: EndPointAddress
83 , _remoteSocket :: N.Socket
168 , _remoteSocket :: N.Socket
84 , sendTo :: [ByteString] -> IO ()
169 , sendTo :: [ByteString] -> IO ()
86−data EndPointState = EndPointState { _nextConnectionId :: ConnectionId
171+data RemoteState = InvalidEndPoint (FailedWith ConnectErrorCode)
87− , _pendingCtrlRequests :: IntMap (MVar [ByteString])
172+ | ValidEndPoint !RefCount RemoteEndPoint
88− , _nextCtrlRequestId :: ControlRequestId
173+ | DeletedEndPoint
90type EndPointId = Int32
175type EndPointId = Int32
91type ControlRequestId = Int32
176type ControlRequestId = Int32
92−data RefCount = RefCount !Int
177+type RefCount = Int32
178+type Closing = MVar [ByteString]
179+type Conn = (LocalEndPoint, RemoteEndPoint)
183+ RequestConnectionId -- ^ Request a new connection ID from the remote endpoint
184+ | CloseConnection -- ^ Tell the remote endpoint we will no longer be using a connection
185+ | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
186+ | CloseSocket -- ^ Request to close the connection (see module description)
189+-- Response sent by /B/ to /A/ when /A/ tries to connect
190+data ConnectionRequestResponse =
191+ ConnectionRequestAccepted -- ^ /B/ accepts the connection
192+ | ConnectionRequestInvalidEndPoint -- ^ /A/ requested an invalid endpoint
193+ | ConnectionRequestCrossed -- ^ /A/s request crossed with a request from /B/ (see protocols)
196+--------------------------------------------------------------------------------
197+-- Top-level functionality --
198+--------------------------------------------------------------------------------
94-- | Create a TCP transport
200-- | Create a TCP transport
96−-- Hostname and service name should be "canonical" for the transport layer to
97−-- use TCP channels bidirectionally (otherwise when A connects to B and B
98−-- connects to A two socket pairs will be created that will both be used
99−-- unidirectionally, at degraded performance).
101-- TODOs: deal with hints
202-- TODOs: deal with hints
102createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
203createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
103createTransport host port = do
204createTransport host port = do
104− state <- newMVar TransportState { _localEndPoints = IntMap.empty
205+ state <- newMVar TransportState { _localEndPoints = IntMap.empty }
105− , _remoteEndPoints = Map.empty
107 let transport = TCPTransport { _transportState = state
206 let transport = TCPTransport { _transportState = state
108 , _transportHost = host
207 , _transportHost = host
109 , _transportPort = port
208 , _transportPort = port
111 tryIO $ do
210 tryIO $ do
112− forkServer host port (transportServer transport)
211+ forkServer host port $ handleConnectionRequest transport
113− return Transport { newEndPoint = tcpNewEndPoint transport }
212+ return Transport { newEndPoint = apiNewEndPoint transport }
115−-- | The transport server handles incoming connections for all endpoints
214+--------------------------------------------------------------------------------
216+--------------------------------------------------------------------------------
218+-- | Create a new endpoint
219+apiNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
220+apiNewEndPoint transport = do
221+ ourEndPoint <- createLocalEndPoint transport
222+ return . Right $ EndPoint { receive = readChan (ourEndPoint ^. localChannel)
223+ , address = ourEndPoint ^. localAddress
224+ , connect = apiConnect ourEndPoint
225+ , newMulticastGroup = return . Left $ newMulticastGroupError
226+ , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
229+ newMulticastGroupError = FailedWith NewMulticastGroupUnsupported "TCP does not support multicast"
230+ resolveMulticastGroupError = FailedWith ResolveMulticastGroupUnsupported "TCP does not support multicast"
232+-- | Connnect to an endpoint
233+apiConnect :: LocalEndPoint -- ^ Local end point
234+ -> EndPointAddress -- ^ Remote address
235+ -> Reliability -- ^ Reliability (ignored)
236+ -> IO (Either (FailedWith ConnectErrorCode) Connection)
237+apiConnect ourEndPoint theirAddress _ = runErrorT $ do
238+ theirEndPoint <- findRemoteEndPoint ourEndPoint theirAddress
239+ connId <- requestNewConnection (ourEndPoint, theirEndPoint)
240+ return $ Connection { send = apiSend theirEndPoint connId
241+ , close = apiClose (ourEndPoint, theirEndPoint) connId
244+-- | Close a connection
117−-- TODO: lots of error checking
246+-- TODO: We ignore errors during a close. Is that right?
118−transportServer :: TCPTransport -> N.Socket -> IO ()
247+apiClose :: Conn -> ConnectionId -> IO ()
119−transportServer transport sock = forever $ do
248+apiClose (ourEndPoint, theirEndPoint) connId = void . tryIO $ do
120− (clientSock, _) <- N.accept sock
249+ sendTo theirEndPoint [encodeInt32 CloseConnection, encodeInt32 connId]
121− handleConnectionRequest transport clientSock
250+ decreaseRemoteRefCount (ourEndPoint, theirEndPoint)
252+-- | Send data across a connection
254+-- TODO: we should have some per-connection state so that the user cannot send
255+-- to the connection after they closed it, or close it more than once
256+apiSend :: RemoteEndPoint -> ConnectionId -> [ByteString] -> IO (Either (FailedWith SendErrorCode) ())
257+apiSend theirEndPoint connId payload = runErrorT $
258+ failWithIO (FailedWith SendFailed . show) $
259+ sendTo theirEndPoint (encodeInt32 connId : prependLength payload)
261+--------------------------------------------------------------------------------
262+-- Lower level functionality --
263+--------------------------------------------------------------------------------
265+createLocalEndPoint :: TCPTransport -> IO LocalEndPoint
266+createLocalEndPoint transport = do
268+ state <- newMVar LocalState { _nextConnectionId = firstNonReservedConnectionId
269+ , _pendingCtrlRequests = IntMap.empty
270+ , _nextCtrlRequestId = 0
271+ , _localConnections = Map.empty
273+ let host = transport ^. transportHost
274+ let port = transport ^. transportPort
275+ modifyMVar (transport ^. transportState) $ \st -> do
276+ let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
277+ let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show ix
278+ let localEndPoint = LocalEndPoint { _localAddress = addr
279+ , _localChannel = chan
280+ , _localState = state
282+ return ((localEndPointAt ix ^= Just localEndPoint) $ st, localEndPoint)
284+findRemoteEndPoint :: LocalEndPoint
286+ -> ErrorT (FailedWith ConnectErrorCode) IO RemoteEndPoint
287+findRemoteEndPoint ourEndPoint theirAddress = ErrorT $ go
290+ endPoint <- modifyMVar state $ \st ->
291+ case st ^. connectionTo theirAddress of
295+ theirMVar <- newEmptyMVar
296+ forkIO $ connectToRemoteEndPoint ourEndPoint theirMVar theirAddress
297+ return (connectionTo theirAddress ^= Just (Nothing, theirMVar) $ st, theirMVar)
298+ theirState <- readMVar endPoint
300+ InvalidEndPoint err ->
301+ -- We remove the endpoint from the state again because the next call
302+ -- to 'connect' might give a different result
303+ modifyMVar state $ \st ->
304+ return (connectionTo theirAddress ^= Nothing $ st, Left err)
305+ ValidEndPoint _ ep ->
310+ state :: MVar LocalState
311+ state = ourEndPoint ^. localState
313+connectToRemoteEndPoint :: LocalEndPoint -> MVar RemoteState -> EndPointAddress -> IO ()
314+connectToRemoteEndPoint ourEndPoint theirMVar theirAddress = do
315+ result <- runErrorT go
317+ Right (ConnectionRequestAccepted, sock) -> do
318+ theirEndPoint <- createRemoteEndPoint ourEndPoint theirAddress sock
319+ putMVar theirMVar (ValidEndPoint 0 theirEndPoint)
320+ Right (ConnectionRequestInvalidEndPoint, sock) -> do
321+ putMVar theirMVar (InvalidEndPoint (invalidAddress "Invalid endpoint"))
323+ Right (ConnectionRequestCrossed, sock) ->
326+ -- TODO: this isn't quite right; socket *might* have been opened here,
327+ -- in which case we should close it
328+ putMVar theirMVar (InvalidEndPoint err)
330+ go :: ErrorT (FailedWith ConnectErrorCode) IO (ConnectionRequestResponse, N.Socket)
332+ (host, port, theirEndPointId) <- failWith (invalidAddress "Could not parse") $
333+ decodeEndPointAddress theirAddress
334+ addr:_ <- failWithIO (invalidAddress . show) $
335+ N.getAddrInfo Nothing (Just host) (Just port)
336+ sock <- failWithIO (insufficientResources . show) $
337+ N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
338+ failWithIO (failed . show) $ do
339+ N.setSocketOption sock N.ReuseAddr 1
340+ N.connect sock (N.addrAddress addr)
341+ sendMany sock (encodeInt32 theirEndPointId : prependLength [ourAddress])
342+ -- TODO: what happens when the server sends an int outside the range of ConnectionRequestResponse?
343+ response <- recvInt32 sock
344+ return (response, sock)
346+ invalidAddress, insufficientResources, failed :: String -> FailedWith ConnectErrorCode
347+ invalidAddress = FailedWith ConnectInvalidAddress
348+ insufficientResources = FailedWith ConnectInsufficientResources
349+ failed = FailedWith ConnectFailed
351+ ourAddress :: ByteString
352+ EndPointAddress ourAddress = ourEndPoint ^. localAddress
354+-- | Request a new connection
355+requestNewConnection :: Conn -> ErrorT (FailedWith ConnectErrorCode) IO ConnectionId
356+requestNewConnection (ourEndPoint, theirEndPoint) = do
357+ mcid <- failWithIO (failed . show) $ do
358+ reply <- sendRemoteRequest (ourEndPoint, theirEndPoint) RequestConnectionId
359+ decodeInt32 . BS.concat <$> takeMVar reply
362+ throwError (failed "Invalid integer")
364+ liftIO $ increaseRemoteRefCount (ourEndPoint, theirEndPoint)
367+ failed = FailedWith ConnectFailed
369+-- | Decode end point address
370+-- TODO: This uses regular expression parsing, which is nice, but unnecessary
371+decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
372+decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack bs)
374+ endPointAddressRE :: RE Char (N.HostName, N.ServiceName, EndPointId)
375+ endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
377+sendRemoteRequest :: Conn -> ControlHeader -> IO (MVar [ByteString])
378+sendRemoteRequest (ourEndPoint, theirEndPoint) header = do
379+ reply <- newEmptyMVar
380+ reqId <- modifyMVar (ourEndPoint ^. localState) $ \st -> do
381+ let reqId = st ^. nextCtrlRequestId
382+ return ((nextCtrlRequestId ^+= 1) . (pendingCtrlRequestsAt reqId ^= Just reply) $ st, reqId)
383+ sendTo theirEndPoint [encodeInt32 header, encodeInt32 reqId]
386+--------------------------------------------------------------------------------
387+-- Reference counting --
388+--------------------------------------------------------------------------------
390+increaseRemoteRefCount :: Conn -> IO ()
391+increaseRemoteRefCount (ourEndPoint, theirEndPoint) = do
392+ let theirAddr = theirEndPoint ^. remoteAddress
393+ state = ourEndPoint ^. localState
394+ -- First check if this remote endpoint is awaiting closure
395+ (mClosing, theirMVar) <- modifyMVar state $ \st -> do
396+ let Just (closure, theirMVar) = st ^. connectionTo theirAddr
397+ return (connectionTo theirAddr ^= Just (Nothing, theirMVar) $ st, (closure, theirMVar))
399+ Just mvar -> putMVar mvar []
401+ -- Increase the refcount
402+ modifyMVar_ theirMVar $ \(ValidEndPoint rc ep) ->
403+ return (ValidEndPoint (rc + 1) ep)
405+decreaseRemoteRefCount :: Conn -> IO ()
406+decreaseRemoteRefCount (ourEndPoint, theirEndPoint) = do
407+ let theirAddr = theirEndPoint ^. remoteAddress
408+ state <- readMVar (ourEndPoint ^. localState)
409+ let Just (Nothing, theirMVar) = state ^. connectionTo theirAddr
410+ -- We don't use modifyMVar here because we want to leave the MVar
411+ -- empty when refcount reaches 0
412+ -- TODO: deal with exceptions
413+ ValidEndPoint rc ep <- takeMVar theirMVar
416+ putMVar theirMVar (ValidEndPoint (rc - 1) ep)
418+ reply <- sendRemoteRequest (ourEndPoint, theirEndPoint) CloseSocket
419+ modifyMVar_ (ourEndPoint ^. localState) $ \st -> do
420+ let Just (_, mvar) = st ^. connectionTo theirAddr
421+ return (connectionTo theirAddr ^= Just (Just reply, mvar) $ st)
423+ -- Wait for the reply from the remote endpoint
424+ canCloseSocket <- takeMVar reply
425+ putStrLn $ "canCloseSocket = " ++ show canCloseSocket
427+--------------------------------------------------------------------------------
428+-- Incoming requests --
429+--------------------------------------------------------------------------------
123−-- | Set up a new TCP connection (at the request of a remote endpoint)
124handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
431handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
125handleConnectionRequest transport sock = do
432handleConnectionRequest transport sock = do
126 request <- tryIO $ do
433 request <- tryIO $ do
127 ourEndPointId <- recvInt32 sock
434 ourEndPointId <- recvInt32 sock
128 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
435 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
129− ourEndPoint <- lookupEndPoint transport ourEndPointId
436+ ourEndPoint <- (^. localEndPointAt ourEndPointId) <$> readMVar state
130− return (ourEndPoint, theirAddress)
437+ return (theirAddress, ourEndPoint)
131 case request of
438 case request of
132− Right (ourEndPoint, theirAddress) -> do
439+ Right (_, Nothing) -> do
133− -- Send acknowledgement
440+ sendMany sock [encodeInt32 ConnectionRequestInvalidEndPoint]
442+ Right (theirAddress, Just ourEndPoint) -> do
443+ (crossed, mvar) <- modifyMVar (ourEndPoint ^. localState) $ \st ->
444+ case st ^. connectionTo theirAddress of
446+ mvar <- newEmptyMVar
447+ return (connectionTo theirAddress ^= Just (Nothing, mvar) $ st, (False, mvar))
449+ return (st, (ourEndPoint ^. localAddress <= theirAddress, mvar))
134− sendMany sock [encodeInt32 ValidEndPoint]
452+ sendMany sock [encodeInt32 ConnectionRequestCrossed]
135− -- We ignore the return value of 'forkRemoteEndPoint' here because _they_ are
453+ N.sClose sock
136− -- connecting to _us_. 'forkRemoteEndPoint' will have stored the remote endpoint
454+ else do
137− -- in the transport state, however, in case _we_ later connect to _them_.
455+ theirEndPoint <- createRemoteEndPoint ourEndPoint theirAddress sock
138− void $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
456+ putMVar mvar (ValidEndPoint 0 theirEndPoint)
457+ sendMany sock [encodeInt32 ConnectionRequestAccepted]
139− Left _ ->
458+ Left _ ->
140 -- Invalid request
459 -- Invalid request
141 N.sClose sock
460 N.sClose sock
143−-- | Look up a local endpoint
462+ where
144−--
463+ state :: MVar TransportState
145−-- Throws an I/O exception if the endpoint cannot be found (this makes it easy to integrate
146−-- this with I/O exceptions that arise from socket reads)
147−lookupEndPoint :: TCPTransport -> EndPointId -> IO LocalEndPoint
148−lookupEndPoint transport endPointId = do
149− mep <- (^. localEndPointAt endPointId) <$> readMVar (transport ^. transportState)
464+ state = transport ^. transportState
151− Nothing -> fail "Endpoint not found"
466+createRemoteEndPoint :: LocalEndPoint -> EndPointAddress -> N.Socket -> IO RemoteEndPoint
152− Just ep -> return ep
154−-- | Set up a new remote endpoint (either because we connected to a
155−-- new remote endpoint, or because a remote endpoint connected to us)
156−forkRemoteEndPoint :: TCPTransport
160− -> IO RemoteEndPoint
161−forkRemoteEndPoint transport ourEndPoint theirAddress sock = do
467+createRemoteEndPoint ourEndPoint theirAddress sock = do
162 lock <- newMVar ()
468 lock <- newMVar ()
163− let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
469+ let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
164− , _remoteSocket = sock
470+ , _remoteSocket = sock
165− , sendTo = bracket_ (takeMVar lock) (putMVar lock ())
471+ , sendTo = bracket_ (takeMVar lock) (putMVar lock ())
166− . sendMany sock
472+ . sendMany sock
168− modifyMVar_ (transport ^. transportState) $
169− return . (remoteEndPointAt theirAddress ^= Just (theirEndPoint, RefCount 0))
170 forkIO $ do
474 forkIO $ do
171− unclosedConnections <- handleIncomingMessages transport ourEndPoint theirEndPoint
475+ unclosedConnections <- handleIncomingMessages (ourEndPoint, theirEndPoint)
172 forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $
476 forM_ (map fromIntegral $ IntSet.elems unclosedConnections) $
173 writeChan (ourEndPoint ^. localChannel) . ConnectionClosed
477 writeChan (ourEndPoint ^. localChannel) . ConnectionClosed
174− -- TODO: should we close the socket here? (forkRemoteEndPoint gets called from multiple places)
175− -- TODO: and should we remove it from the transaction state?
176 return theirEndPoint
478 return theirEndPoint
180− ValidEndPoint -- ^ Acknowledgement sent when setting up a new TCP connection
181− | RequestConnectionId -- ^ Request a new connection ID from the remote endpoint
182− | CloseConnection -- ^ Tell the remote endpoint we will no longer be using a connection
183− | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
186-- | Handle requests from a remote endpoint.
480-- | Handle requests from a remote endpoint.
188-- Returns only if the remote party closes the socket or if an error occurs.
482-- Returns only if the remote party closes the socket or if an error occurs.
189-- The return value is the list of connections that are still open (normally,
483-- The return value is the list of connections that are still open (normally,
190-- this should be an empty list).
484-- this should be an empty list).
191−handleIncomingMessages :: TCPTransport -> LocalEndPoint -> RemoteEndPoint -> IO IntSet
485+handleIncomingMessages :: Conn -> IO IntSet
192−handleIncomingMessages transport ourEndPoint theirEndPoint = do
486+handleIncomingMessages (ourEndPoint, theirEndPoint) = do
193− tlog $ "Handling incoming messages from " ++ show (theirEndPoint ^. remoteAddress)
194 -- We use an IORef rather than the state monad because when an exception occurs we
487 -- We use an IORef rather than the state monad because when an exception occurs we
195 -- want to know the current list of open connections
488 -- want to know the current list of open connections
196 openConnections <- newIORef IntSet.empty
489 openConnections <- newIORef IntSet.empty
198− let -- The main loop terminates only when an exception occurs
491+ let go :: IO ()
199− mainLoop :: IO ()
492+ go = do
200− mainLoop = forever $ do
201 connId <- recvInt32 sock
493 connId <- recvInt32 sock
202 if connId >= firstNonReservedConnectionId
494 if connId >= firstNonReservedConnectionId
203 then readMessage connId
495 then readMessage connId
205 reqId <- recvInt32 sock
497 reqId <- recvInt32 sock
206 case toEnum (fromIntegral connId) of
498 case toEnum (fromIntegral connId) of
207− ValidEndPoint -> fail "Unexpected control header"
208 RequestConnectionId -> createNewConnection reqId
499 RequestConnectionId -> createNewConnection reqId
209 ControlResponse -> readControlResponse reqId
500 ControlResponse -> readControlResponse reqId
210 CloseConnection -> closeConnection reqId
501 CloseConnection -> closeConnection reqId
502+ CloseSocket -> closeSocket reqId
212 -- Create a new connection
504 -- Create a new connection
213 createNewConnection :: ControlRequestId -> IO ()
505 createNewConnection :: ControlRequestId -> IO ()
214 createNewConnection reqId = do
506 createNewConnection reqId = do
215− increaseRemoteRefCount transport theirEndPoint
507+ increaseRemoteRefCount (ourEndPoint, theirEndPoint)
216 newId <- getNextConnectionId ourEndPoint
508 newId <- getNextConnectionId ourEndPoint
217 sendTo theirEndPoint ( encodeInt32 ControlResponse
509 sendTo theirEndPoint ( encodeInt32 ControlResponse
218− : encodeInt32 reqId
510+ : encodeInt32 reqId
219− : prependLength [encodeInt32 newId]
511+ : prependLength [encodeInt32 newId]
221− writeChan ourChannel (ConnectionOpened newId ReliableOrdered (theirEndPoint ^. remoteAddress))
513+ writeChan ourChannel (ConnectionOpened newId ReliableOrdered theirAddr)
222 -- We add the new connection ID to the list of open connections only once the
514 -- We add the new connection ID to the list of open connections only once the
223 -- endpoint has been notified of the new connection (sendMany may fail)
515 -- endpoint has been notified of the new connection (sendMany may fail)
224 modifyIORef openConnections (IntSet.insert (fromIntegral newId))
516 modifyIORef openConnections (IntSet.insert (fromIntegral newId))
226 -- Read a control response
519 -- Read a control response
227 readControlResponse :: ControlRequestId -> IO ()
520 readControlResponse :: ControlRequestId -> IO ()
@@ -229,27 +522,56 @@ handleIncomingMessages transport ourEndPoint theirEndPoint = do
229 response <- recvWithLength sock
522 response <- recvWithLength sock
230 mmvar <- (^. pendingCtrlRequestsAt reqId) <$> readMVar ourState
523 mmvar <- (^. pendingCtrlRequestsAt reqId) <$> readMVar ourState
231 case mmvar of
524 case mmvar of
232− Nothing -> return () -- Invalid request ID. TODO: We just ignore it?
526+ return () -- Invalid request ID. TODO: We just ignore it?
528+ modifyMVar_ ourState $ return . (pendingCtrlRequestsAt reqId ^= Nothing)
233− Just mvar -> putMVar mvar response
529+ putMVar mvar response
235 -- Close a connection (in this case the reqId is abused for the connection ID)
532 -- Close a connection (in this case the reqId is abused for the connection ID)
236 closeConnection :: ConnectionId -> IO ()
533 closeConnection :: ConnectionId -> IO ()
237 closeConnection cid = do
534 closeConnection cid = do
238 writeChan ourChannel (ConnectionClosed cid)
535 writeChan ourChannel (ConnectionClosed cid)
239− decreaseRemoteRefCount transport theirEndPoint
536+ decreaseRemoteRefCount (ourEndPoint, theirEndPoint)
240 modifyIORef openConnections (IntSet.delete (fromIntegral cid))
537 modifyIORef openConnections (IntSet.delete (fromIntegral cid))
540+ -- Close the socket (if we don't have any outgoing connections)
541+ closeSocket :: ControlRequestId -> IO ()
542+ closeSocket cid = do
543+ let ok = encodeInt32 ControlResponse : encodeInt32 cid : prependLength [encodeInt32 (1 :: Int32)]
544+ didClose <- modifyMVar (ourEndPoint ^. localState) $ \st -> do
545+ case st ^. connectionTo theirAddr of
546+ Just (Just _, _) -> do
547+ sendTo theirEndPoint ok
550+ Just (Nothing, theirMVar) -> do
551+ ValidEndPoint rc _ <- readMVar theirMVar
554+ sendTo theirEndPoint ok
560+ error "Internal error"
242 -- Read a message and output it on the endPoint's channel
563 -- Read a message and output it on the endPoint's channel
243 readMessage :: ConnectionId -> IO ()
564 readMessage :: ConnectionId -> IO ()
244− readMessage connId = recvWithLength sock >>= writeChan ourChannel . Received connId
565+ readMessage connId = recvWithLength sock >>= writeChan ourChannel . Received connId >> go
246 -- Breakdown of the arguments
567 -- Breakdown of the arguments
247 ourChannel = ourEndPoint ^. localChannel
568 ourChannel = ourEndPoint ^. localChannel
248 ourState = ourEndPoint ^. localState
569 ourState = ourEndPoint ^. localState
249 sock = theirEndPoint ^. remoteSocket
570 sock = theirEndPoint ^. remoteSocket
571+ theirAddr = theirEndPoint ^. remoteAddress
251 -- We keep handling messages until an exception occurs
573 -- We keep handling messages until an exception occurs
252− tryIO mainLoop
574+ tryIO go
253 readIORef openConnections
575 readIORef openConnections
255-- | Get the next connection ID
577-- | Get the next connection ID
@@ -257,162 +579,17 @@ getNextConnectionId :: LocalEndPoint -> IO ConnectionId
257getNextConnectionId endpoint =
579getNextConnectionId endpoint =
258 modifyMVar (endpoint ^. localState) $ \st ->
580 modifyMVar (endpoint ^. localState) $ \st ->
259 return (nextConnectionId ^+= 1 $ st, st ^. nextConnectionId)
581 return (nextConnectionId ^+= 1 $ st, st ^. nextConnectionId)
261−-- | Create a new endpoint
583+--------------------------------------------------------------------------------
262−tcpNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
584+-- Constants --
263−tcpNewEndPoint transport = do
585+--------------------------------------------------------------------------------
264− endPoint <- modifyMVar (transport ^. transportState) $ \st -> do
266− state <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId
267− , _pendingCtrlRequests = IntMap.empty
268− , _nextCtrlRequestId = 0
270− let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
271− let host = transport ^. transportHost
272− let port = transport ^. transportPort
273− let addr = EndPointAddress . BSC.pack $ host ++ ":" ++ port ++ ":" ++ show ix
274− let localEndPoint = LocalEndPoint { _localAddress = addr
275− , _localChannel = chan
276− , _localState = state
278− return ((localEndPointAt ix ^= Just localEndPoint) $ st, localEndPoint)
279− return . Right $ EndPoint { receive = readChan (endPoint ^. localChannel)
280− , address = endPoint ^. localAddress
281− , connect = tcpConnect transport endPoint
282− , newMulticastGroup = return . Left $ newMulticastGroupError
283− , resolveMulticastGroup = \_ -> return . Left $ resolveMulticastGroupError
286− newMulticastGroupError = FailedWith NewMulticastGroupUnsupported "TCP does not support multicast"
287− resolveMulticastGroupError = FailedWith ResolveMulticastGroupUnsupported "TCP does not support multicast"
289-- | We reserve a bunch of connection IDs for control messages
587-- | We reserve a bunch of connection IDs for control messages
290firstNonReservedConnectionId :: ConnectionId
588firstNonReservedConnectionId :: ConnectionId
291firstNonReservedConnectionId = 1024
589firstNonReservedConnectionId = 1024
293−-- | Connnect to an endpoint
294−tcpConnect :: TCPTransport -- ^ Transport
295− -> LocalEndPoint -- ^ Local end point
296− -> EndPointAddress -- ^ Remote address
297− -> Reliability -- ^ Reliability (ignored)
298− -> IO (Either (FailedWith ConnectErrorCode) Connection)
299−tcpConnect transport ourEndPoint theirAddress _ = runErrorT $ do
300− theirEndPoint <- remoteEndPoint transport ourEndPoint theirAddress
301− increaseRemoteRefCount transport theirEndPoint
302− connId <- requestNewConnection ourEndPoint theirEndPoint
303− return Connection { send = tcpSend connId theirEndPoint
304− , close = tcpClose transport connId theirEndPoint
307−-- | Send data across a connection
309−-- TODO: should we modify send so that it returns an error when the user closes the connection?
310−tcpSend :: ConnectionId -> RemoteEndPoint -> [ByteString] -> IO (Either (FailedWith SendErrorCode) ())
311−tcpSend connId theirEndPoint payload = runErrorT $
312− failWithIO (FailedWith SendFailed . show) $
313− sendTo theirEndPoint (encodeInt32 connId : prependLength payload)
315−-- | Close a connection
317−-- We ignore errors during a close
318−tcpClose :: TCPTransport -> ConnectionId -> RemoteEndPoint -> IO ()
319−tcpClose transport connId theirEndPoint = void . runErrorT . failWithIO id $ do
320− sendTo theirEndPoint [encodeInt32 CloseConnection, encodeInt32 connId]
321− decreaseRemoteRefCount transport theirEndPoint
323−-- | Increase the refcount on a remote endpoint
324−increaseRemoteRefCount :: MonadIO m => TCPTransport -> RemoteEndPoint -> m ()
325−increaseRemoteRefCount transport endPoint = do
326− liftIO $ modifyMVar_ (transport ^. transportState) $
327− return . (remoteEndPointAt (endPoint ^. remoteAddress) ^%= go)
329− go :: Maybe (RemoteEndPoint, RefCount) -> Maybe (RemoteEndPoint, RefCount)
330− go (Just (ep, RefCount rc)) = (Just (ep, RefCount $ rc + 1))
331− go Nothing = Nothing
333−-- | Decrease the refcount on a remote endpoint and close the socket when it reaches zero.
334−decreaseRemoteRefCount :: MonadIO m => TCPTransport -> RemoteEndPoint -> m ()
335−decreaseRemoteRefCount transport endPoint = do
336− liftIO $ modifyMVar_ (transport ^. transportState) $
337− (remoteEndPointAt (endPoint ^. remoteAddress) ^%%= go)
339− go :: Maybe (RemoteEndPoint, RefCount) -> IO (Maybe (RemoteEndPoint, RefCount))
340− go (Just (_, RefCount 1)) = do tlog $ "Closing socket to " ++ show (endPoint ^. remoteAddress)
341− N.sClose (endPoint ^. remoteSocket)
343− go (Just (ep, RefCount rc)) = return (Just (ep, RefCount $ rc - 1))
344− go Nothing = return Nothing
346−-- | Request a new connection
347−requestNewConnection :: (MonadIO m, MonadError (FailedWith ConnectErrorCode) m)
351−requestNewConnection ourEndPoint theirEndPoint = do
352− mcid <- failWithIO (failed . show) $ do
353− reply <- newEmptyMVar
354− reqId <- modifyMVar state $ \st -> do
355− let reqId = st ^. nextCtrlRequestId
356− return ((nextCtrlRequestId ^+= 1) . (pendingCtrlRequestsAt reqId ^= Just reply) $ st, reqId)
357− sendTo theirEndPoint [encodeInt32 RequestConnectionId, encodeInt32 reqId]
358− response <- decodeInt32 . BS.concat <$> takeMVar reply
359− modifyMVar_ state $ return . (pendingCtrlRequestsAt reqId ^= Nothing)
362− Nothing -> throwError (failed "Invalid integer")
363− Just cid -> return cid
365− failed = FailedWith ConnectFailed
366− state = ourEndPoint ^. localState
368−-- | Find a remote endpoint
371−remoteEndPoint :: TCPTransport
374− -> ErrorT (FailedWith ConnectErrorCode) IO RemoteEndPoint
375−remoteEndPoint transport ourEndPoint theirAddress = do
376− mendpoint <- liftIO $ (^. remoteEndPointAt theirAddress) <$> readMVar (transport ^. transportState)
378− Just (endpoint, _) ->
381− -- Create a new connection
382− liftIO . tlog $ "Creating socket from " ++ show ourAddress ++ " to " ++ show theirAddress
383− (host, port, endPointId) <- failWith (invalidAddress "Could not parse") $
384− decodeEndPointAddress theirAddress
385− addr:_ <- failWithIO (invalidAddress . show) $
386− N.getAddrInfo Nothing (Just host) (Just port)
387− sock <- failWithIO (insufficientResources . show) $
388− N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
389− liftIO $ N.setSocketOption sock N.ReuseAddr 1
390− failWithIO (failed . show) $ N.connect sock (N.addrAddress addr)
391− -- Send the endpoint ID we're interested in and our own address
392− failWithIO (failed . show) $ sendMany sock (encodeInt32 endPointId : prependLength [ourAddress])
393− -- For the remote endpoint to acknowledge
394− -- We fail with 'invalidAddress' here because the remote endpoint will close
395− -- the connection if we send an invalid endpoint identifier
396− ack <- failWithIO (invalidAddress . show) $ recvInt32 sock
398− ValidEndPoint -> liftIO $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
399− _ -> throwError (invalidAddress "Unexpected response")
401− invalidAddress = FailedWith ConnectInvalidAddress
402− insufficientResources = FailedWith ConnectInsufficientResources
403− failed = FailedWith ConnectFailed
404− EndPointAddress ourAddress = ourEndPoint ^. localAddress
406−-- | Decode end point address
407−-- TODO: This uses regular expression parsing, which is nice, but unnecessary
408−decodeEndPointAddress :: EndPointAddress -> Maybe (N.HostName, N.ServiceName, EndPointId)
409−decodeEndPointAddress (EndPointAddress bs) = match endPointAddressRE (BSC.unpack bs)
411− endPointAddressRE :: RE Char (N.HostName, N.ServiceName, EndPointId)
412− endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
414--------------------------------------------------------------------------------
591--------------------------------------------------------------------------------
415−-- Lens definitions
592+-- Lens definitions --
416--------------------------------------------------------------------------------
593--------------------------------------------------------------------------------
418transportState :: Lens TCPTransport (MVar TransportState)
595transportState :: Lens TCPTransport (MVar TransportState)
@@ -427,40 +604,38 @@ transportPort = lens _transportPort (\port trans -> trans { _transportPort = por
427localEndPoints :: Lens TransportState (IntMap LocalEndPoint)
604localEndPoints :: Lens TransportState (IntMap LocalEndPoint)
428localEndPoints = lens _localEndPoints (\es st -> st { _localEndPoints = es })
605localEndPoints = lens _localEndPoints (\es st -> st { _localEndPoints = es })
430−remoteEndPoints :: Lens TransportState (Map EndPointAddress (RemoteEndPoint, RefCount))
431−remoteEndPoints = lens _remoteEndPoints (\es st -> st { _remoteEndPoints = es })
433localAddress :: Lens LocalEndPoint EndPointAddress
607localAddress :: Lens LocalEndPoint EndPointAddress
434localAddress = lens _localAddress (\addr ep -> ep { _localAddress = addr })
608localAddress = lens _localAddress (\addr ep -> ep { _localAddress = addr })
436localChannel :: Lens LocalEndPoint (Chan Event)
610localChannel :: Lens LocalEndPoint (Chan Event)
437localChannel = lens _localChannel (\ch ep -> ep { _localChannel = ch })
611localChannel = lens _localChannel (\ch ep -> ep { _localChannel = ch })
439−localState :: Lens LocalEndPoint (MVar EndPointState)
613+localState :: Lens LocalEndPoint (MVar LocalState)
440localState = lens _localState (\st ep -> ep { _localState = st })
614localState = lens _localState (\st ep -> ep { _localState = st })
442−remoteAddress :: Lens RemoteEndPoint EndPointAddress
616+pendingCtrlRequests :: Lens LocalState (IntMap (MVar [ByteString]))
443−remoteAddress = lens _remoteAddress (\addr ep -> ep { _remoteAddress = addr })
617+pendingCtrlRequests = lens _pendingCtrlRequests (\rep st -> st { _pendingCtrlRequests = rep })
445−remoteSocket :: Lens RemoteEndPoint N.Socket
619+nextCtrlRequestId :: Lens LocalState ControlRequestId
446−remoteSocket = lens _remoteSocket (\sock ep -> ep { _remoteSocket = sock })
620+nextCtrlRequestId = lens _nextCtrlRequestId (\cid st -> st { _nextCtrlRequestId = cid })
448−nextConnectionId :: Lens EndPointState ConnectionId
622+nextConnectionId :: Lens LocalState ConnectionId
449nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
623nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
451−pendingCtrlRequests :: Lens EndPointState (IntMap (MVar [ByteString]))
625+localConnections :: Lens LocalState (Map EndPointAddress (Maybe Closing, MVar RemoteState))
452−pendingCtrlRequests = lens _pendingCtrlRequests (\rep st -> st { _pendingCtrlRequests = rep })
626+localConnections = lens _localConnections (\cs st -> st { _localConnections = cs })
454−nextCtrlRequestId :: Lens EndPointState ControlRequestId
628+remoteAddress :: Lens RemoteEndPoint EndPointAddress
455−nextCtrlRequestId = lens _nextCtrlRequestId (\cid st -> st { _nextCtrlRequestId = cid })
629+remoteAddress = lens _remoteAddress (\addr ep -> ep { _remoteAddress = addr })
631+remoteSocket :: Lens RemoteEndPoint N.Socket
632+remoteSocket = lens _remoteSocket (\sock ep -> ep { _remoteSocket = sock })
457localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
634localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
458localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
635localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
460−remoteEndPointAt :: EndPointAddress -> Lens TransportState (Maybe (RemoteEndPoint, RefCount))
461−remoteEndPointAt addr = remoteEndPoints >>> mapLens addr
463−pendingCtrlRequestsAt :: ControlRequestId -> Lens EndPointState (Maybe (MVar [ByteString]))
637+pendingCtrlRequestsAt :: ControlRequestId -> Lens LocalState (Maybe (MVar [ByteString]))
464pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
638pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)
466−
640+connectionTo :: EndPointAddress -> Lens LocalState (Maybe (Maybe Closing, MVar RemoteState))
641+connectionTo addr = localConnections >>> mapLens addr