git.iko.soy/distributed-process
on master help sign in
Finish the local backend
commitbfacdc49c8910c306c57b121bd28902b3e321439 authorEdsko de Vries <edsko@edsko.net> dateFri 6 Jul 2012, 13:00 (14 years ago) parent964c186
10 files changed+241−69
distributed-process/src/Control/Distributed/Process.hs+9−1 distributed-process/src/Control/Distributed/Process/Backend/Local.hs+167−22 distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+11−3 distributed-process/src/Control/Distributed/Process/Internal/Types.hs+2−1 distributed-process/src/Control/Distributed/Process/Node.hs+4−2 distributed-process/src/Control/Distributed/Process/Serializable.hs+10−3 distributed-process/tests/TestCH.hs+18−1 distributed-process/tests/TestClosure.hs−1 distributed-process/tests/TestLocalBackend.hs+11−29 distributed-process/tests/runTestLocalBackend.hs+9−6
distributed-process/src/Control/Distributed/Process.hs+9−1
@@ -13,6 +13,9 @@ module Control.Distributed.Process
1313 , NodeId
1414 , Process
1515 , SendPortId
16+ , processNodeId
17+ , sendPortProcessId
18+ , liftIO -- Reexported for convenience
1619 -- * Basic messaging
1720 , send
1821 , expect
@@ -74,7 +77,9 @@ module Control.Distributed.Process
7477 , registerRemote
7578 , unregisterRemote
7679 , whereisRemote
80+ , whereisRemoteAsync
7781 , nsendRemote
82+ , WhereIsReply(..)
7883 -- * Auxiliary API
7984 , catch
8085 , expectTimeout
@@ -89,6 +94,7 @@ import Prelude hiding (catch)
8994import Data.Typeable (Typeable, typeOf)
9095import Control.Applicative ((<$>))
9196import Control.Exception (throw)
97+import Control.Monad.IO.Class (liftIO)
9298import Control.Distributed.Process.Internal.MessageT (getLocalNode)
9399import Control.Distributed.Process.Internal.Dynamic (fromDyn, dynTypeRep)
94100import Control.Distributed.Process.Internal.Types
@@ -114,7 +120,8 @@ import Control.Distributed.Process.Internal.Types
114120 , SerializableDict(..)
115121 , procMsg
116122 , LocalNode(..)
117− , SendPortId
123+ , SendPortId(..)
124+ , WhereIsReply(..)
118125 )
119126import Control.Distributed.Process.Internal.Closure.BuiltIn
120127 ( linkClosure
@@ -167,6 +174,7 @@ import Control.Distributed.Process.Internal.Primitives
167174 , registerRemote
168175 , unregisterRemote
169176 , whereisRemote
177+ , whereisRemoteAsync
170178 , nsendRemote
171179 -- Auxiliary API
172180 , catch
distributed-process/src/Control/Distributed/Process/Backend/Local.hs+167−22
@@ -1,31 +1,87 @@
11-- | Simple backend based on the TCP transport which offers node discovery
2−-- based on UDP multicast
2+-- based on UDP multicast. This is a zero-configuration backend designed to
3+-- get you going with Cloud Haskell quickly without imposing any structure
4+-- on your application.
5+--
6+-- To simplify getting started we provide special support for /master/ and
7+-- /slave/ nodes (see 'startSlave' and 'startMaster'). Use of these functions
8+-- is completely optional; you can use the local backend without making use
9+-- of the predefined master and slave nodes.
10+--
11+-- [Minimal example]
12+--
13+-- > import System.Environment (getArgs)
14+-- > import Control.Distributed.Process
15+-- > import Control.Distributed.Process.Node (initRemoteTable)
16+-- > import Control.Distributed.Process.Backend.Local
17+-- >
18+-- > master :: LocalBackend -> [NodeId] -> Process ()
19+-- > master backend slaves = do
20+-- > -- Do something interesting with the slaves
21+-- > liftIO . putStrLn $ "Slaves: " ++ show slaves
22+-- > -- Terminate the slaves when the master terminates (this is optional)
23+-- > terminateAllSlaves backend
24+-- >
25+-- > main :: IO ()
26+-- > main = do
27+-- > args <- getArgs
28+-- >
29+-- > case args of
30+-- > ["master", host, port] -> do
31+-- > backend <- initializeBackend host port initRemoteTable
32+-- > startMaster backend (master backend)
33+-- > ["slave", host, port] -> do
34+-- > backend <- initializeBackend host port initRemoteTable
35+-- > startSlave backend
336{-# OPTIONS_GHC -fno-warn-orphans #-}
437module Control.Distributed.Process.Backend.Local
5− ( LocalBackend(..)
38+ ( -- * Initialization
39+ LocalBackend(..)
640 , initializeBackend
41+ -- * Slave nodes
42+ , startSlave
43+ , terminateSlave
44+ , findSlaves
45+ , terminateAllSlaves
46+ -- * Master nodes
47+ , startMaster
748 ) where
849
50+import System.IO (fixIO)
51+import Data.Maybe (catMaybes)
952import Data.Binary (Binary(get, put), getWord8, putWord8)
1053import Data.Accessor (Accessor, accessor, (^:), (^.))
1154import Data.Set (Set)
1255import qualified Data.Set as Set (insert, empty, toList)
1356import Data.Foldable (forM_)
57+import Data.Typeable (Typeable)
1458import Control.Applicative ((<$>))
1559import Control.Exception (throw)
16−import Control.Monad (forever)
60+import Control.Monad (forever, forM)
1761import Control.Monad.IO.Class (liftIO)
18−import Control.Concurrent (forkIO, threadDelay)
62+import Control.Concurrent (forkIO, threadDelay, ThreadId)
1963import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
20−import Control.Distributed.Process.Internal.Types
21− ( LocalNode
22− , RemoteTable
64+import Control.Distributed.Process
65+ ( RemoteTable
2366 , NodeId
2467 , Process
68+ , WhereIsReply(..)
69+ , whereis
70+ , whereisRemoteAsync
71+ , registerRemote
72+ , getSelfPid
73+ , register
74+ , expect
75+ , nsendRemote
76+ , receiveWait
77+ , matchIf
78+ , processNodeId
2579 )
2680import qualified Control.Distributed.Process.Node as Node
27− ( newLocalNode
81+ ( LocalNode
82+ , newLocalNode
2883 , localNodeId
84+ , runProcess
2985 )
3086import qualified Network.Transport.TCP as NT
3187 ( createTransport
@@ -34,12 +90,11 @@ import qualified Network.Transport.TCP as NT
3490import qualified Network.Transport as NT (Transport)
3591import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
3692import Control.Distributed.Process.Internal.Multicast (initMulticast)
37−import Control.Distributed.Process.Internal.Primitives (whereis, registerRemote)
3893
3994-- | Local backend
4095data LocalBackend = LocalBackend {
4196 -- | Create a new local node
42− newLocalNode :: IO LocalNode
97+ newLocalNode :: IO Node.LocalNode
4398 -- | @findPeers t@ sends out a /who's there?/ request, waits 't' msec,
4499 -- and then collects and returns the answers
45100 , findPeers :: Int -> IO [NodeId]
@@ -49,8 +104,9 @@ data LocalBackend = LocalBackend {
49104 }
50105
51106data LocalBackendState = LocalBackendState {
52− _localNodes :: [LocalNode]
53− , _peers :: Set NodeId
107+ _localNodes :: [Node.LocalNode]
108+ , _peers :: Set NodeId
109+ , discoveryDaemon :: ThreadId
54110 }
55111
56112-- | Initialize the backend
@@ -58,18 +114,21 @@ initializeBackend :: N.HostName -> N.ServiceName -> RemoteTable -> IO LocalBacke
58114initializeBackend host port rtable = do
59115 mTransport <- NT.createTransport host port NT.defaultTCPParameters
60116 (recv, send) <- initMulticast "224.0.0.99" 9999 1024
61− backendState <- newMVar LocalBackendState
62− { _localNodes = []
63− , _peers = Set.empty
64− }
65− forkIO $ peerDiscoveryDaemon backendState recv send
117+ (_, backendState) <- fixIO $ \ ~(tid, _) -> do
118+ backendState <- newMVar LocalBackendState
119+ { _localNodes = []
120+ , _peers = Set.empty
121+ , discoveryDaemon = tid
122+ }
123+ tid' <- forkIO $ peerDiscoveryDaemon backendState recv send
124+ return (tid', backendState)
66125 case mTransport of
67126 Left err -> throw err
68127 Right transport ->
69128 let backend = LocalBackend {
70− newLocalNode = apiNewLocalNode transport rtable backendState
71− , findPeers = apiFindPeers send backendState
72− , redirectLogsHere = apiRedirectLogsHere backend
129+ newLocalNode = apiNewLocalNode transport rtable backendState
130+ , findPeers = apiFindPeers send backendState
131+ , redirectLogsHere = apiRedirectLogsHere backend
73132 }
74133 in return backend
75134
@@ -77,7 +136,7 @@ initializeBackend host port rtable = do
77136apiNewLocalNode :: NT.Transport
78137 -> RemoteTable
79138 -> MVar LocalBackendState
80− -> IO LocalNode
139+ -> IO Node.LocalNode
81140apiNewLocalNode transport rtable backendState = do
82141 localNode <- Node.newLocalNode transport rtable
83142 modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
@@ -135,11 +194,97 @@ apiRedirectLogsHere backend = do
135194 nids <- liftIO $ findPeers backend 1000000
136195 forM_ nids $ \nid -> registerRemote nid "logger" logger
137196
197+--------------------------------------------------------------------------------
198+-- Slaves --
199+--------------------------------------------------------------------------------
200+
201+-- | Messages to slave nodes
202+--
203+-- This datatype is not exposed; instead, we expose primitives for dealing
204+-- with slaves.
205+data SlaveControllerMsg =
206+ SlaveTerminate
207+ deriving (Typeable, Show)
208+
209+instance Binary SlaveControllerMsg where
210+ put SlaveTerminate = putWord8 0
211+ get = do
212+ header <- getWord8
213+ case header of
214+ 0 -> return SlaveTerminate
215+ _ -> fail "SlaveControllerMsg.get: invalid"
216+
217+-- | Calling 'slave' sets up a new local node and then waits. You start
218+-- processes on the slave by calling 'spawn' from other nodes.
219+--
220+-- This function does not return. The only way to exit the slave is to CTRL-C
221+-- the process or call terminateSlave from another node.
222+startSlave :: LocalBackend -> IO ()
223+startSlave backend = do
224+ node <- newLocalNode backend
225+ Node.runProcess node slaveController
226+
227+-- | The slave controller interprets 'SlaveControllerMsg's
228+slaveController :: Process ()
229+slaveController = do
230+ pid <- getSelfPid
231+ register "slaveController" pid
232+ go
233+ where
234+ go = do
235+ msg <- expect
236+ case msg of
237+ SlaveTerminate -> return ()
238+
239+-- | Terminate the slave at the given node ID
240+terminateSlave :: NodeId -> Process ()
241+terminateSlave nid = nsendRemote nid "slaveController" SlaveTerminate
242+
243+-- | Find slave nodes
244+findSlaves :: LocalBackend -> Process [NodeId]
245+findSlaves backend = do
246+ nodes <- liftIO $ findPeers backend 1000000
247+ -- Fire of asynchronous requests for the slave controller
248+ forM_ nodes $ \nid -> whereisRemoteAsync nid "slaveController"
249+ -- Wait for the replies
250+ catMaybes <$> forM nodes (\_ ->
251+ receiveWait
252+ [ matchIf (\(WhereIsReply label _) -> label == "slaveController")
253+ (\(WhereIsReply _ mPid) -> return (processNodeId <$> mPid))
254+ ])
255+
256+-- | Terminate all slaves
257+terminateAllSlaves :: LocalBackend -> Process ()
258+terminateAllSlaves backend = do
259+ slaves <- findSlaves backend
260+ forM_ slaves terminateSlave
261+ liftIO $ threadDelay 1000000
262+
263+--------------------------------------------------------------------------------
264+-- Master nodes
265+--------------------------------------------------------------------------------
266+
267+-- | 'startMaster' finds all slaves currently available on the local network
268+-- (which should therefore be started first), redirects all log messages to
269+-- itself, and then calls the specified process, passing the list of slaves
270+-- nodes.
271+--
272+-- Terminates when the specified process terminates. If you want to terminate
273+-- the slaves when the master terminates, you should manually call
274+-- 'terminateAllSlaves'.
275+startMaster :: LocalBackend -> ([NodeId] -> Process ()) -> IO ()
276+startMaster backend proc = do
277+ node <- newLocalNode backend
278+ Node.runProcess node $ do
279+ slaves <- findSlaves backend
280+ redirectLogsHere backend
281+ proc slaves
282+
138283--------------------------------------------------------------------------------
139284-- Accessors --
140285--------------------------------------------------------------------------------
141286
142−localNodes :: Accessor LocalBackendState [LocalNode]
287+localNodes :: Accessor LocalBackendState [Node.LocalNode]
143288localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
144289
145290peers :: Accessor LocalBackendState (Set NodeId)
distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+11−3
@@ -39,6 +39,7 @@ module Control.Distributed.Process.Internal.Primitives
3939 , registerRemote
4040 , unregisterRemote
4141 , whereisRemote
42+ , whereisRemoteAsync
4243 , nsendRemote
4344 -- * Auxiliary API
4445 , catch
@@ -54,7 +55,7 @@ module Control.Distributed.Process.Internal.Primitives
5455
5556import Prelude hiding (catch)
5657import Data.Binary (decode)
57−import Data.Typeable (Typeable,)
58+import Data.Typeable (Typeable)
5859import Data.Time.Clock (getCurrentTime)
5960import Data.Time.Format (formatTime)
6061import System.Locale (defaultTimeLocale)
@@ -422,11 +423,18 @@ whereis label = do
422423-- | Query a remote process registry (synchronous)
423424whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
424425whereisRemote nid label = do
425− sendCtrlMsg (Just nid) (WhereIs label)
426+ whereisRemoteAsync nid label
426427 receiveWait [ matchIf (\(WhereIsReply label' _) -> label == label')
427428 (\(WhereIsReply _ mPid) -> return mPid)
428429 ]
429430
431+-- | Query a remote process registry (asynchronous)
432+--
433+-- Reply will come in the form of a 'WhereIsReply' message
434+whereisRemoteAsync :: NodeId -> String -> Process ()
435+whereisRemoteAsync nid label =
436+ sendCtrlMsg (Just nid) (WhereIs label)
437+
430438-- | Named send to a process in the local registry (asynchronous)
431439nsend :: Serializable a => String -> a -> Process ()
432440nsend label msg =
@@ -434,7 +442,7 @@ nsend label msg =
434442
435443-- | Named send to a process in a remote registry (asynchronous)
436444nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
437−nsendRemote nid label msg =
445+nsendRemote nid label msg =
438446 sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
439447
440448--------------------------------------------------------------------------------
distributed-process/src/Control/Distributed/Process/Internal/Types.hs+2−1
@@ -106,6 +106,7 @@ import Control.Distributed.Process.Serializable
106106 , encodeFingerprint
107107 , sizeOfFingerprint
108108 , decodeFingerprint
109+ , showFingerprint
109110 )
110111import Control.Distributed.Process.Internal.CQueue (CQueue)
111112import Control.Distributed.Process.Internal.Dynamic (Dynamic)
@@ -313,7 +314,7 @@ data Message = Message
313314 }
314315
315316instance Show Message where
316− show = show . messageEncoding
317+ show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp []
317318
318319-- | Turn any serialiable term into a message
319320createMessage :: Serializable a => a -> Message
distributed-process/src/Control/Distributed/Process/Node.hs+4−2
@@ -1,6 +1,7 @@
11-- | Local nodes
22module Control.Distributed.Process.Node
3− ( newLocalNode
3+ ( LocalNode
4+ , newLocalNode
45 , closeLocalNode
56 , forkProcess
67 , runProcess
@@ -262,7 +263,8 @@ handleIncomingMessages node = go [] Map.empty Map.empty Set.empty
262263 atomically $ writeTChan chan . decode . BSL.fromChunks $ payload
263264 go uninitConns procs chans ctrls
264265 (_, _, True, _) -> do
265− writeChan ctrlChan (decode . BSL.fromChunks $ payload)
266+ let ctrlMsg = decode . BSL.fromChunks $ payload
267+ writeChan ctrlChan ctrlMsg
266268 go uninitConns procs chans ctrls
267269 (_, _, _, True) ->
268270 case decode (BSL.fromChunks payload) of
distributed-process/src/Control/Distributed/Process/Serializable.hs+10−3
@@ -5,13 +5,15 @@ module Control.Distributed.Process.Serializable
55 , fingerprint
66 , sizeOfFingerprint
77 , Fingerprint
8+ , showFingerprint
89 ) where
910
1011import Data.Binary (Binary)
1112import Data.Typeable (Typeable(..))
1213import Data.Typeable.Internal (TypeRep(TypeRep))
14+import Numeric (showHex)
1315import Control.Exception (throw)
14−import GHC.Fingerprint.Type (Fingerprint)
16+import GHC.Fingerprint.Type (Fingerprint(..))
1517import Data.ByteString (ByteString)
1618import qualified Data.ByteString as BS
1719import qualified Data.ByteString.Internal as BSI ( unsafeCreate
@@ -38,8 +40,8 @@ decodeFingerprint bs
3840 | BS.length bs /= sizeOfFingerprint =
3941 throw $ userError "decodeFingerprint: Invalid length"
4042 | otherwise = BSI.inlinePerformIO $ do
41− let (fp, _, _) = BSI.toForeignPtr bs
42− withForeignPtr fp $ \p -> peekByteOff p 0
43+ let (fp, offset, _) = BSI.toForeignPtr bs
44+ withForeignPtr fp $ \p -> peekByteOff p offset
4345
4446-- | Size of a fingerprint
4547sizeOfFingerprint :: Int
@@ -48,3 +50,8 @@ sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
4850-- | The fingerprint of the typeRep of the argument
4951fingerprint :: Typeable a => a -> Fingerprint
5052fingerprint a = let TypeRep fp _ _ = typeOf a in fp
53+
54+-- | Show fingerprint (for debugging purposes)
55+showFingerprint :: Fingerprint -> ShowS
56+showFingerprint (Fingerprint hi lo) =
57+ showString "(" . showHex hi . showString "," . showHex lo . showString ")"
distributed-process/tests/TestCH.hs+18−1
@@ -13,7 +13,6 @@ import Control.Concurrent.MVar
1313 , readMVar
1414 )
1515import Control.Monad (replicateM_, replicateM)
16−import Control.Monad.IO.Class (liftIO)
1716import Control.Exception (throwIO)
1817import Control.Applicative ((<$>), (<*>))
1918import qualified Network.Transport as NT (Transport, closeEndPoint)
@@ -540,6 +539,23 @@ testRegistry transport = do
540539 True <- return $ pingServer == pid'
541540 return ()
542541
542+testRemoteRegistry :: NT.Transport -> IO ()
543+testRemoteRegistry transport = do
544+ node1 <- newLocalNode transport initRemoteTable
545+ node2 <- newLocalNode transport initRemoteTable
546+
547+ pingServer <- forkProcess node1 ping
548+
549+ runProcess node2 $ do
550+ let nid1 = localNodeId node1
551+ registerRemote nid1 "ping" pingServer
552+ Just pid <- whereisRemote nid1 "ping"
553+ True <- return $ pingServer == pid
554+ us <- getSelfPid
555+ nsendRemote nid1 "ping" (Pong us)
556+ Ping pid' <- expect
557+ True <- return $ pingServer == pid'
558+ return ()
543559
544560main :: IO ()
545561main = do
@@ -554,6 +570,7 @@ main = do
554570 , ("MergeChannels", testMergeChannels transport)
555571 , ("Terminate", testTerminate transport)
556572 , ("Registry", testRegistry transport)
573+ , ("RemoteRegistry", testRemoteRegistry transport)
557574 -- Monitoring processes
558575 --
559576 -- The "missing" combinations in the list below don't make much sense, as
distributed-process/tests/TestClosure.hs−1
@@ -2,7 +2,6 @@ module Main where
22
33import qualified Data.ByteString.Lazy as BSL (empty)
44import Control.Monad (join, replicateM)
5−import Control.Monad.IO.Class (liftIO)
65import Control.Exception (IOException, throw)
76import Control.Concurrent (forkIO, threadDelay)
87import Control.Concurrent.MVar (MVar, newEmptyMVar, readMVar, takeMVar, putMVar)
distributed-process/tests/TestLocalBackend.hs+11−29
@@ -1,30 +1,12 @@
11import System.Environment (getArgs, getProgName)
2−import Control.Distributed.Process (say)
3−import Control.Distributed.Process.Node
4− ( initRemoteTable
5− , forkProcess
6− , runProcess
7− )
2+import Control.Distributed.Process (NodeId, Process, liftIO)
3+import Control.Distributed.Process.Node (initRemoteTable)
84import Control.Distributed.Process.Backend.Local
9−import Control.Concurrent (threadDelay)
10−import Control.Monad (forever)
11−import Control.Monad.IO.Class (liftIO)
125
13−server :: LocalBackend -> IO ()
14−server backend = do
15− node <- newLocalNode backend
16− runProcess node $ do
17− redirectLogsHere backend
18− say "this is the server"
19− threadDelay 30000000
20−
21−client :: LocalBackend -> IO ()
22−client backend = do
23− node <- newLocalNode backend
24− forkProcess node . forever $ do
25− say "this is a client"
26− liftIO $ threadDelay 2000000
27− threadDelay 30000000
6+master :: LocalBackend -> [NodeId] -> Process ()
7+master backend slaves = do
8+ liftIO . putStrLn $ "Slaves: " ++ show slaves
9+ terminateAllSlaves backend
2810
2911main :: IO ()
3012main = do
@@ -32,11 +14,11 @@ main = do
3214 args <- getArgs
3315
3416 case args of
35− ["server", host, port] -> do
17+ ["master", host, port] -> do
3618 backend <- initializeBackend host port initRemoteTable
37− server backend
38− ["client", host, port] -> do
19+ startMaster backend (master backend)
20+ ["slave", host, port] -> do
3921 backend <- initializeBackend host port initRemoteTable
40− client backend
22+ startSlave backend
4123 _ ->
42− putStrLn $ "usage: " ++ prog ++ " (server | client) host port"
24+ putStrLn $ "usage: " ++ prog ++ " (master | slave) host port"
distributed-process/tests/runTestLocalBackend.hs+9−6
@@ -1,8 +1,11 @@
11#!/bin/bash
22TestLocalBackend=dist/build/TestLocalBackend/TestLocalBackend
3−$TestLocalBackend client 127.0.0.1 8080 &
4−$TestLocalBackend client 127.0.0.1 8081 &
5−$TestLocalBackend client 127.0.0.1 8082 &
6−$TestLocalBackend client 127.0.0.1 8083 &
7−sleep 2 # give clients a change to start
8−$TestLocalBackend server 127.0.0.1 8084
3+$TestLocalBackend slave 127.0.0.1 8080 &
4+sleep 1
5+$TestLocalBackend slave 127.0.0.1 8081 &
6+sleep 1
7+$TestLocalBackend slave 127.0.0.1 8082 &
8+sleep 1
9+$TestLocalBackend slave 127.0.0.1 8083 &
10+sleep 1
11+$TestLocalBackend master 127.0.0.1 8084