git.iko.soy/distributed-process
on master help sign in
Add multicast support to in-memory transport
commit53e08894b0e24339f880e0af4be73b0a788edea7 authorEdsko de Vries <edsko@edsko.net> dateThu 19 Apr 2012, 18:47 (15 years ago) parent386ead7
6 files changed+195−30
network-transport-2/network-transport.cabal+16−2 network-transport-2/src/Network/Transport.hs+4−4 network-transport-2/src/Network/Transport/Chan.hs+91−20 network-transport-2/tests/TestMulticast.hs+70 network-transport-2/tests/TestMulticastInMemory.hs+10 network-transport-2/tests/TestTransport.hs+4−4
network-transport-2/network-transport.cabal+16−2
@@ -10,19 +10,33 @@ Cabal-Version: >=1.9.2
1010Library
1111 Build-Depends: base,
1212 bytestring,
13− containers
13+ containers,
14+ data-lens
1415 Exposed-modules: Network.Transport,
1516 Network.Transport.Chan
1617 extensions: EmptyDataDecls
1718 ghc-options: -Wall -fno-warn-unused-do-bind
1819 HS-Source-Dirs: src
1920
21+Test-Suite TestMulticastInMemory
22+ Type: exitcode-stdio-1.0
23+ Main-Is: TestMulticastInMemory.hs
24+ Build-Depends: base,
25+ containers,
26+ bytestring,
27+ data-lens
28+ extensions: EmptyDataDecls OverloadedStrings
29+ ghc-options: -Wall -fno-warn-unused-do-bind
30+ HS-Source-Dirs: tests src
31+
2032Test-Suite TestInMemory
2133 Type: exitcode-stdio-1.0
2234 Main-Is: TestInMemory.hs
2335 Build-Depends: base,
2436 containers,
25− bytestring
37+ bytestring,
38+ data-lens
2639 extensions: EmptyDataDecls OverloadedStrings
2740 ghc-options: -Wall -fno-warn-unused-do-bind
2841 HS-Source-Dirs: tests src
42+
network-transport-2/src/Network/Transport.hs+4−4
@@ -14,11 +14,11 @@ data Transport = Transport {
1414
1515-- | Address of an endpoint.
1616newtype Address = Address ByteString
17− deriving Show
17+ deriving (Show, Eq, Ord)
1818
1919-- | Address of a multicast group.
2020newtype MulticastAddress = MulticastAddress ByteString
21− deriving Show
21+ deriving (Show, Eq, Ord)
2222
2323-- | Error codes (none defined at the moment).
2424data ErrorCode
@@ -60,10 +60,10 @@ data Connection = Connection {
6060
6161-- | Event on an endpoint.
6262data Event =
63− Receive ConnectionId [ByteString]
63+ Received ConnectionId [ByteString]
6464 | ConnectionClosed ConnectionId
6565 | ConnectionOpened ConnectionId Reliability Address
66− | MulticastReceive MulticastAddress [ByteString]
66+ | ReceivedMulticast MulticastAddress [ByteString]
6767 deriving Show
6868
6969-- | Multicast group.
network-transport-2/src/Network/Transport/Chan.hs+91−20
@@ -3,14 +3,41 @@ module Network.Transport.Chan (createTransport) where
33
44import Network.Transport
55import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
6−import Control.Concurrent.MVar (MVar, newMVar, modifyMVar)
6+import Control.Applicative ((<$>))
7+import Control.Category ((>>>))
8+import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, modifyMVar_, readMVar)
9+import Control.Monad (forM_)
710import Data.Map (Map)
8−import qualified Data.Map as Map (empty, insert, (!))
9−import Data.ByteString (ByteString)
11+import qualified Data.Map as Map (empty, insert, (!), size, delete)
1012import qualified Data.ByteString.Char8 as BSC (pack)
13+import Data.Lens.Lazy (Lens, lens, (^.), (^%=), (^=), (^+=), mapLens)
1114
1215-- Global state: next available "address", mapping from addresses to channels and next available connection
13−type TransportState = (Int, Map ByteString (Chan Event, Int))
16+data TransportState = State { _channels :: Map Address (Chan Event)
17+ , _connections :: Map Address Int
18+ , _multigroups :: Map MulticastAddress (MVar [Chan Event])
19+ }
20+
21+channels :: Lens TransportState (Map Address (Chan Event))
22+channels = lens _channels (\ch st -> st { _channels = ch })
23+
24+connections :: Lens TransportState (Map Address Int)
25+connections = lens _connections (\conn st -> st { _connections = conn })
26+
27+multigroups :: Lens TransportState (Map MulticastAddress (MVar [Chan Event]))
28+multigroups = lens _multigroups (\gs st -> st { _multigroups = gs })
29+
30+at :: Ord k => k -> Lens (Map k v) v
31+at k = lens (Map.! k) (Map.insert k)
32+
33+channelAt :: Address -> Lens TransportState (Chan Event)
34+channelAt addr = channels >>> at addr
35+
36+connectionAt :: Address -> Lens TransportState Int
37+connectionAt addr = connections >>> at addr
38+
39+multigroupAt :: MulticastAddress -> Lens TransportState (MVar [Chan Event])
40+multigroupAt addr = multigroups >>> at addr
1441
1542-- | Create a new Transport.
1643--
@@ -18,30 +45,74 @@ type TransportState = (Int, Map ByteString (Chan Event, Int))
1845-- (threads can, and should, create their own endpoints though).
1946createTransport :: IO Transport
2047createTransport = do
21− endpoints <- newMVar (0, Map.empty)
22− return Transport { newEndPoint = chanNewEndPoint endpoints }
48+ state <- newMVar $ State { _channels = Map.empty
49+ , _connections = Map.empty
50+ , _multigroups = Map.empty
51+ }
52+ return Transport { newEndPoint = chanNewEndPoint state }
2353
2454-- Create a new end point
2555chanNewEndPoint :: MVar TransportState -> IO (Either Error EndPoint)
26−chanNewEndPoint endpoints = do
56+chanNewEndPoint state = do
2757 chan <- newChan
28− addr <- modifyMVar endpoints $ \(next, state) -> do
29− let addr = BSC.pack (show next)
30− return ((next + 1, Map.insert addr (chan, 0) state), addr)
58+ addr <- modifyMVar state $ \st -> do
59+ let addr = Address . BSC.pack . show . Map.size $ st ^. channels
60+ return ((channelAt addr ^= chan) . (connectionAt addr ^= 0) $ st, addr)
3161 return . Right $ EndPoint { receive = readChan chan
32− , address = Address addr
33− , connect = chanConnect (Address addr) endpoints
34− , newMulticastGroup = undefined
35− , resolveMulticastGroup = undefined
62+ , address = addr
63+ , connect = chanConnect addr state
64+ , newMulticastGroup = chanNewMulticastGroup state chan
65+ , resolveMulticastGroup = chanResolveMulticastGroup state chan
3666 }
3767
3868-- Create a new connection
3969chanConnect :: Address -> MVar TransportState -> Address -> Reliability -> IO (Either Error Connection)
40−chanConnect myAddress endpoints (Address theirAddress) _ = do
41− (chan, conn) <- modifyMVar endpoints $ \(next, state) -> do
42− let (chan, conn) = state Map.! theirAddress
43− return ((next, Map.insert theirAddress (chan, conn + 1) state), (chan, conn))
70+chanConnect myAddress state theirAddress _ = do
71+ (chan, conn) <- modifyMVar state $ \st -> do
72+ let chan = st ^. channelAt theirAddress
73+ let conn = st ^. connectionAt theirAddress
74+ return (connectionAt theirAddress ^+= 1 $ st, (chan, conn))
4475 writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
45− return . Right $ Connection { send = writeChan chan . Receive conn
46− , close = writeChan chan $ ConnectionClosed conn
76+ return . Right $ Connection { send = writeChan chan . Received conn
77+ , close = writeChan chan $ ConnectionClosed conn
4778 }
79+
80+-- Create a new multicast group
81+chanNewMulticastGroup :: MVar TransportState -> Chan Event -> IO (Either Error MulticastGroup)
82+chanNewMulticastGroup state endpoint = do
83+ group <- newMVar []
84+ addr <- modifyMVar state $ \st -> do
85+ let addr = MulticastAddress . BSC.pack . show . Map.size $ st ^. multigroups
86+ return (multigroupAt addr ^= group $ st, addr)
87+ return . Right $ chanMulticastGroup state endpoint addr group
88+
89+-- Construct a multicast group
90+--
91+-- When the group is deleted some endpoints may still receive messages, but
92+-- subsequent calls to resolveMulticastGroup will fail. This mimicks the fact
93+-- that some multicast messages may still be in transit when the group is
94+-- deleted.
95+--
96+-- TODO: subscribing twice means you will receive messages twice, but as soon
97+-- as you unsubscribe you will stop receiving all messages. Not sure if this
98+-- is the right behaviour.
99+chanMulticastGroup :: MVar TransportState -> Chan Event -> MulticastAddress -> MVar [Chan Event] -> MulticastGroup
100+chanMulticastGroup state endpoint addr group =
101+ MulticastGroup { multicastAddress = addr
102+ , deleteMulticastGroup = modifyMVar_ state $ return . (multigroups ^%= Map.delete addr)
103+ , maxMsgSize = Nothing
104+ , multicastSend = \payload -> do
105+ cs <- readMVar group
106+ forM_ cs $ \ch -> writeChan ch (ReceivedMulticast addr payload)
107+ , multicastSubscribe = modifyMVar_ group $ return . (endpoint :)
108+ , multicastUnsubscribe = modifyMVar_ group $ return . (filter (/= endpoint))
109+ , multicastClose = return ()
110+ }
111+
112+-- Resolve a multicast group
113+chanResolveMulticastGroup :: MVar TransportState -> Chan Event -> MulticastAddress -> IO (Either Error MulticastGroup)
114+chanResolveMulticastGroup state endpoint addr = do
115+ group <- (^. (multigroups >>> mapLens addr)) <$> readMVar state
116+ case group of
117+ Nothing -> return . Left $ Error undefined ("Group " ++ show addr ++ " not found")
118+ Just mvar -> return . Right $ chanMulticastGroup state endpoint addr mvar
network-transport-2/tests/TestMulticast.hsnew file+70
@@ -0,0 +1,70 @@
1+module TestMulticast where
2+
3+import Network.Transport
4+import Control.Monad (replicateM, replicateM_, forM_, when)
5+import Control.Concurrent (forkIO)
6+import Control.Concurrent.MVar (MVar, newEmptyMVar, takeMVar, putMVar, readMVar)
7+import Control.Applicative ((<$>))
8+import Data.ByteString (ByteString)
9+import Data.List (elemIndex)
10+
11+-- | Node for the "No confusion" test
12+noConfusionNode :: Transport -- ^ Transport
13+ -> [MVar MulticastAddress] -- ^ my group : groups to subscribe to
14+ -> [MVar ()] -- ^ I'm ready : others ready
15+ -> Int -- ^ number of pings
16+ -> [ByteString] -- ^ my message : messages from subscribed groups (same order as 'groups to subscribe to')
17+ -> MVar Bool -- ^ I'm done
18+ -> IO ()
19+noConfusionNode transport groups ready numPings msgs done = do
20+ -- Create a new endpoint
21+ Right endpoint <- newEndPoint transport
22+
23+ -- Create a new multicast group and broadcast its address
24+ Right myGroup <- newMulticastGroup endpoint
25+ putMVar (head groups) (multicastAddress myGroup)
26+
27+ -- Subscribe to the given multicast groups
28+ addrs <- mapM readMVar (tail groups)
29+ forM_ addrs $ \addr -> do Right group <- resolveMulticastGroup endpoint addr
30+ multicastSubscribe group
31+
32+ -- Indicate that we're ready and wait for everybody else to be ready
33+ putMVar (head ready) ()
34+ mapM_ readMVar (tail ready)
35+
36+ -- Send messages..
37+ forkIO . replicateM_ numPings $ multicastSend myGroup [head msgs]
38+
39+ -- ..while checking that the messages we receive are the right ones
40+ replicateM_ (2 * numPings) $ do
41+ event <- receive endpoint
42+ case event of
43+ ReceivedMulticast addr [msg] ->
44+ let mix = addr `elemIndex` addrs in
45+ case mix of
46+ Nothing -> error "Message from unexpected source"
47+ Just ix -> when (msgs !! (ix + 1) /= msg) $ error "Unexpected message"
48+ _ ->
49+ error "Unexpected event"
50+
51+ -- Success
52+ putMVar done True
53+
54+-- | Test that distinct multicast groups are not confused
55+testNoConfusion :: Transport -> Int -> IO Bool
56+testNoConfusion transport numPings = do
57+ [group1, group2, group3] <- replicateM 3 newEmptyMVar
58+ [readyA, readyB, readyC] <- replicateM 3 newEmptyMVar
59+ [doneA, doneB, doneC] <- replicateM 3 newEmptyMVar
60+ let [msgA, msgB, msgC] = ["A says hi", "B says hi", "C says hi"]
61+
62+ forkIO $ noConfusionNode transport [group1, group1, group2] [readyA, readyB, readyC] numPings [msgA, msgA, msgB] doneA
63+ forkIO $ noConfusionNode transport [group2, group1, group3] [readyB, readyC, readyA] numPings [msgB, msgA, msgC] doneB
64+ forkIO $ noConfusionNode transport [group3, group2, group3] [readyC, readyA, readyB] numPings [msgC, msgB, msgC] doneC
65+
66+ and <$> mapM takeMVar [doneA, doneB, doneC]
67+
68+-- | Test multicast
69+testMulticast :: Transport -> IO Bool
70+testMulticast transport = testNoConfusion transport 10000
network-transport-2/tests/TestMulticastInMemory.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import TestMulticast
4+import Network.Transport.Chan
5+import System.Exit (exitFailure, exitSuccess)
6+
7+main :: IO ()
8+main = do
9+ success <- createTransport >>= testMulticast
10+ if success then exitSuccess else exitFailure
network-transport-2/tests/TestTransport.hs+4−4
@@ -23,13 +23,13 @@ echoServer endpoint = go Map.empty
2323 ConnectionOpened cid rel addr -> do
2424 Right conn <- connect endpoint addr rel
2525 go (Map.insert cid conn cs)
26− Receive cid payload -> do
26+ Received cid payload -> do
2727 send (cs Map.! cid) payload
2828 go cs
2929 ConnectionClosed cid -> do
3030 close (cs Map.! cid)
3131 go (Map.delete cid cs)
32− MulticastReceive _ _ ->
32+ ReceivedMulticast _ _ ->
3333 -- Ignore
3434 go cs
3535
@@ -44,7 +44,7 @@ ping endpoint server numPings msg = do
4444 -- Send pings and wait for reply
4545 replicateM_ numPings $ do
4646 send conn [msg]
47− Receive _ [reply] <- receive endpoint
47+ Received _ [reply] <- receive endpoint
4848 when (reply /= msg) . error $ "Message mismatch: " ++ show msg ++ " /= " ++ show reply
4949
5050 -- Close the connection
@@ -119,7 +119,7 @@ testConnections transport numPings = do
119119 verifyResponse n = do
120120 event <- receive endpoint
121121 case event of
122− Receive cid [payload] -> do
122+ Received cid [payload] -> do
123123 when (cid == serv1 && payload /= "pingA") $ error "Wrong message"
124124 when (cid == serv2 && payload /= "pingB") $ error "Wrong message"
125125 verifyResponse (n - 1)