@@ -1,31 +1,87 @@
1-- | Simple backend based on the TCP transport which offers node discovery
1-- | 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.
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.Local
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
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
3{-# OPTIONS_GHC -fno-warn-orphans #-}
36{-# OPTIONS_GHC -fno-warn-orphans #-}
4module Control.Distributed.Process.Backend.Local
37module Control.Distributed.Process.Backend.Local
38+ ( -- * Initialization
5− ( LocalBackend(..)
39+ LocalBackend(..)
6 , initializeBackend
40 , initializeBackend
50+import System.IO (fixIO)
51+import Data.Maybe (catMaybes)
9import Data.Binary (Binary(get, put), getWord8, putWord8)
52import Data.Binary (Binary(get, put), getWord8, putWord8)
10import Data.Accessor (Accessor, accessor, (^:), (^.))
53import Data.Accessor (Accessor, accessor, (^:), (^.))
11import Data.Set (Set)
54import Data.Set (Set)
12import qualified Data.Set as Set (insert, empty, toList)
55import qualified Data.Set as Set (insert, empty, toList)
13import Data.Foldable (forM_)
56import Data.Foldable (forM_)
57+import Data.Typeable (Typeable)
14import Control.Applicative ((<$>))
58import Control.Applicative ((<$>))
15import Control.Exception (throw)
59import Control.Exception (throw)
16−import Control.Monad (forever)
60+import Control.Monad (forever, forM)
17import Control.Monad.IO.Class (liftIO)
61import Control.Monad.IO.Class (liftIO)
18−import Control.Concurrent (forkIO, threadDelay)
62+import Control.Concurrent (forkIO, threadDelay, ThreadId)
19import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
63import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
20−import Control.Distributed.Process.Internal.Types
64+import Control.Distributed.Process
22− , RemoteTable
65+ ( RemoteTable
26import qualified Control.Distributed.Process.Node as Node
80import qualified Control.Distributed.Process.Node as Node
27− ( newLocalNode
82+ , newLocalNode
28 , localNodeId
83 , localNodeId
30import qualified Network.Transport.TCP as NT
86import qualified Network.Transport.TCP as NT
31 ( createTransport
87 ( createTransport
@@ -34,12 +90,11 @@ import qualified Network.Transport.TCP as NT
34import qualified Network.Transport as NT (Transport)
90import qualified Network.Transport as NT (Transport)
35import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
91import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
36import Control.Distributed.Process.Internal.Multicast (initMulticast)
92import Control.Distributed.Process.Internal.Multicast (initMulticast)
37−import Control.Distributed.Process.Internal.Primitives (whereis, registerRemote)
39-- | Local backend
94-- | Local backend
40data LocalBackend = LocalBackend {
95data LocalBackend = LocalBackend {
41 -- | Create a new local node
96 -- | Create a new local node
42− newLocalNode :: IO LocalNode
97+ newLocalNode :: IO Node.LocalNode
43 -- | @findPeers t@ sends out a /who's there?/ request, waits 't' msec,
98 -- | @findPeers t@ sends out a /who's there?/ request, waits 't' msec,
44 -- and then collects and returns the answers
99 -- and then collects and returns the answers
45 , findPeers :: Int -> IO [NodeId]
100 , findPeers :: Int -> IO [NodeId]
@@ -49,8 +104,9 @@ data LocalBackend = LocalBackend {
51data LocalBackendState = LocalBackendState {
106data LocalBackendState = LocalBackendState {
52− _localNodes :: [LocalNode]
107+ _localNodes :: [Node.LocalNode]
53− , _peers :: Set NodeId
108+ , _peers :: Set NodeId
109+ , discoveryDaemon :: ThreadId
56-- | Initialize the backend
112-- | Initialize the backend
@@ -58,18 +114,21 @@ initializeBackend :: N.HostName -> N.ServiceName -> RemoteTable -> IO LocalBacke
58initializeBackend host port rtable = do
114initializeBackend host port rtable = do
59 mTransport <- NT.createTransport host port NT.defaultTCPParameters
115 mTransport <- NT.createTransport host port NT.defaultTCPParameters
60 (recv, send) <- initMulticast "224.0.0.99" 9999 1024
116 (recv, send) <- initMulticast "224.0.0.99" 9999 1024
117+ (_, backendState) <- fixIO $ \ ~(tid, _) -> do
61− backendState <- newMVar LocalBackendState
118+ backendState <- newMVar LocalBackendState
62− { _localNodes = []
119+ { _localNodes = []
63− , _peers = Set.empty
120+ , _peers = Set.empty
121+ , discoveryDaemon = tid
65− forkIO $ peerDiscoveryDaemon backendState recv send
123+ tid' <- forkIO $ peerDiscoveryDaemon backendState recv send
124+ return (tid', backendState)
66 case mTransport of
125 case mTransport of
67 Left err -> throw err
126 Left err -> throw err
68 Right transport ->
127 Right transport ->
69 let backend = LocalBackend {
128 let backend = LocalBackend {
70− newLocalNode = apiNewLocalNode transport rtable backendState
129+ newLocalNode = apiNewLocalNode transport rtable backendState
71− , findPeers = apiFindPeers send backendState
130+ , findPeers = apiFindPeers send backendState
72− , redirectLogsHere = apiRedirectLogsHere backend
131+ , redirectLogsHere = apiRedirectLogsHere backend
74 in return backend
133 in return backend
@@ -77,7 +136,7 @@ initializeBackend host port rtable = do
77apiNewLocalNode :: NT.Transport
136apiNewLocalNode :: NT.Transport
78 -> RemoteTable
137 -> RemoteTable
79 -> MVar LocalBackendState
138 -> MVar LocalBackendState
80− -> IO LocalNode
139+ -> IO Node.LocalNode
81apiNewLocalNode transport rtable backendState = do
140apiNewLocalNode transport rtable backendState = do
82 localNode <- Node.newLocalNode transport rtable
141 localNode <- Node.newLocalNode transport rtable
83 modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
142 modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
@@ -135,11 +194,97 @@ apiRedirectLogsHere backend = do
135 nids <- liftIO $ findPeers backend 1000000
194 nids <- liftIO $ findPeers backend 1000000
136 forM_ nids $ \nid -> registerRemote nid "logger" logger
195 forM_ nids $ \nid -> registerRemote nid "logger" logger
197+--------------------------------------------------------------------------------
199+--------------------------------------------------------------------------------
201+-- | Messages to slave nodes
203+-- This datatype is not exposed; instead, we expose primitives for dealing
205+data SlaveControllerMsg =
207+ deriving (Typeable, Show)
209+instance Binary SlaveControllerMsg where
210+ put SlaveTerminate = putWord8 0
214+ 0 -> return SlaveTerminate
215+ _ -> fail "SlaveControllerMsg.get: invalid"
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.
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
227+-- | The slave controller interprets 'SlaveControllerMsg's
228+slaveController :: Process ()
231+ register "slaveController" pid
237+ SlaveTerminate -> return ()
239+-- | Terminate the slave at the given node ID
240+terminateSlave :: NodeId -> Process ()
241+terminateSlave nid = nsendRemote nid "slaveController" SlaveTerminate
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 (\_ ->
252+ [ matchIf (\(WhereIsReply label _) -> label == "slaveController")
253+ (\(WhereIsReply _ mPid) -> return (processNodeId <$> mPid))
256+-- | Terminate all slaves
257+terminateAllSlaves :: LocalBackend -> Process ()
258+terminateAllSlaves backend = do
259+ slaves <- findSlaves backend
260+ forM_ slaves terminateSlave
261+ liftIO $ threadDelay 1000000
263+--------------------------------------------------------------------------------
265+--------------------------------------------------------------------------------
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
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
138--------------------------------------------------------------------------------
283--------------------------------------------------------------------------------
139-- Accessors --
284-- Accessors --
140--------------------------------------------------------------------------------
285--------------------------------------------------------------------------------
142−localNodes :: Accessor LocalBackendState [LocalNode]
287+localNodes :: Accessor LocalBackendState [Node.LocalNode]
143localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
288localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
145peers :: Accessor LocalBackendState (Set NodeId)
290peers :: Accessor LocalBackendState (Set NodeId)