git.iko.soy/distributed-process
on master help sign in
Cleanup
78147beEdsko de Vries
network-transport-2/network-transport.cabal+2−1
@@ -18,7 +18,8 @@ Library
18 transformers
18 transformers
19 Exposed-modules: Network.Transport,
19 Exposed-modules: Network.Transport,
20 Network.Transport.Chan,
20 Network.Transport.Chan,
21− Network.Transport.TCP
21+ Network.Transport.TCP,
22+ Network.Transport.Util
22 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
23 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
23 ghc-options: -Wall -fno-warn-unused-do-bind
24 ghc-options: -Wall -fno-warn-unused-do-bind
24 HS-Source-Dirs: src
25 HS-Source-Dirs: src
network-transport-2/src/Network/Transport.hs+58−65
@@ -9,8 +9,6 @@ module Network.Transport ( -- * Types
9 , MulticastGroup(..)
9 , MulticastGroup(..)
10 , EndPointAddress(..)
10 , EndPointAddress(..)
11 , MulticastAddress(..)
11 , MulticastAddress(..)
12− -- * Utility functions
13− , spawn
14 -- * Error codes
12 -- * Error codes
15 , FailedWith(..)
13 , FailedWith(..)
16 , NewEndPointErrorCode
14 , NewEndPointErrorCode
@@ -21,11 +19,13 @@ module Network.Transport ( -- * Types
21 ) where
19 ) where
22
20
23import Data.ByteString (ByteString)
21import Data.ByteString (ByteString)
24−import Control.Concurrent (forkIO)
25−import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
26import Control.Monad.Error (Error(..))
22import Control.Monad.Error (Error(..))
27import Data.Int (Int32)
23import Data.Int (Int32)
28
24
25+--------------------------------------------------------------------------------
26+-- Main API --
27+--------------------------------------------------------------------------------
28+
29-- | To create a network abstraction layer, use one of the
29-- | To create a network abstraction layer, use one of the
30-- @Network.Transport.*@ packages.
30-- @Network.Transport.*@ packages.
31data Transport = Transport {
31data Transport = Transport {
@@ -33,46 +33,6 @@ data Transport = Transport {
33 newEndPoint :: IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
33 newEndPoint :: IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
34 }
34 }
35
35
36−-- | EndPointAddress of an endpoint.
37−newtype EndPointAddress = EndPointAddress ByteString
38− deriving (Show, Eq, Ord)
39−
40−-- | EndPointAddress of a multicast group.
41−newtype MulticastAddress = MulticastAddress ByteString
42− deriving (Show, Eq, Ord)
43−
44−-- | Errors during the creation of an endpoint (currently, there are none)
45−data NewEndPointErrorCode
46−
47−-- | Connection failure
48−data ConnectErrorCode =
49− ConnectInvalidAddress -- ^ Could not parse or resolve the address
50− | ConnectInsufficientResources -- ^ Insufficient resources (for instance, no more sockets available)
51− | ConnectFailed -- ^ Failed for other reasons
52− deriving Show
53−
54−-- | Failure during the creation of a new multicast group
55−data NewMulticastGroupErrorCode =
56− NewMulticastGroupUnsupported
57− deriving Show
58−
59−-- | Failure during the resolution of a multicast group
60−data ResolveMulticastGroupErrorCode =
61− ResolveMulticastGroupNotFound
62− | ResolveMulticastGroupUnsupported
63− deriving Show
64−
65−-- | Failure during sending a message
66−data SendErrorCode =
67− SendFailed
68− deriving Show
69−
70−data FailedWith error = FailedWith error String
71− deriving Show
72−
73−instance Error (FailedWith error) where
74− strMsg = FailedWith undefined
75−
76-- | Network endpoint.
36-- | Network endpoint.
77data EndPoint = EndPoint {
37data EndPoint = EndPoint {
78 -- | Endpoints have a single shared receive queue.
38 -- | Endpoints have a single shared receive queue.
@@ -87,16 +47,6 @@ data EndPoint = EndPoint {
87 , resolveMulticastGroup :: MulticastAddress -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
47 , resolveMulticastGroup :: MulticastAddress -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
88 }
48 }
89
49
90−-- | Reliability guarantees of a connection.
91−data Reliability =
92− ReliableOrdered
93− | ReliableUnordered
94− | Unreliable
95− deriving Show
96−
97−-- | Connection IDs enable receivers to distinguish one connection from another.
98−type ConnectionId = Int32
99−
100-- | Lightweight connection to an endpoint.
50-- | Lightweight connection to an endpoint.
101data Connection = Connection {
51data Connection = Connection {
102 -- | Send a message on this connection.
52 -- | Send a message on this connection.
@@ -113,6 +63,16 @@ data Event =
113 | ReceivedMulticast MulticastAddress [ByteString]
63 | ReceivedMulticast MulticastAddress [ByteString]
114 deriving Show
64 deriving Show
115
65
66+-- | Connection IDs enable receivers to distinguish one connection from another.
67+type ConnectionId = Int32
68+
69+-- | Reliability guarantees of a connection.
70+data Reliability =
71+ ReliableOrdered
72+ | ReliableUnordered
73+ | Unreliable
74+ deriving Show
75+
116-- | Multicast group.
76-- | Multicast group.
117data MulticastGroup = MulticastGroup {
77data MulticastGroup = MulticastGroup {
118 -- | EndPointAddress of the multicast group.
78 -- | EndPointAddress of the multicast group.
@@ -131,14 +91,47 @@ data MulticastGroup = MulticastGroup {
131 , multicastClose :: IO ()
91 , multicastClose :: IO ()
132 }
92 }
133
93
134−-- | Fork a new thread, create a new end point on that thread, and run the specified IO operation on that thread.
94+-- | EndPointAddress of an endpoint.
135−--
95+newtype EndPointAddress = EndPointAddress { endPointAddressToByteString :: ByteString }
136−-- Returns the address of the new end point.
96+ deriving (Show, Eq, Ord)
137−spawn :: Transport -> (EndPoint -> IO ()) -> IO EndPointAddress
97+
138−spawn transport proc = do
98+-- | EndPointAddress of a multicast group.
139− addr <- newEmptyMVar
99+newtype MulticastAddress = MulticastAddress { multicastAddressToByteString :: ByteString }
140− forkIO $ do
100+ deriving (Show, Eq, Ord)
141− Right endpoint <- newEndPoint transport
101+
142− putMVar addr (address endpoint)
102+--------------------------------------------------------------------------------
143− proc endpoint
103+-- Error codes --
144− takeMVar addr
104+--------------------------------------------------------------------------------
105+
106+data FailedWith error = FailedWith error String
107+ deriving Show
108+
109+instance Error (FailedWith error) where
110+ strMsg = FailedWith undefined
111+
112+-- | Errors during the creation of an endpoint (currently, there are none)
113+data NewEndPointErrorCode
114+
115+-- | Connection failure
116+data ConnectErrorCode =
117+ ConnectInvalidAddress -- ^ Could not parse or resolve the address
118+ | ConnectInsufficientResources -- ^ Insufficient resources (for instance, no more sockets available)
119+ | ConnectFailed -- ^ Failed for other reasons
120+ deriving Show
121+
122+-- | Failure during the creation of a new multicast group
123+data NewMulticastGroupErrorCode =
124+ NewMulticastGroupUnsupported
125+ deriving Show
126+
127+-- | Failure during the resolution of a multicast group
128+data ResolveMulticastGroupErrorCode =
129+ ResolveMulticastGroupNotFound
130+ | ResolveMulticastGroupUnsupported
131+ deriving Show
132+
133+-- | Failure during sending a message
134+data SendErrorCode =
135+ SendFailed
136+ | SendConnectionClosed
137+ deriving Show
network-transport-2/src/Network/Transport/TCP.hs+59−40
@@ -14,12 +14,7 @@ module Network.Transport.TCP ( -- * Main API
14 , ControlHeader(..)
14 , ControlHeader(..)
15 , ConnectionRequestResponse(..)
15 , ConnectionRequestResponse(..)
16 -- * Design notes
16 -- * Design notes
17− -- ** Overview
17+ -- $design
18− -- $overview
19− -- ** Connecting
20− -- $connecting
21− -- ** Disconnecting
22− -- $disconnecting
23 ) where
18 ) where
24
19
25import Network.Transport
20import Network.Transport
@@ -80,10 +75,12 @@ import Data.IORef (newIORef, modifyIORef, readIORef)
80import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
75import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
81import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
76import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
82
77
83−-- $overview
78+-- $design
84--
79--
80+-- [Goals]
81+--
85−-- The TCP transport maps multiple logical connections between A and B
82+-- The TCP transport maps multiple logical connections between /A/ and /B/ (in
86−-- (in either direction) to a single TCP connection:
83+-- either direction) to a single TCP connection:
87--
84--
88-- > +-------+ +-------+
85-- > +-------+ +-------+
89-- > | A |==========================| B |
86-- > | A |==========================| B |
@@ -92,32 +89,41 @@ import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
92-- > | \~~~|~~~~~~~~~~~~~~~~~~~~~~~~~<| |
89-- > | \~~~|~~~~~~~~~~~~~~~~~~~~~~~~~<| |
93-- > | |==========================| |
90-- > | |==========================| |
94-- > +-------+ +-------+
91-- > +-------+ +-------+
95−
96−-- $connecting
97--
92--
93+-- Ignoring the complications detailed below, the TCP connection is created is
94+-- created the first lightweight connection is created (in either direction),
95+-- and torn down when the last lightweight connection (in either direction) is
96+-- closed.
97+--
98+-- [Connecting]
99+--
98-- Let /A/, /B/ be two endpoints without any connections. When /A/ wants to
100-- Let /A/, /B/ be two endpoints without any connections. When /A/ wants to
99-- connect to /B/, it locally records that it is trying to connect to /B/ and
101-- connect to /B/, it locally records that it is trying to connect to /B/ and
100-- sends a request to /B/. As part of the request /A/ sends its own endpoint
102-- sends a request to /B/. As part of the request /A/ sends its own endpoint
101−-- address to /B/.
103+-- address to /B/ (so that /B/ can reuse the connection in the other direction).
102--
104--
103-- When /B/ receives the connection request it first checks if it had not
105-- When /B/ receives the connection request it first checks if it had not
104-- already initiated a connection request to /A/. If it had not, then it will
106-- already initiated a connection request to /A/. If it had not, then it will
107+-- acknowledge the connection request by sending 'ConnectionRequestAccepted' to
105−-- acknowledge the connection request and record that it has a TCP connection to
108+-- /A/ and record that it has a TCP connection to /A/.
106−-- /A/.
107--
109--
108-- The tricky case arises when /A/ sends a connection request to /B/ and /B/
110-- The tricky case arises when /A/ sends a connection request to /B/ and /B/
109-- finds that it had already sent a connection request to /A/. In this case /B/
111-- finds that it had already sent a connection request to /A/. In this case /B/
110-- will accept the connection request from /A/ if /A/s endpoint address is
112-- will accept the connection request from /A/ if /A/s endpoint address is
111−-- smaller (lexicographically) than /B/s, and reject it otherwise.
113+-- smaller (lexicographically) than /B/s, and reject it otherwise. If it rejects
114+-- it, it sends a 'ConnectionRequestCrossed' message to /A/. (The
115+-- lexicographical ordering is an arbitrary but convenient way to break the
116+-- tie.)
112--
117--
118+-- When it receives a 'ConnectionRequestCrossed' message the /A/ thread that
113−-- Upon a rejection the /A/ thread that initiated the request just needs to wait
119+-- initiated the request just needs to wait until the /A/ thread that is dealing
114−-- until the /A/ thread that is dealing with /B/s connection request completes.
120+-- with /B/'s connection request completes.
115−
121+--
116−-- $disconnecting
122+-- [Disconnecting]
117--
123--
118−-- The TCP connection is created as soon as the first logical connection
124+-- The TCP connection is created as soon as the first logical connection from
119−-- from A to B (or B to A) is established. At this point a thread (@#@)
125+-- /A/ to /B/ (or /B/ to /A/) is established. At this point a thread (@#@) is
120−-- is spawned that listens for incoming connections from B:
126+-- spawned that listens for incoming connections from /B/:
121--
127--
122-- > +-------+ +-------+
128-- > +-------+ +-------+
123-- > | A |==========================| B |
129-- > | A |==========================| B |
@@ -127,25 +133,37 @@ import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
127-- > | |==========================| |
133-- > | |==========================| |
128-- > +-------+ +-------+
134-- > +-------+ +-------+
129--
135--
130−-- The question is when the TCP connection can be closed again.
136+-- The question is when the TCP connection can be closed again. Conceptually,
131−-- Conceptually, we want to do reference counting: when there are no
137+-- we want to do reference counting: when there are no logical connections left
132−-- logical connections left between A and B we want to close the socket
138+-- between /A/ and /B/ we want to close the socket (possibly after some
133−-- (possibly after some timeout).
139+-- timeout).
134--
140--
135−-- However, A and B need to agree that the refcount has reached zero.
141+-- However, /A/ and /B/ need to agree that the refcount has reached zero. It
136−-- It might happen that B sends a connection request over the existing
142+-- might happen that /B/ sends a connection request over the existing socket at
137−-- socket at the same time that A closes its logical connection to B and
143+-- the same time that /A/ closes its logical connection to /B/ and closes the
138−-- closes the socket. This will cause a failure in B (which will have to
144+-- socket. This will cause a failure in /B/ (which will have to retry) which is
139−-- retry) which is not caused by a network failure, which is
145+-- not caused by a network failure, which is unfortunate. (Note that the
140−-- unfortunate. (Note that the connection request from B might succeed
146+-- connection request from /B/ might succeed even if /A/ closes the socket.)
141−-- even if A closes the socket.)
142--
147--
143−-- Instead, A will first ask B if it's okay to close the connection. If
148+-- Instead, when /A/ is ready to close the socket it sends a 'CloseSocket'
144−-- B has already initiated a logical connection to A then it can simply
149+-- request to /B/ and records that its connection to /B/ is closing. If /A/
145−-- ignore this message, because any message other than 'OkayToClose'
150+-- receives a new connection request from /B/ after having sent the
146−-- will imply to A that the connection should not yet be closed. On the
151+-- 'CloseSocket' request it simply forgets that it sent a 'CloseSocket' request
147−-- other hand, B must guarantee that once it does send an 'OkayToClose'
152+-- and increments the reference count of the connection again.
148−-- message it will not reuse the socket anymore.
153+--
154+-- When /B/ receives a 'CloseSocket' message and it too is ready to close the
155+-- connection, it will respond with a reciprocal 'CloseSocket' request to /A/
156+-- and then actually close the socket. /A/ meanwhile will not send any more
157+-- requests to /B/ after having sent a 'CloseSocket' request, and will actually
158+-- close its end of the socket only when receiving the 'CloseSocket' message
159+-- from /B/. (Since /A/ recorded that its connection to /B/ is in closing state
160+-- after sending a 'CloseSocket' request to /B/, it knows not to reciprocate /B/
161+-- reciprocal 'CloseSocket' message.)
162+--
163+-- If there is a concurrent thread in /A/ waiting to connect to /B/ after /A/
164+-- has sent a 'CloseSocket' request then this thread will block until /A/ knows
165+-- whether to reuse the old socket (if /B/ sends a new connection request
166+-- instead of acknowledging the 'CloseSocket') or to set up a new socket.
149
167
150--------------------------------------------------------------------------------
168--------------------------------------------------------------------------------
151-- Internal datatypes --
169-- Internal datatypes --
@@ -541,7 +559,8 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
541 didClose <- modifyMVar (ourEndPoint ^. localState) $ \st -> do
559 didClose <- modifyMVar (ourEndPoint ^. localState) $ \st -> do
542 case st ^. connectionTo theirAddr of
560 case st ^. connectionTo theirAddr of
543 Just (Just _, mvar) -> do
561 Just (Just _, mvar) -> do
544− sendTo theirEndPoint [encodeInt32 CloseSocket]
562+ -- The connection is already in "closing" state, which means we
563+ -- don't need to send another CloseSocket request (we already did)
545 N.sClose sock
564 N.sClose sock
546 putMVar mvar EndPointClosed
565 putMVar mvar EndPointClosed
547 return (connectionTo theirAddr ^= Nothing $ st, True)
566 return (connectionTo theirAddr ^= Nothing $ st, True)
network-transport-2/tests/TestTCP.hs+1−1
@@ -147,7 +147,7 @@ testIgnoreCloseSocket = do
147 tlog "Ignoring it, requesting another connection"
147 tlog "Ignoring it, requesting another connection"
148 let reqId' = 1 :: Int32
148 let reqId' = 1 :: Int32
149 sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId']
149 sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId']
150− response' <- replicateM 4 $ recvInt32 sock :: IO [Int32]
150+ replicateM 4 $ recvInt32 sock :: IO [Int32]
151
151
152 -- Close it again
152 -- Close it again
153 tlog "Closing connection"
153 tlog "Closing connection"
network-transport-2/tests/TestTransport.hs+1
@@ -10,6 +10,7 @@ import Control.Exception (evaluate, throw, catch, SomeException)
10import Control.Applicative ((<$>))
10import Control.Applicative ((<$>))
11import Network.Transport
11import Network.Transport
12import Network.Transport.Internal (tlog)
12import Network.Transport.Internal (tlog)
13+import Network.Transport.Util (spawn)
13import Data.ByteString (ByteString)
14import Data.ByteString (ByteString)
14import Data.ByteString.Char8 (pack)
15import Data.ByteString.Char8 (pack)
15import Data.Map (Map)
16import Data.Map (Map)