1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE RecordWildCards #-}
4module Control.Distributed.Process.Management.Table
8 , startTableCoordinator
17import Control.Distributed.Process.Internal.Primitives
34import Control.Distributed.Process.Internal.Types
37 , ProcessMonitorNotification(..)
41 , unsafeCreateUnencodedMessage
43import Control.Distributed.Process.Management.Types
48import Control.Distributed.Process.Serializable (Serializable)
49import Control.Monad.IO.Class (liftIO)
50import Data.Accessor (Accessor, accessor, (^=), (^:))
51import Data.Binary (Binary)
52import Data.Map.Strict (Map)
53import qualified Data.Map.Strict as Map
54import Data.Typeable (Typeable)
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
70 | Set !String !Message
71 | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]
72 deriving (Typeable, Generic)
73instance Binary MxTableRequest where
75data MxTableState = MxTableState { _name :: !String
76 , _entries :: !(Map String Message)
79type MxTables = Map MxAgentId ProcessId
81mxTableCoordinator :: String
82mxTableCoordinator = "mx.table.coordinator"
84delete :: MxTableId -> Process ()
85delete = sendReq Delete
87purge :: MxTableId -> Process ()
90clear :: String -> MxTableId -> Process ()
91clear k = sendReq (Clear k)
93set :: String -> Message -> MxTableId -> Process ()
94set k v = sendReq (Set k v)
96fetch :: forall a. (Serializable a)
100fetch (MxForPid pid) key = get pid key
101fetch mxId@(MxForAgent _) key = do
102 (sp, rp) <- newChan :: Process (SendPort (Maybe Message),
103 ReceivePort (Maybe Message))
104 sendReq (Get key sp) mxId
105 receiveChan rp >>= maybe (return Nothing)
106 (unwrapMessage :: Message -> Process (Maybe a))
108-- [note: un-typed send port]
109-- Here, fetch uses a typed channel over a raw Message to obtain
110-- its result, so type checking is deferred until receipt and will
111-- be handled in the caller's thread. This is necessary because
112-- the server portion of the code knows nothing about the types
113-- involved, nor should it, since these tables can be used to
114-- store arbitrary serializable data.
116get :: forall a. (Serializable a)
121 safeFetch pid key >>= maybe (return Nothing)
122 (unwrapMessage :: Message -> Process (Maybe a))
124safeFetch :: ProcessId -> String -> Process (Maybe Message)
125safeFetch pid key = do
127 send pid $ Get key sp
131 , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)
132 (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))
135sendReq :: MxTableRequest -> MxTableId -> Process ()
136sendReq req tid = (resolve tid) req
138resolve :: Serializable a => MxTableId -> (a -> Process ())
139resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)
140resolve (MxForPid pid) = \msg -> send pid msg
142startTableCoordinator :: Fork -> Process ()
143startTableCoordinator fork = run Map.empty
145 run :: MxTables -> Process ()
148 -- note that this state change can race with MxAgentStart requests
149 match (\(ProcessMonitorNotification _ pid _) -> do
150 return $ Map.filter (/= pid) tables)
151 , match (\(MxAgentStart ch agent) -> do
152 lookupAgent tables agent >>= \(p, t) -> do
153 sendChan ch p >> return t)
154 , match (\req@(agent, tReq :: MxTableRequest) -> do
157 lookupAgent tables agent >>= \(p, t) -> do
158 safeFetch p k >>= sendChan sp >> return t
160 handleRequest tables req)
161 , matchAny (\_ -> return tables) -- unrecognised messages are dropped
164 handleRequest :: MxTables
165 -> (MxAgentId, MxTableRequest)
167 handleRequest tables' (agent, req) = do
168 lookupAgent tables' agent >>= \(p, t) -> send p req >> return t
170 lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)
171 lookupAgent tables' agentId' = do
172 case Map.lookup agentId' tables' of
173 Nothing -> launchNew agentId' tables'
174 Just p -> return (p, tables')
176 launchNew :: MxAgentId
178 -> Process (ProcessId, MxTables)
179 launchNew mxId tblMap = do
180 let initState = MxTableState { _name = (agentId mxId)
181 , _entries = Map.empty
183 (pid, _) <- spawnSup $ tableHandler initState
184 return $ (pid, Map.insert mxId pid tblMap)
188 -- we need to use that passed in "fork", in order to
189 -- break an import cycle with Node.hs courtesy of the
190 -- management agent, API and tracing modules
191 them <- liftIO $ fork $ link us >> proc
195tableHandler :: MxTableState -> Process ()
196tableHandler state = do
198 match (handleTableRequest state)
199 , matchAny (\_ -> return (Just state))
203 Just s' -> tableHandler s'
205 handleTableRequest _ Delete = return Nothing
206 handleTableRequest st Purge = return $ Just $ (entries ^= Map.empty) $ st
207 handleTableRequest st (Clear k) = return $ Just $ (entries ^: Map.delete k) $ st
208 handleTableRequest st (Set k v) = return $ Just $ (entries ^: Map.insert k v) st
209 handleTableRequest st (Get k c) = getEntry k c st >> return (Just st)
212 -> SendPort (Maybe Message)
215getEntry k m MxTableState{..} = do
216 sendChan m =<< return (Map.lookup k _entries)
218entries :: Accessor MxTableState (Map String Message)
219entries = accessor _entries (\ls st -> st { _entries = ls })
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