1-- | Simple backend based on the TCP transport which offers node discovery
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
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.
13-- > import System.Environment (getArgs)
14-- > import Control.Distributed.Process
15-- > import Control.Distributed.Process.Node (initRemoteTable)
16-- > import Control.Distributed.Process.Backend.SimpleLocalnet
18-- > master :: Backend -> [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
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
37-- [Compiling and Running]
39-- Save to @example.hs@ and compile using
41-- > ghc -threaded example.hs
43-- Fire up some slave nodes (for the example, we run them on a single machine):
45-- > ./example slave localhost 8080 &
46-- > ./example slave localhost 8081 &
47-- > ./example slave localhost 8082 &
48-- > ./example slave localhost 8083 &
50-- And start the master node:
52-- > ./example master localhost 8084
54-- which should then output:
56-- > Slaves: [nid://localhost:8083:0,nid://localhost:8082:0,nid://localhost:8081:0,nid://localhost:8080:0]
58-- at which point the slaves should exit.
60-- To run the example on multiple machines, you could run
62-- > ./example slave 198.51.100.1 8080 &
63-- > ./example slave 198.51.100.2 8080 &
64-- > ./example slave 198.51.100.3 8080 &
65-- > ./example slave 198.51.100.4 8080 &
67-- on four different machines (with IP addresses 198.51.100.1..4), and run the
68-- master on a fifth node (or on any of the four machines that run the slave
71-- It is important that every node has a unique (hostname, port number) pair,
72-- and that the hostname you use to initialize the node can be resolved by
73-- peer nodes. In other words, if you start a node and pass hostname @localhost@
74-- then peer nodes won't be able to reach it because @localhost@ will resolve
75-- to a different IP address for them.
76{-# OPTIONS_GHC -fno-warn-orphans #-}
77module Control.Distributed.Process.Backend.SimpleLocalnet
90import System.IO (fixIO)
91import Data.Maybe (catMaybes)
92import Data.Binary (Binary(get, put), getWord8, putWord8)
93import Data.Accessor (Accessor, accessor, (^:), (^.))
95import qualified Data.Set as Set (insert, empty, toList)
96import Data.Foldable (forM_)
97import Data.Typeable (Typeable)
98import Control.Applicative ((<$>))
99import Control.Exception (throw)
100import Control.Monad (forever, forM, replicateM)
101import Control.Monad.IO.Class (liftIO)
102import Control.Concurrent (forkIO, threadDelay, ThreadId)
103import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
104import Control.Distributed.Process
122 , NodeMonitorNotification(..)
124import qualified Control.Distributed.Process.Node as Node
130import qualified Network.Transport.TCP as NT
132 , defaultTCPParameters
134import qualified Network.Transport as NT (Transport)
135import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
136import Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast (initMulticast)
139data Backend = Backend {
140 -- | Create a new local node
141 newLocalNode :: IO Node.LocalNode
142 -- | @findPeers t@ broadcasts a /who's there?/ message on the local
143 -- network, waits 't' msec, and then collects and returns the answers.
144 -- You can use this to dynamically discover peer nodes.
145 , findPeers :: Int -> IO [NodeId]
146 -- | Make sure that all log messages are printed by the logger on the
148 , redirectLogsHere :: Process ()
151data BackendState = BackendState {
152 _localNodes :: [Node.LocalNode]
153 , _peers :: Set NodeId
154 , discoveryDaemon :: ThreadId
157-- | Initialize the backend
158initializeBackend :: N.HostName -> N.ServiceName -> RemoteTable -> IO Backend
159initializeBackend host port rtable = do
160 mTransport <- NT.createTransport host port NT.defaultTCPParameters
161 (recv, send) <- initMulticast "224.0.0.99" 9999 1024
162 (_, backendState) <- fixIO $ \ ~(tid, _) -> do
163 backendState <- newMVar BackendState
166 , discoveryDaemon = tid
168 tid' <- forkIO $ peerDiscoveryDaemon backendState recv send
169 return (tid', backendState)
171 Left err -> throw err
173 let backend = Backend {
174 newLocalNode = apiNewLocalNode transport rtable backendState
175 , findPeers = apiFindPeers send backendState
176 , redirectLogsHere = apiRedirectLogsHere backend
180-- | Create a new local node
181apiNewLocalNode :: NT.Transport
185apiNewLocalNode transport rtable backendState = do
186 localNode <- Node.newLocalNode transport rtable
187 modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
191apiFindPeers :: (PeerDiscoveryMsg -> IO ())
195apiFindPeers send backendState delay = do
196 send PeerDiscoveryRequest
198 Set.toList . (^. peers) <$> readMVar backendState
200data PeerDiscoveryMsg =
202 | PeerDiscoveryReply NodeId
204instance Binary PeerDiscoveryMsg where
205 put PeerDiscoveryRequest = putWord8 0
206 put (PeerDiscoveryReply nid) = putWord8 1 >> put nid
210 0 -> return PeerDiscoveryRequest
211 1 -> PeerDiscoveryReply <$> get
212 _ -> fail "PeerDiscoveryMsg.get: invalid"
214-- | Respond to peer discovery requests sent by other nodes
215peerDiscoveryDaemon :: MVar BackendState
216 -> IO (PeerDiscoveryMsg, N.SockAddr)
217 -> (PeerDiscoveryMsg -> IO ())
219peerDiscoveryDaemon backendState recv send = forever go
224 PeerDiscoveryRequest -> do
225 nodes <- (^. localNodes) <$> readMVar backendState
226 forM_ nodes $ send . PeerDiscoveryReply . Node.localNodeId
227 PeerDiscoveryReply nid ->
228 modifyMVar_ backendState $ return . (peers ^: Set.insert nid)
230--------------------------------------------------------------------------------
231-- Back-end specific primitives --
232--------------------------------------------------------------------------------
234-- | Make sure that all log messages are printed by the logger on this node
235apiRedirectLogsHere :: Backend -> Process ()
236apiRedirectLogsHere backend = do
237 mLogger <- whereis "logger"
238 forM_ mLogger $ \logger -> do
239 nids <- liftIO $ findPeers backend 1000000
240 forM_ nids $ \nid -> registerRemote nid "logger" logger
242--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
246-- | Messages to slave nodes
248-- This datatype is not exposed; instead, we expose primitives for dealing
250data SlaveControllerMsg =
252 deriving (Typeable, Show)
254instance Binary SlaveControllerMsg where
255 put SlaveTerminate = putWord8 0
259 0 -> return SlaveTerminate
260 _ -> fail "SlaveControllerMsg.get: invalid"
262-- | Calling 'slave' sets up a new local node and then waits. You start
263-- processes on the slave by calling 'spawn' from other nodes.
265-- This function does not return. The only way to exit the slave is to CTRL-C
266-- the process or call terminateSlave from another node.
267startSlave :: Backend -> IO ()
268startSlave backend = do
269 node <- newLocalNode backend
270 Node.runProcess node slaveController
272-- | The slave controller interprets 'SlaveControllerMsg's
273slaveController :: Process ()
276 register "slaveController" pid
282 SlaveTerminate -> return ()
284-- | Terminate the slave at the given node ID
285terminateSlave :: NodeId -> Process ()
286terminateSlave nid = nsendRemote nid "slaveController" SlaveTerminate
289findSlaves :: Backend -> Process [NodeId]
290findSlaves backend = do
291 nodes <- liftIO $ findPeers backend 1000000
292 -- Fire of asynchronous requests for the slave controller
293 refs <- forM nodes $ \nid -> do
294 whereisRemoteAsync nid "slaveController"
295 ref <- monitorNode nid
297 -- Wait for the replies
298 catMaybes <$> replicateM (length nodes) (
300 [ matchIf (\(WhereIsReply label _) -> label == "slaveController")
301 (\(WhereIsReply _ mPid) ->
306 let nid = processNodeId pid
307 Just ref = lookup nid refs
310 , match (\(NodeMonitorNotification {}) -> return Nothing)
313-- | Terminate all slaves
314terminateAllSlaves :: Backend -> Process ()
315terminateAllSlaves backend = do
316 slaves <- findSlaves backend
317 forM_ slaves terminateSlave
318 liftIO $ threadDelay 1000000
320--------------------------------------------------------------------------------
322--------------------------------------------------------------------------------
324-- | 'startMaster' finds all slaves /currently/ available on the local network,
325-- redirects all log messages to itself, and then calls the specified process,
326-- passing the list of slaves nodes.
328-- Terminates when the specified process terminates. If you want to terminate
329-- the slaves when the master terminates, you should manually call
330-- 'terminateAllSlaves'.
332-- If you start more slave nodes after having started the master node, you can
333-- discover them with later calls to 'findSlaves', but be aware that you will
334-- need to call 'redirectLogHere' to redirect their logs to the master node.
336-- Note that you can use functionality of "SimpleLocalnet" directly (through
337-- 'Backend'), instead of using 'startMaster'/'startSlave', if the master/slave
338-- distinction does not suit your application.
339startMaster :: Backend -> ([NodeId] -> Process ()) -> IO ()
340startMaster backend proc = do
341 node <- newLocalNode backend
342 Node.runProcess node $ do
343 slaves <- findSlaves backend
344 redirectLogsHere backend
347--------------------------------------------------------------------------------
349--------------------------------------------------------------------------------
351localNodes :: Accessor BackendState [Node.LocalNode]
352localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
354peers :: Accessor BackendState (Set NodeId)
355peers = accessor _peers (\ps st -> st { _peers = ps })
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list