git.iko.soy/distributed-process
on master help sign in
Cleanup
commit78147bef227eeaf2269881c2911682d9452dfa87 authorEdsko de Vries <edsko@edsko.net> dateWed 2 May 2012, 11:12 (14 years ago) parentb7aacf8
5 files changed+121−107
network-transport-2/network-transport.cabal+2−1 network-transport-2/src/Network/Transport.hs+58−65 network-transport-2/src/Network/Transport/TCP.hs+59−40 network-transport-2/tests/TestTCP.hs+1−1 network-transport-2/tests/TestTransport.hs+1
network-transport-2/network-transport.cabal+2−1
@@ -18,7 +18,8 @@ Library
1818 transformers
1919 Exposed-modules: Network.Transport,
2020 Network.Transport.Chan,
21− Network.Transport.TCP
21+ Network.Transport.TCP,
22+ Network.Transport.Util
2223 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
2324 ghc-options: -Wall -fno-warn-unused-do-bind
2425 HS-Source-Dirs: src
network-transport-2/src/Network/Transport.hs+58−65
@@ -9,8 +9,6 @@ module Network.Transport ( -- * Types
99 , MulticastGroup(..)
1010 , EndPointAddress(..)
1111 , MulticastAddress(..)
12− -- * Utility functions
13− , spawn
1412 -- * Error codes
1513 , FailedWith(..)
1614 , NewEndPointErrorCode
@@ -21,11 +19,13 @@ module Network.Transport ( -- * Types
2119 ) where
2220
2321import Data.ByteString (ByteString)
24−import Control.Concurrent (forkIO)
25−import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
2622import Control.Monad.Error (Error(..))
2723import Data.Int (Int32)
2824
25+--------------------------------------------------------------------------------
26+-- Main API --
27+--------------------------------------------------------------------------------
28+
2929-- | To create a network abstraction layer, use one of the
3030-- @Network.Transport.*@ packages.
3131data Transport = Transport {
@@ -33,46 +33,6 @@ data Transport = Transport {
3333 newEndPoint :: IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
3434 }
3535
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−
7636-- | Network endpoint.
7737data EndPoint = EndPoint {
7838 -- | Endpoints have a single shared receive queue.
@@ -87,16 +47,6 @@ data EndPoint = EndPoint {
8747 , resolveMulticastGroup :: MulticastAddress -> IO (Either (FailedWith ResolveMulticastGroupErrorCode) MulticastGroup)
8848 }
8949
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−
10050-- | Lightweight connection to an endpoint.
10151data Connection = Connection {
10252 -- | Send a message on this connection.
@@ -113,6 +63,16 @@ data Event =
11363 | ReceivedMulticast MulticastAddress [ByteString]
11464 deriving Show
11565
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+
11676-- | Multicast group.
11777data MulticastGroup = MulticastGroup {
11878 -- | EndPointAddress of the multicast group.
@@ -131,14 +91,47 @@ data MulticastGroup = MulticastGroup {
13191 , multicastClose :: IO ()
13292 }
13393
134−-- | Fork a new thread, create a new end point on that thread, and run the specified IO operation on that thread.
135−--
136−-- Returns the address of the new end point.
137−spawn :: Transport -> (EndPoint -> IO ()) -> IO EndPointAddress
138−spawn transport proc = do
139− addr <- newEmptyMVar
140− forkIO $ do
141− Right endpoint <- newEndPoint transport
142− putMVar addr (address endpoint)
143− proc endpoint
144− takeMVar addr
94+-- | EndPointAddress of an endpoint.
95+newtype EndPointAddress = EndPointAddress { endPointAddressToByteString :: ByteString }
96+ deriving (Show, Eq, Ord)
97+
98+-- | EndPointAddress of a multicast group.
99+newtype MulticastAddress = MulticastAddress { multicastAddressToByteString :: ByteString }
100+ deriving (Show, Eq, Ord)
101+
102+--------------------------------------------------------------------------------
103+-- Error codes --
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
1414 , ControlHeader(..)
1515 , ConnectionRequestResponse(..)
1616 -- * Design notes
17− -- ** Overview
18− -- $overview
19− -- ** Connecting
20− -- $connecting
21− -- ** Disconnecting
22− -- $disconnecting
17+ -- $design
2318 ) where
2419
2520import Network.Transport
@@ -80,10 +75,12 @@ import Data.IORef (newIORef, modifyIORef, readIORef)
8075import Data.Lens.Lazy (Lens, lens, intMapLens, mapLens, (^.), (^=), (^+=))
8176import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
8277
83−-- $overview
78+-- $design
8479--
85−-- The TCP transport maps multiple logical connections between A and B
86−-- (in either direction) to a single TCP connection:
80+-- [Goals]
81+--
82+-- The TCP transport maps multiple logical connections between /A/ and /B/ (in
83+-- either direction) to a single TCP connection:
8784--
8885-- > +-------+ +-------+
8986-- > | A |==========================| B |
@@ -92,32 +89,41 @@ import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
9289-- > | \~~~|~~~~~~~~~~~~~~~~~~~~~~~~~<| |
9390-- > | |==========================| |
9491-- > +-------+ +-------+
95−
96−-- $connecting
9792--
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+--
98100-- Let /A/, /B/ be two endpoints without any connections. When /A/ wants to
99101-- connect to /B/, it locally records that it is trying to connect to /B/ and
100102-- 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).
102104--
103105-- When /B/ receives the connection request it first checks if it had not
104106-- already initiated a connection request to /A/. If it had not, then it will
105−-- acknowledge the connection request and record that it has a TCP connection to
106−-- /A/.
107+-- acknowledge the connection request by sending 'ConnectionRequestAccepted' to
108+-- /A/ and record that it has a TCP connection to /A/.
107109--
108110-- The tricky case arises when /A/ sends a connection request to /B/ and /B/
109111-- finds that it had already sent a connection request to /A/. In this case /B/
110112-- 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.)
112117--
113−-- Upon a rejection the /A/ thread that initiated the request just needs to wait
114−-- until the /A/ thread that is dealing with /B/s connection request completes.
115−
116−-- $disconnecting
118+-- When it receives a 'ConnectionRequestCrossed' message the /A/ thread that
119+-- initiated the request just needs to wait until the /A/ thread that is dealing
120+-- with /B/'s connection request completes.
121+--
122+-- [Disconnecting]
117123--
118−-- The TCP connection is created as soon as the first logical connection
119−-- from A to B (or B to A) is established. At this point a thread (@#@)
120−-- is spawned that listens for incoming connections from B:
124+-- The TCP connection is created as soon as the first logical connection from
125+-- /A/ to /B/ (or /B/ to /A/) is established. At this point a thread (@#@) is
126+-- spawned that listens for incoming connections from /B/:
121127--
122128-- > +-------+ +-------+
123129-- > | A |==========================| B |
@@ -127,25 +133,37 @@ import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
127133-- > | |==========================| |
128134-- > +-------+ +-------+
129135--
130−-- The question is when the TCP connection can be closed again.
131−-- Conceptually, we want to do reference counting: when there are no
132−-- logical connections left between A and B we want to close the socket
133−-- (possibly after some timeout).
136+-- The question is when the TCP connection can be closed again. Conceptually,
137+-- we want to do reference counting: when there are no logical connections left
138+-- between /A/ and /B/ we want to close the socket (possibly after some
139+-- timeout).
134140--
135−-- However, A and B need to agree that the refcount has reached zero.
136−-- It might happen that B sends a connection request over the existing
137−-- socket at the same time that A closes its logical connection to B and
138−-- closes the socket. This will cause a failure in B (which will have to
139−-- retry) which is not caused by a network failure, which is
140−-- unfortunate. (Note that the connection request from B might succeed
141−-- even if A closes the socket.)
141+-- However, /A/ and /B/ need to agree that the refcount has reached zero. It
142+-- might happen that /B/ sends a connection request over the existing socket at
143+-- the same time that /A/ closes its logical connection to /B/ and closes the
144+-- socket. This will cause a failure in /B/ (which will have to retry) which is
145+-- not caused by a network failure, which is unfortunate. (Note that the
146+-- connection request from /B/ might succeed even if /A/ closes the socket.)
142147--
143−-- Instead, A will first ask B if it's okay to close the connection. If
144−-- B has already initiated a logical connection to A then it can simply
145−-- ignore this message, because any message other than 'OkayToClose'
146−-- will imply to A that the connection should not yet be closed. On the
147−-- other hand, B must guarantee that once it does send an 'OkayToClose'
148−-- message it will not reuse the socket anymore.
148+-- Instead, when /A/ is ready to close the socket it sends a 'CloseSocket'
149+-- request to /B/ and records that its connection to /B/ is closing. If /A/
150+-- receives a new connection request from /B/ after having sent the
151+-- 'CloseSocket' request it simply forgets that it sent a 'CloseSocket' request
152+-- and increments the reference count of the connection again.
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.
149167
150168--------------------------------------------------------------------------------
151169-- Internal datatypes --
@@ -541,7 +559,8 @@ handleIncomingMessages (ourEndPoint, theirEndPoint) = do
541559 didClose <- modifyMVar (ourEndPoint ^. localState) $ \st -> do
542560 case st ^. connectionTo theirAddr of
543561 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)
545564 N.sClose sock
546565 putMVar mvar EndPointClosed
547566 return (connectionTo theirAddr ^= Nothing $ st, True)
network-transport-2/tests/TestTCP.hs+1−1
@@ -147,7 +147,7 @@ testIgnoreCloseSocket = do
147147 tlog "Ignoring it, requesting another connection"
148148 let reqId' = 1 :: Int32
149149 sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId']
150− response' <- replicateM 4 $ recvInt32 sock :: IO [Int32]
150+ replicateM 4 $ recvInt32 sock :: IO [Int32]
151151
152152 -- Close it again
153153 tlog "Closing connection"
network-transport-2/tests/TestTransport.hs+1
@@ -10,6 +10,7 @@ import Control.Exception (evaluate, throw, catch, SomeException)
1010import Control.Applicative ((<$>))
1111import Network.Transport
1212import Network.Transport.Internal (tlog)
13+import Network.Transport.Util (spawn)
1314import Data.ByteString (ByteString)
1415import Data.ByteString.Char8 (pack)
1516import Data.Map (Map)