@@ -1,50 +1,245 @@
1−module Control.Distributed.Process.Internal.NodeController
2+module Control.Distributed.Process.Node
2− ( runNodeController
3+ ( newLocalNode
3− , NCMsg(..)
4+ , closeLocalNode
11+import Prelude hiding (catch)
12+import System.IO (fixIO)
13+import qualified Data.ByteString.Lazy as BSL (fromChunks)
14+import Data.Binary (decode)
6import Data.Map (Map)
15import Data.Map (Map)
7−import qualified Data.Map as Map (empty, toList)
16+import qualified Data.Map as Map (empty, lookup, insert, delete, toList)
17+import qualified Data.List as List (delete)
8import Data.Set (Set)
18import Data.Set (Set)
9−import qualified Data.Set as Set (empty, insert, delete)
19+import qualified Data.Set as Set (empty, insert, delete, member)
10import Data.Foldable (forM_)
20import Data.Foldable (forM_)
11import Data.Maybe (isJust)
21import Data.Maybe (isJust)
12−import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
22+import Data.Typeable (Typeable)
13−import qualified Data.Accessor.Container as DAC (mapDefault)
23+import Data.Dynamic (fromDynamic)
14import Control.Category ((>>>))
24import Control.Category ((>>>))
25+import Control.Applicative ((<$>))
15−import Control.Monad (when, forever)
26+import Control.Monad (void, when, forever)
16import Control.Monad.IO.Class (MonadIO, liftIO)
27import Control.Monad.IO.Class (MonadIO, liftIO)
17import Control.Monad.State (MonadState, StateT, evalStateT, gets, modify)
28import Control.Monad.State (MonadState, StateT, evalStateT, gets, modify)
18import qualified Control.Monad.Trans.Class as Trans (lift)
29import qualified Control.Monad.Trans.Class as Trans (lift)
30+import Control.Exception (throwIO, SomeException, Exception, throwTo)
31+import qualified Control.Exception as Exception (catch)
32+import Control.Concurrent (forkIO)
19−import Control.Concurrent.MVar (withMVar)
33+import Control.Concurrent.MVar
20−import Control.Concurrent.Chan (readChan)
42+import Control.Concurrent.Chan (newChan, writeChan, readChan)
21−import Control.Exception (Exception, throwTo)
22−import Control.Distributed.Process.Serializable (Serializable)
23−import Control.Distributed.Process.Internal.CQueue (enqueue)
43+import Control.Distributed.Process.Internal.CQueue (enqueue, newCQueue)
44+import qualified Network.Transport as NT
56+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
57+import qualified Data.Accessor.Container as DAC (mapDefault)
58+import System.Random (randomIO)
24−import Control.Distributed.Process.Internal.Types
59+import Control.Distributed.Process.Internal.Types
25− ( NodeId(..)
61+ , NodeId(..)
26 , LocalProcessId(..)
62 , LocalProcessId(..)
27 , ProcessId(..)
63 , ProcessId(..)
28 , LocalNode(..)
64 , LocalNode(..)
29 , LocalProcess(..)
66 , LocalProcess(..)
67+ , LocalProcessState(..)
30 , MonitorRef(..)
79 , MonitorRef(..)
31 , MonitorNotification(..)
80 , MonitorNotification(..)
32 , LinkException(..)
81 , LinkException(..)
33 , DidUnmonitor(..)
82 , DidUnmonitor(..)
34 , DidUnlink(..)
83 , DidUnlink(..)
35− , DiedReason(..)
84+ , SpawnRef
36− , NCMsg(..)
85+ , DidSpawn(..)
37− , ProcessSignal(..)
86+ , Closure(..)
38 , createMessage
88 , createMessage
40 , MessageT
89 , MessageT
41 , runMessageT
90 , runMessageT
92+import Control.Distributed.Process.Serializable (Serializable)
43import Control.Distributed.Process.Internal.MessageT
93import Control.Distributed.Process.Internal.MessageT
44 ( sendBinary
94 ( sendBinary
45 , getLocalNode
96 , getLocalNode
99+--------------------------------------------------------------------------------
101+--------------------------------------------------------------------------------
103+-- | Initialize a new local node.
105+-- Note that proper Cloud Haskell initialization and configuration is still
107+newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
108+newLocalNode transport rtable = do
109+ mEndPoint <- NT.newEndPoint transport
111+ Left ex -> throwIO ex
112+ Right endPoint -> do
114+ state <- newMVar LocalNodeState
115+ { _localProcesses = Map.empty
116+ , _localPidCounter = 0
117+ , _localPidUnique = unq
120+ let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
121+ , localEndPoint = endPoint
122+ , localState = state
123+ , localCtrlChan = ctrlChan
124+ , remoteTable = rtable
126+ void . forkIO $ runNodeController node
127+ void . forkIO $ handleIncomingMessages node
130+-- | Force-close a local node
132+-- TODO: for now we just close the associated endpoint
133+closeLocalNode :: LocalNode -> IO ()
134+closeLocalNode node =
135+ NT.closeEndPoint (localEndPoint node)
137+-- | Run a process on a local node and wait for it to finish
138+runProcess :: LocalNode -> Process () -> IO ()
139+runProcess node proc = do
140+ done <- newEmptyMVar
141+ void $ forkProcess node (proc >> liftIO (putMVar done ()))
144+-- | Spawn a new process on a local node
145+forkProcess :: LocalNode -> Process () -> IO ProcessId
146+forkProcess node proc = modifyMVar (localState node) $ \st -> do
147+ let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
148+ , lpidUnique = st ^. localPidUnique
150+ let pid = ProcessId { processNodeId = localNodeId node
151+ , processLocalId = lpid
153+ pst <- newMVar LocalProcessState { _monitorCounter = 0
157+ (_, lproc) <- fixIO $ \ ~(tid, _) -> do
158+ let lproc = LocalProcess { processQueue = queue
160+ , processState = pst
161+ , processThread = tid
164+ reason <- Exception.catch
165+ (runLocalProcess node proc lproc >> return DiedNormal)
166+ (return . DiedException . (show :: SomeException -> String))
167+ -- [Unified: Table 4, rules termination and exiting]
168+ modifyMVar_ (localState node) $
169+ return . (localProcessWithId lpid ^= Nothing)
170+ writeChan (localCtrlChan node) NCMsg
171+ { ctrlMsgSender = Left pid
172+ , ctrlMsgSignal = Died (Left pid) reason
174+ return (tid', lproc)
176+ -- TODO: if the counter overflows we should pick a new unique
177+ return ( (localProcessWithId lpid ^= Just lproc)
178+ . (localPidCounter ^: (+ 1))
183+handleIncomingMessages :: LocalNode -> IO ()
184+handleIncomingMessages node = go [] Map.empty Set.empty
186+ go :: [NT.ConnectionId] -- ^ Connections whose purpose we don't yet know
187+ -> Map NT.ConnectionId LocalProcess -- ^ Connections to local processes
188+ -> Set NT.ConnectionId -- ^ Connections to our controller
190+ go uninitConns procConns ctrlConns = do
191+ event <- NT.receive endpoint
193+ NT.ConnectionOpened cid _rel _theirAddr ->
194+ go (cid : uninitConns) procConns ctrlConns
195+ NT.Received cid payload ->
196+ case Map.lookup cid procConns of
198+ let msg = payloadToMessage payload
199+ enqueue (processQueue proc) msg
200+ go uninitConns procConns ctrlConns
201+ Nothing | cid `Set.member` ctrlConns -> do
202+ writeChan ctrlChan (decode . BSL.fromChunks $ payload)
203+ go uninitConns procConns ctrlConns
204+ Nothing | cid `elem` uninitConns ->
205+ case payloadToId payload of
207+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
210+ go (List.delete cid uninitConns)
211+ (Map.insert cid proc procConns)
214+ fail "handleIncomingMessages: TODO 1"
216+ go (List.delete cid uninitConns)
218+ (Set.insert cid ctrlConns)
220+ fail "handleIncomingMessages: TODO 2"
221+ NT.ConnectionClosed cid ->
222+ go (List.delete cid uninitConns)
223+ (Map.delete cid procConns)
224+ (Set.delete cid ctrlConns)
225+ NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost (Just theirAddr) _) _) -> do
226+ -- [Unified table 9, rule node_disconnect]
227+ let nid = Right $ NodeId theirAddr
228+ writeChan ctrlChan NCMsg
229+ { ctrlMsgSender = nid
230+ , ctrlMsgSignal = Died nid DiedDisconnect
233+ fail "handleIncomingMessages: TODO 4"
234+ NT.EndPointClosed ->
236+ NT.ReceivedMulticast _ _ ->
237+ fail "Unexpected multicast"
239+ state = localState node
240+ endpoint = localEndPoint node
241+ ctrlChan = localCtrlChan node
48--------------------------------------------------------------------------------
243--------------------------------------------------------------------------------
49-- Top-level access to the node controller --
244-- Top-level access to the node controller --
50--------------------------------------------------------------------------------
245--------------------------------------------------------------------------------
@@ -104,8 +299,8 @@ nodeController = do
104 ncEffectNodeDied nid reason
299 ncEffectNodeDied nid reason
105 NCMsg _from (Died (Left pid) reason) ->
300 NCMsg _from (Died (Left pid) reason) ->
106 ncEffectProcessDied pid reason
301 ncEffectProcessDied pid reason
107− NCMsg _from (Spawn _ _) ->
302+ NCMsg (Left from) (Spawn proc ref) ->
108− fail $ show (localNodeId node) ++ ": spawn not implemented"
303+ ncEffectSpawn from proc ref
109 unexpected ->
304 unexpected ->
110 error $ "Unexpected message " ++ show unexpected
305 error $ "Unexpected message " ++ show unexpected
@@ -187,6 +382,16 @@ ncEffectProcessDied pid reason = do
187 modify $ (linksForProcess pid ^= Set.empty)
382 modify $ (linksForProcess pid ^= Set.empty)
188 . (monitorsForProcess pid ^= Map.empty)
383 . (monitorsForProcess pid ^= Map.empty)
385+-- [Unified: Table 13]
386+ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
387+ncEffectSpawn pid cProc ref = do
388+ mProc <- unClosure cProc
389+ -- TODO: what should we do when unClosure returns Nothing?
390+ forM_ mProc $ \proc -> do
391+ node <- lift getLocalNode
392+ pid' <- liftIO $ forkProcess node proc
393+ lift $ sendMessage (Left pid) (DidSpawn ref pid')
190--------------------------------------------------------------------------------
395--------------------------------------------------------------------------------
191-- Auxiliary --
396-- Auxiliary --
192--------------------------------------------------------------------------------
397--------------------------------------------------------------------------------
@@ -225,6 +430,18 @@ destNid (Spawn _ _) = Nothing
225isLocal :: LocalNode -> ProcessId -> Bool
430isLocal :: LocalNode -> ProcessId -> Bool
226isLocal nid pid = processNodeId pid == localNodeId nid
431isLocal nid pid = processNodeId pid == localNodeId nid
433+-- | Lookup a local closure
435+-- TODO: Duplication with onClosure in Process
436+unClosure :: Typeable a => Closure a -> NC (Maybe a)
437+unClosure (Closure (Static label) env) = do
438+ rtable <- remoteTable <$> lift getLocalNode
439+ case Map.lookup label rtable of
440+ Nothing -> return Nothing
441+ Just dyn -> case fromDynamic dyn of
442+ Nothing -> return Nothing
443+ Just dec -> return (Just $ dec env)
228--------------------------------------------------------------------------------
445--------------------------------------------------------------------------------
229-- Messages to local processes --
446-- Messages to local processes --
230--------------------------------------------------------------------------------
447--------------------------------------------------------------------------------