git.iko.soy/distributed-process
on master help sign in
Build up an initial execution environment for agents
dd2ddb5Tim Watson
.gitignore+1
@@ -11,4 +11,5 @@ dist/
11*.ps
11*.ps
12*.swp
12*.swp
13cabal-dev
13cabal-dev
14+cabal-dev*
14.DS_Store
15.DS_Store
distributed-process.cabal+5−4
@@ -70,7 +70,6 @@ Library
70 Control.Distributed.Process.Serializable,
70 Control.Distributed.Process.Serializable,
71 Control.Distributed.Process.UnsafePrimitives
71 Control.Distributed.Process.UnsafePrimitives
72 Other-modules: Control.Distributed.Process.Internal.Primitives,
72 Other-modules: Control.Distributed.Process.Internal.Primitives,
73− Control.Distributed.Process.Management.Bus,
74 Control.Distributed.Process.Internal.Spawn,
73 Control.Distributed.Process.Internal.Spawn,
75 Control.Distributed.Process.Internal.Primitives,
74 Control.Distributed.Process.Internal.Primitives,
76 Control.Distributed.Process.Internal.CQueue,
75 Control.Distributed.Process.Internal.CQueue,
@@ -80,10 +79,12 @@ Library
80 Control.Distributed.Process.Internal.StrictMVar,
79 Control.Distributed.Process.Internal.StrictMVar,
81 Control.Distributed.Process.Internal.WeakTQueue,
80 Control.Distributed.Process.Internal.WeakTQueue,
82 Control.Distributed.Process.Internal.StrictContainerAccessors,
81 Control.Distributed.Process.Internal.StrictContainerAccessors,
83− Control.Distributed.Process.Management.Trace.Types,
82+ Control.Distributed.Process.Management.Bus,
83+ Control.Distributed.Process.Management.Table,
84 Control.Distributed.Process.Management.Trace.Primitives,
84 Control.Distributed.Process.Management.Trace.Primitives,
85− Control.Distributed.Process.Management.Trace.Tracer,
85+ Control.Distributed.Process.Management.Trace.Remote,
86− Control.Distributed.Process.Management.Trace.Remote
86+ Control.Distributed.Process.Management.Trace.Types,
87+ Control.Distributed.Process.Management.Trace.Tracer
87 Extensions: RankNTypes,
88 Extensions: RankNTypes,
88 ScopedTypeVariables,
89 ScopedTypeVariables,
89 FlexibleInstances,
90 FlexibleInstances,
src/Control/Distributed/Process/Internal/Types.hs+2−1
@@ -99,6 +99,7 @@ import Control.Exception (Exception)
99import Control.Concurrent (ThreadId)
99import Control.Concurrent (ThreadId)
100import Control.Concurrent.Chan (Chan)
100import Control.Concurrent.Chan (Chan)
101import Control.Concurrent.STM (STM)
101import Control.Concurrent.STM (STM)
102+import Control.Concurrent.STM.TChan (TChan)
102import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
103import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
103import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
104import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
104import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
105import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
@@ -214,7 +215,7 @@ data MxEventBus =
214 agent :: !ProcessId
215 agent :: !ProcessId
215 , tracer :: !Tracer
216 , tracer :: !Tracer
216 , evbuss :: !(Weak (CQueue Message))
217 , evbuss :: !(Weak (CQueue Message))
217− , mxNew :: !((Message -> Process ()) -> IO ProcessId)
218+ , mxNew :: !((TChan Message -> Process ()) -> IO ProcessId)
218-- , mxReg :: !(StrictMVar (Map MxAgentId ))
219-- , mxReg :: !(StrictMVar (Map MxAgentId ))
219 }
220 }
220
221
src/Control/Distributed/Process/Management.hs+118−12
@@ -38,9 +38,38 @@
38--
38--
39--
39--
40-----------------------------------------------------------------------------
40-----------------------------------------------------------------------------
41−module Control.Distributed.Process.Management where
41+module Control.Distributed.Process.Management
42+ (
43+ MxAction()
44+ , MxAgentId
45+ -- * Firing /mx events/
46+ , mxNotify
47+ -- * Constructing Agents
48+ , mxAgent
49+ , mxAgentDeactivate
50+ , mxAgentReady
51+ , mxAgentPublish
52+ , mxSetLocal
53+ , mxGetLocal
54+ -- * Lifting
55+ , liftP
56+ -- * Management Data API
57+ , mxPublish
58+ , mxSet
59+ , mxGet
60+ ) where
42
61
43import Control.Applicative ((<$>))
62import Control.Applicative ((<$>))
63+import Control.Concurrent.STM (atomically)
64+import Control.Concurrent.STM.TChan
65+ ( readTChan
66+ )
67+import Control.Distributed.Process.Internal.Primitives
68+ ( newChan
69+ , nsend
70+ , receiveWait
71+ , matchChan
72+ )
44import Control.Distributed.Process.Internal.Types
73import Control.Distributed.Process.Internal.Types
45 ( Process
74 ( Process
46 , ProcessId
75 , ProcessId
@@ -51,9 +80,28 @@ import Control.Distributed.Process.Internal.Types
51 , unsafeCreateUnencodedMessage
80 , unsafeCreateUnencodedMessage
52 )
81 )
53import Control.Distributed.Process.Management.Bus (publishEvent)
82import Control.Distributed.Process.Management.Bus (publishEvent)
83+import qualified Control.Distributed.Process.Management.Table as Table
84+import Control.Distributed.Process.Management.Types
85+ ( MxAgentId(..)
86+ , MxAgent(..)
87+ , MxAgentState(..)
88+ , MxAgentStart(..)
89+ )
54import Control.Distributed.Process.Serializable (Serializable)
90import Control.Distributed.Process.Serializable (Serializable)
55import Control.Monad.IO.Class (liftIO)
91import Control.Monad.IO.Class (liftIO)
56import Control.Monad.Reader (ask)
92import Control.Monad.Reader (ask)
93+import qualified Control.Monad.State as ST
94+ ( MonadState
95+ , StateT
96+ , get
97+ , modify
98+ , lift
99+ , runStateT
100+ )
101+
102+data MxAction =
103+ MxAgentDeactivate !String
104+ | MxAgentReady
57
105
58-- | Publishes an arbitrary @Serializable@ message to the management event bus.
106-- | Publishes an arbitrary @Serializable@ message to the management event bus.
59-- Note that /no attempt is made to force the argument/, therefore it is very
107-- Note that /no attempt is made to force the argument/, therefore it is very
@@ -65,20 +113,78 @@ mxNotify msg = do
65 bus <- localEventBus . processNode <$> ask
113 bus <- localEventBus . processNode <$> ask
66 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
114 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
67
115
68−mxAgent :: (Message -> Process ()) -> Process ProcessId
116+-- | Publish an arbitrary @Message@ as a property in the management database.
69−mxAgent handler = do
117+--
70− node <- processNode <$> ask
118+-- For publishing @Serializable@ data, use 'mxSet' instead. For publishing
71− pid <- liftIO $ mxNew (localEventBus node) handler
119+-- properties efficiently within an agent, use 'mxAgentPublish' instead,
72− -- registerAgent pid
120+-- since it will be more efficient in that context.
73− return pid
121+mxPublish :: MxAgentId -> String -> Message -> Process ()
122+mxPublish a k v = Table.set k v (Table.MxForAgent a)
123+
124+-- | Sets an arbitrary @Serializable@ datum against a key in the management
125+-- database. Note that /no attempt is made to force the argument/, therefore
126+-- it is very important that you do not pass unevaluated thunks that might
127+-- crash some other, arbitrary process (or management agent!) that obtains
128+-- and attempts to force the value later on.
129+--
130+mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()
131+mxSet mxId key msg =
132+ Table.set key (unsafeCreateUnencodedMessage msg) (Table.MxForAgent mxId)
74
133
75−mxPublish = undefined
134+-- | Fetches a property from the management database for the given key.
135+-- If the property is not set, or does not match the expected type when
136+-- typechecked (at runtime), returns @Nothing@.
137+mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)
138+mxGet mxId = Table.fetch (Table.MxForAgent mxId)
76
139
77−mxSet = undefined
140+--------------------------------------------------------------------------------
141+-- API for writing user defined management extensions (i.e., agents) --
142+--------------------------------------------------------------------------------
78
143
79−mxGet = undefined
144+mxAgentPublish = undefined
145+
146+mxAgentDeactivate :: forall s. String -> MxAgent s MxAction
147+mxAgentDeactivate = return . MxAgentDeactivate
148+
149+mxAgentReady :: forall s. MxAgent s MxAction
150+mxAgentReady = return MxAgentReady
151+
152+liftP :: Process a -> MxAgent s a
153+liftP p = MxAgent $ ST.lift p
154+
155+mxSetLocal :: s -> MxAgent s ()
156+mxSetLocal s = ST.modify $ \st -> st { mxLocalState = s }
157+
158+mxGetLocal :: MxAgent s s
159+mxGetLocal = ST.get >>= return . mxLocalState
160+
161+-- | Activates a new agent.
162+--
163+mxAgent :: MxAgentId
164+ -> s
165+ -> (Message -> MxAgent s MxAction)
166+ -> Process ProcessId
167+mxAgent mxId initState handler = do
168+ node <- processNode <$> ask
169+ pid <- liftIO $ mxNew (localEventBus node) $ start
170+ return pid
171+ where
172+ start chan = do
173+ (sp, rp) <- newChan
174+ nsend Table.mxTableCoordinator (MxAgentStart sp mxId)
175+ tablePid <- receiveWait [ matchChan rp (\(p :: ProcessId) -> return p) ]
176+ runAgent handler chan $ MxAgentState mxId tablePid initState
80
177
81−mxIncrement = undefined
178+ runAgent h c s = do
179+ msg <- (liftIO $ atomically $ readTChan c)
180+ (action, state) <- runAgentST s $ h msg
181+ case action of
182+ MxAgentReady -> runAgent h c state
183+ MxAgentDeactivate _ -> {- TODO: log r -} return ()
184+-- MxAgentBecome h' -> runAgent h' c state
82
185
83−mxDecrement = undefined
186+ runAgentST :: MxAgentState s
187+ -> MxAgent s MxAction
188+ -> Process (MxAction, MxAgentState s)
189+ runAgentST state proc = ST.runStateT (unAgent proc) state
84
190
src/Control/Distributed/Process/Management/Agent.hs+13−21
@@ -8,7 +8,6 @@ import Control.Concurrent.STM (atomically)
8import Control.Concurrent.STM.TChan
8import Control.Concurrent.STM.TChan
9 ( TChan
9 ( TChan
10 , newBroadcastTChanIO
10 , newBroadcastTChanIO
11− , readTChan
12 , writeTChan
11 , writeTChan
13 , dupTChan
12 , dupTChan
14 )
13 )
@@ -23,39 +22,36 @@ import Control.Distributed.Process.Internal.CQueue
23 ( enqueueSTM
22 ( enqueueSTM
24 , CQueue
23 , CQueue
25 )
24 )
25+import Control.Distributed.Process.Management.Types
26+ ( Fork
27+ )
26import Control.Distributed.Process.Management.Trace.Tracer
28import Control.Distributed.Process.Management.Trace.Tracer
27 ( traceController
29 ( traceController
28 )
30 )
29import Control.Distributed.Process.Internal.Types
31import Control.Distributed.Process.Internal.Types
30 ( Process
32 ( Process
31− , SendPort
32 , Message
33 , Message
33− , MxEventBus(..)
34 , Tracer(..)
34 , Tracer(..)
35− , LocalNode(..)
36 , LocalProcess(..)
35 , LocalProcess(..)
37 , ProcessId
36 , ProcessId
38 , forever'
37 , forever'
39 )
38 )
40import Control.Exception (AsyncException(ThreadKilled), SomeException)
39import Control.Exception (AsyncException(ThreadKilled), SomeException)
41−import qualified Control.Exception as Ex (catch, finally)
42import Control.Monad (void)
40import Control.Monad (void)
43import Control.Monad.IO.Class (liftIO)
41import Control.Monad.IO.Class (liftIO)
44import Control.Monad.Reader (ask)
42import Control.Monad.Reader (ask)
45−import GHC.Weak (Weak(Weak), deRefWeak)
43+import GHC.Weak (Weak, deRefWeak)
46−import System.Environment (getEnv)
44+
45+--------------------------------------------------------------------------------
46+-- Agent Controller Implementation --
47+--------------------------------------------------------------------------------
47
48
48−-- | Gross though it is, this synonym represents a function
49−-- used to forking new processes, which has to be passed as a HOF
50−-- when calling mxAgentController, since there's no other way to
51−-- avoid a circular dependency with Node.hs
52−type Fork = (Process () -> IO ProcessId)
53
49
54-- | A triple containing a configured tracer, weak pointer to the
50-- | A triple containing a configured tracer, weak pointer to the
55-- agent controller's mailbox (CQueue) and an expression used to
51-- agent controller's mailbox (CQueue) and an expression used to
56-- instantiate new agents on the current node.
52-- instantiate new agents on the current node.
57type AgentConfig =
53type AgentConfig =
58− (Tracer, Weak (CQueue Message), ((Message -> Process ()) -> IO ProcessId))
54+ (Tracer, Weak (CQueue Message), ((TChan Message -> Process ()) -> IO ProcessId))
59
55
60-- | Starts a management agent for the current node. The agent process
56-- | Starts a management agent for the current node. The agent process
61-- must not crash or be killed, so we generally avoid publishing its
57-- must not crash or be killed, so we generally avoid publishing its
@@ -75,11 +71,11 @@ mxAgentController :: Fork
75 -> MVar AgentConfig
71 -> MVar AgentConfig
76 -> Process ()
72 -> Process ()
77mxAgentController forkProcess mv = do
73mxAgentController forkProcess mv = do
78− node <- processNode <$> ask
74+ -- node <- processNode <$> ask
79 trc <- liftIO $ startTracing forkProcess
75 trc <- liftIO $ startTracing forkProcess
80 sigbus <- liftIO $ newBroadcastTChanIO
76 sigbus <- liftIO $ newBroadcastTChanIO
81 weakQueue <- processWeakQ <$> ask
77 weakQueue <- processWeakQ <$> ask
82− liftIO $ putMVar mv (trc, weakQueue, mxAgent forkProcess sigbus)
78+ liftIO $ putMVar mv (trc, weakQueue, mxStartAgent forkProcess sigbus)
83 go sigbus trc forkProcess
79 go sigbus trc forkProcess
84 where
80 where
85 go bus tracer fork = forever' $ do
81 go bus tracer fork = forever' $ do
@@ -119,12 +115,8 @@ mxAgentController forkProcess mv = do
119 liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
115 liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
120
116
121-- | Forks a new process in which an mxAgent is run.
117-- | Forks a new process in which an mxAgent is run.
122−mxAgent :: Fork -> TChan Message -> (Message -> Process ()) -> IO ProcessId
118+mxStartAgent :: Fork -> TChan Message -> (TChan Message -> Process ()) -> IO ProcessId
123−mxAgent fork chan handler = (atomically (dupTChan chan)) >>= fork . run handler
119+mxStartAgent fork chan handler = (atomically (dupTChan chan)) >>= fork . handler
124− where
125− run :: (Message -> Process ()) -> TChan Message -> Process ()
126− run handler' chan' = do
127− (liftIO $ atomically $ readTChan chan') >>= handler' >> run handler' chan'
128
120
129startTracing :: Fork -> IO Tracer
121startTracing :: Fork -> IO Tracer
130startTracing forkProcess = do
122startTracing forkProcess = do
src/Control/Distributed/Process/Management/Bus.hs−23
@@ -21,29 +21,6 @@ import Network.Transport
21 )
21 )
22import System.Mem.Weak (deRefWeak)
22import System.Mem.Weak (deRefWeak)
23
23
24−data MxEvent =
25− MxSpawned ProcessId
26− -- ^ fired whenever a local process is spawned
27− | MxRegistered ProcessId String
28− -- ^ fired whenever a process/name is registered (locally)
29− | MxUnRegistered ProcessId String
30− -- ^ fired whenever a process/name is unregistered (locally)
31− | MxProcessDied ProcessId DiedReason
32− -- ^ fired whenever a process dies
33− | MxNodeDied NodeId DiedReason
34− -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
35− | MxSent ProcessId ProcessId Message
36− -- ^ fired whenever a message is sent from a local process
37− | MxReceived ProcessId Message
38− -- ^ fired whenever a message is received by a local process
39− | MxConnected ConnectionId EndPointAddress
40− -- ^ fired when a network-transport connection is first established
41− | MxDisconnected ConnectionId
42− -- ^ fired when a network-transport connection is broken/disconnected
43− | MxUser Message
44− -- ^ a user defined trace event (see 'traceMessage')
45− deriving (Typeable, Show)
46−
47publishEvent :: MxEventBus -> Message -> IO ()
24publishEvent :: MxEventBus -> Message -> IO ()
48publishEvent MxEventBusInitialising _ = return ()
25publishEvent MxEventBusInitialising _ = return ()
49publishEvent (MxEventBus _ _ wqRef _) msg = do
26publishEvent (MxEventBus _ _ wqRef _) msg = do
src/Control/Distributed/Process/Management/Table.hsnew file+219
@@ -0,0 +1,219 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE RecordWildCards #-}
3+
4+module Control.Distributed.Process.Management.Table
5+ ( MxTableRequest(..)
6+ , MxTableId(..)
7+ , mxTableCoordinator
8+ , startTableCoordinator
9+ , delete
10+ , purge
11+ , clear
12+ , set
13+ , get
14+ , fetch
15+ ) where
16+
17+import Control.Distributed.Process.Internal.Primitives
18+ ( receiveWait
19+ , receiveChan
20+ , match
21+ , matchAny
22+ , matchIf
23+ , matchChan
24+ , send
25+ , nsend
26+ , sendChan
27+ , getSelfPid
28+ , link
29+ , monitor
30+ , unwrapMessage
31+ , newChan
32+ , withMonitor
33+ )
34+import Control.Distributed.Process.Internal.Types
35+ ( Process
36+ , ProcessId
37+ , ProcessMonitorNotification(..)
38+ , SendPort
39+ , ReceivePort
40+ , Message
41+ , unsafeCreateUnencodedMessage
42+ )
43+import Control.Distributed.Process.Management.Types
44+ ( MxTableId(..)
45+ , MxAgentId(..)
46+ , MxAgentStart(..)
47+ , Fork)
48+import Control.Distributed.Process.Serializable (Serializable)
49+import Control.Monad.IO.Class (liftIO)
50+import Data.Accessor (Accessor, accessor, (^=), (^:))
51+import Data.Binary (Binary)
52+import Data.Map.Strict (Map)
53+import qualified Data.Map.Strict as Map
54+import Data.Typeable (Typeable)
55+
56+import GHC.Generics
57+
58+-- An extremely lightweight shared Map implementation, for use
59+-- by /management agents/ and their cohorts. Each agent is assigned
60+-- a table, into which any serializable @Message@ can be inserted.
61+-- Data are inserted, removed and searched for via their key, which
62+-- is a string. Tables can be purged, values can be set, fetched or
63+-- cleared/removed.
64+--
65+
66+data MxTableRequest =
67+ Delete
68+ | Purge
69+ | Clear !String
70+ | Set !String !Message
71+ | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]
72+ deriving (Typeable, Generic)
73+instance Binary MxTableRequest where
74+
75+data MxTableState = MxTableState
76+ {
77+ _name :: !String
78+ , _entries :: !(Map String Message)
79+ }
80+
81+type MxTables = Map MxAgentId ProcessId
82+
83+mxTableCoordinator :: String
84+mxTableCoordinator = "mx.table.coordinator"
85+
86+delete :: MxTableId -> Process ()
87+delete = sendReq Delete
88+
89+purge :: MxTableId -> Process ()
90+purge = sendReq Purge
91+
92+clear :: String -> MxTableId -> Process ()
93+clear k = sendReq (Clear k)
94+
95+set :: String -> Message -> MxTableId -> Process ()
96+set k v = sendReq (Set k v)
97+
98+fetch :: forall a. (Serializable a)
99+ => MxTableId
100+ -> String
101+ -> Process (Maybe a)
102+fetch (MxForPid pid) key = get pid key
103+fetch mxId@(MxForAgent _) key = do
104+ (sp, rp) <- newChan :: Process (SendPort (Maybe Message),
105+ ReceivePort (Maybe Message))
106+ sendReq (Get key sp) mxId
107+ receiveChan rp >>= maybe (return Nothing)
108+ (unwrapMessage :: Message -> Process (Maybe a))
109+
110+-- [note: un-typed send port]
111+-- Here, fetch uses a typed channel over a raw Message to obtain
112+-- its result, so type checking is deferred until receipt and will
113+-- be handled in the caller's thread. This is necessary because
114+-- the server portion of the code knows nothing about the types
115+-- involved, nor should it, since these tables can be used to
116+-- store arbitrary serializable data.
117+
118+get :: forall a. (Serializable a)
119+ => ProcessId
120+ -> String
121+ -> Process (Maybe a)
122+get pid key = do
123+ safeFetch pid key >>= maybe (return Nothing)
124+ (unwrapMessage :: Message -> Process (Maybe a))
125+
126+safeFetch :: ProcessId -> String -> Process (Maybe Message)
127+safeFetch pid key = do
128+ (sp, rp) <- newChan
129+ send pid $ Get key sp
130+ withMonitor pid $ do
131+ receiveWait [
132+ matchChan rp return
133+ , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)
134+ (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))
135+ ]
136+
137+sendReq :: MxTableRequest -> MxTableId -> Process ()
138+sendReq req tid = (resolve tid) req
139+
140+resolve :: Serializable a => MxTableId -> (a -> Process ())
141+resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)
142+resolve (MxForPid pid) = \msg -> send pid msg
143+
144+startTableCoordinator :: Fork -> Process ()
145+startTableCoordinator fork = run Map.empty
146+ where
147+
148+ -- TODO: rethink this design. routing via the
149+ -- coordinator will be a publishing bottle neck,
150+ -- plus the code in 'fetch' really requires a
151+ -- pid to send to, so that'll not work via an
152+ -- intermediary anyway
153+
154+ run :: MxTables -> Process ()
155+ run tables =
156+ receiveWait [
157+ match (\(MxAgentStart ch agent) ->
158+ lookupAgent tables agent >>= \(p, t) -> do
159+ sendChan ch p >> return t)
160+ , match (\(agent, Get k sp) -> do
161+ lookupAgent tables agent >>= \(p, t) -> do
162+ safeFetch p k >>= sendChan sp >> return t)
163+ , match $ handleRequest tables
164+ ] >>= run
165+
166+ handleRequest :: MxTables
167+ -> (MxAgentId, MxTableRequest)
168+ -> Process MxTables
169+ handleRequest tables' (agent, req) =
170+ lookupAgent tables' agent >>= \(p, t) -> do send p req >> return t
171+
172+ lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)
173+ lookupAgent tables' agentId' =
174+ case Map.lookup agentId' tables' of
175+ Nothing -> launchNew agentId' tables'
176+ Just p -> return (p, tables')
177+
178+ launchNew :: MxAgentId
179+ -> MxTables
180+ -> Process (ProcessId, MxTables)
181+ launchNew mxId tblMap = do
182+ let initState = MxTableState { _name = (agentId mxId)
183+ , _entries = Map.empty
184+ }
185+ (pid, _) <- spawnSup $ tableHandler initState
186+ return $ (pid, Map.insert mxId pid tblMap)
187+
188+ spawnSup proc = do
189+ us <- getSelfPid
190+ -- we need to used that passed in "fork", in order to
191+ -- break an import cycle with Node.hs via the management
192+ -- agent, management API and the tracing modules
193+ them <- liftIO $ fork $ link us >> proc
194+ -- them <- spawnLocal nid $ link us >> proc
195+ ref <- monitor them
196+ return (them, ref)
197+
198+tableHandler :: MxTableState -> Process ()
199+tableHandler state = do
200+ ns <- receiveWait [
201+ match (\Delete -> return Nothing)
202+ , match (\Purge -> return $ Just $ (entries ^= Map.empty) $ state)
203+ , match (\(Clear k) -> return $ Just $ (entries ^: Map.delete k) $ state)
204+ , match (\(Set k v) -> return $ Just $ (entries ^: Map.insert k v) state)
205+ , match (\(Get k c) -> getEntry k c state >> return (Just state))
206+ ]
207+ case ns of
208+ Nothing -> return ()
209+ Just s' -> tableHandler s'
210+
211+getEntry :: String
212+ -> SendPort (Maybe Message)
213+ -> MxTableState
214+ -> Process ()
215+getEntry k m MxTableState{..} = sendChan m =<< return (Map.lookup k _entries)
216+
217+entries :: Accessor MxTableState (Map String Message)
218+entries = accessor _entries (\ls st -> st { _entries = ls })
219+
src/Control/Distributed/Process/Management/Types.hsnew file+72
@@ -0,0 +1,72 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+module Control.Distributed.Process.Management.Types
3+ ( MxAgentId(..)
4+ , MxTableId(..)
5+ , MxAgentState(..)
6+ , MxAgent(..)
7+ , MxAgentStart(..)
8+ , Fork
9+ ) where
10+
11+import Control.Applicative (Applicative)
12+import Control.Distributed.Process.Internal.Types
13+ ( Process
14+ , ProcessId
15+ , Message
16+ , SendPort
17+ )
18+import Control.Monad.IO.Class (MonadIO)
19+import qualified Control.Monad.State as ST
20+ ( MonadState
21+ , StateT
22+ , get
23+ , lift
24+ , runStateT
25+ )
26+import Data.Binary (Binary)
27+import Data.Map.Strict (Map)
28+import Data.Typeable (Typeable)
29+import GHC.Generics
30+
31+-- | Gross though it is, this synonym represents a function
32+-- used to forking new processes, which has to be passed as a HOF
33+-- when calling mxAgentController, since there's no other way to
34+-- avoid a circular dependency with Node.hs
35+type Fork = (Process () -> IO ProcessId)
36+
37+-- | A newtype wrapper for an agent id (which is a string).
38+newtype MxAgentId = MxAgentId { agentId :: String }
39+ deriving (Typeable, Binary, Eq, Ord)
40+
41+data MxTableId =
42+ MxForAgent !MxAgentId
43+ | MxForPid !ProcessId
44+
45+data MxAgentState s = MxAgentState
46+ {
47+ mxAgentId :: !MxAgentId
48+ -- , mxBus :: !(TChan Message)
49+ , mxSharedTable :: !ProcessId
50+ , mxLocalState :: !s
51+ }
52+
53+newtype MxAgent s a =
54+ MxAgent
55+ {
56+ unAgent :: ST.StateT (MxAgentState s) Process a
57+ } deriving ( Functor
58+ , Monad
59+ , MonadIO
60+ , ST.MonadState (MxAgentState s)
61+ , Typeable
62+ , Applicative
63+ )
64+
65+data MxAgentStart = MxAgentStart
66+ {
67+ mxAgentTableChan :: SendPort ProcessId
68+ , mxAgentIdStart :: MxAgentId
69+ }
70+ deriving (Typeable, Generic)
71+instance Binary MxAgentStart where
72+
src/Control/Distributed/Process/Node.hs+5−5
@@ -56,7 +56,7 @@ import Control.Exception
56 , Exception
56 , Exception
57 , throwTo
57 , throwTo
58 )
58 )
59−import qualified Control.Exception as Exception (Handler(..), catch, catches, finally)
59+import qualified Control.Exception as Exception (Handler(..), catches, finally)
60import Control.Concurrent (forkIO, myThreadId)
60import Control.Concurrent (forkIO, myThreadId)
61import Control.Distributed.Process.Internal.StrictMVar
61import Control.Distributed.Process.Internal.StrictMVar
62 ( newMVar
62 ( newMVar
@@ -188,7 +188,6 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
188import Control.Distributed.Process.Management.Agent
188import Control.Distributed.Process.Management.Agent
189 ( mxAgentController
189 ( mxAgentController
190 )
190 )
191−import System.Environment (getEnv)
192import Unsafe.Coerce
191import Unsafe.Coerce
193
192
194--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
@@ -251,8 +250,8 @@ startMxAgent node = do
251 let fork = forkProcess node
250 let fork = forkProcess node
252 mv <- MVar.newEmptyMVar
251 mv <- MVar.newEmptyMVar
253 pid <- fork $ mxAgentController fork mv
252 pid <- fork $ mxAgentController fork mv
254− (tracer, wqRef, mxNew) <- MVar.takeMVar mv
253+ (tracer', wqRef, mxNew') <- MVar.takeMVar mv
255− return node { localEventBus = (MxEventBus pid tracer wqRef mxNew) }
254+ return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
256
255
257startDefaultTracer :: LocalNode -> IO ()
256startDefaultTracer :: LocalNode -> IO ()
258startDefaultTracer node' = do
257startDefaultTracer node' = do
@@ -353,12 +352,13 @@ forkProcess node proc = modifyMVar (localState node) startProcess
353 }
352 }
354 return (tid', lproc)
353 return (tid', lproc)
355
354
356−
357 -- see note [tracer/forkProcess races]
355 -- see note [tracer/forkProcess races]
358 trace node (MxSpawned pid)
356 trace node (MxSpawned pid)
359
357
360 if lpidCounter lpid == maxBound
358 if lpidCounter lpid == maxBound
361 then do
359 then do
360+ -- TODO: this doesn't look right at all - how do we know
361+ -- that newUnique represents a process id that is available!?
362 newUnique <- randomIO
362 newUnique <- randomIO
363 return ( (localProcessWithId lpid ^= Just lproc)
363 return ( (localProcessWithId lpid ^= Just lproc)
364 . (localPidCounter ^= firstNonReservedProcessId)
364 . (localPidCounter ^= firstNonReservedProcessId)