git.iko.soy/distributed-process
on master help sign in
Remove Mx Data Tables. This API isn't used, is easy to replace with various other packages, and contains races that are leading to failing test cases in CI.
commitf28ea4bf1b86fffda8ad13598c0d82b6ce9a9707 authorTim Watson <watson.timothy@gmail.com> dateWed 2 Mar 2016, 01:17 (11 years ago) parentd0b7885
6 files changed+5−407
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+1−82 distributed-process.cabal−1 src/Control/Distributed/Process/Management.hs+4−79 src/Control/Distributed/Process/Management/Internal/Table.hs−222 src/Control/Distributed/Process/Management/Internal/Types.hs−17 src/Control/Distributed/Process/Node.hs−6
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+1−82
@@ -22,11 +22,6 @@ import Control.Distributed.Process.Management
2222 , mxNotify
2323 , mxBroadcast
2424 , mxGetId
25− , mxGet
26− , mxSet
27− , mxClear
28− , mxPurgeTable
29− , mxDropTable
3025 )
3126import Control.Monad (void)
3227import Data.Binary
@@ -180,61 +175,6 @@ testAgentEventHandling result = do
180175
181176 stash result $ seenAlive && seenDead
182177
183−testAgentPublication :: TestResult (Maybe Publish) -> Process ()
184−testAgentPublication result = do
185− (syncChan, rp) <- newChan
186− let ourId = MxAgentId "publication-agent"
187− agentPid <- mxAgent ourId () [
188− mxSink $ \() -> do
189− selfId <- mxGetId
190− liftMX $ mxSet selfId "publish" Publish >> sendChan syncChan ()
191− mxReady
192− ]
193−
194− mxNotify ()
195− () <- receiveChan rp
196−
197− stash result =<< mxGet ourId "publish"
198−
199− mref <- monitor agentPid
200− kill agentPid "finished"
201− receiveWait [
202− matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
203− ((\_ -> return ()))
204− ]
205−
206−testAgentTableClear :: Int -> TestResult (Maybe Int, Maybe Int) -> Process ()
207−testAgentTableClear val result =
208− let tId = (MxAgentId "agent-1")
209− tKey = "key-1" in do
210− mxSet tId tKey val
211− get1 <- mxGet tId tKey
212− mxClear tId tKey
213− get2 <- mxGet tId tKey
214− stash result (get1, get2)
215−
216−testAgentTablePurge :: TestResult (Maybe Int) -> Process ()
217−testAgentTablePurge result =
218− let tId = MxAgentId "agent-2"
219− tKey = "key-2" in do
220− mxSet tId tKey (12345 :: Int)
221− mxPurgeTable tId
222− stash result =<< mxGet tId tKey
223−
224−testAgentTableDelete :: Int
225− -> TestResult (Maybe Int, Maybe Int, Maybe Int)
226− -> Process ()
227−testAgentTableDelete val result =
228− let tId = (MxAgentId "agent-3")
229− tKey = "key-3" in do
230− mxSet tId tKey val
231− get1 <- mxGet tId tKey
232− mxDropTable tId
233− get2 <- mxGet tId tKey
234− mxSet tId tKey val
235− get3 <- mxGet tId tKey
236− stash result (get1, get2, get3)
237−
238178tests :: TestTransport -> IO [Test]
239179tests TestTransport{..} = do
240180 node1 <- newLocalNode testTransport initRemoteTable
@@ -262,25 +202,4 @@ tests TestTransport{..} = do
262202 node1 (sort ["first", "second",
263203 "third", "fourth",
264204 "fifth"]) testAgentPrioritisation)
265− ],
266− testGroup "Mx Global Properties" [
267− testCase "Global Property Publication"
268− (delayedAssertion
269− "expected (Just Publish), but no table entry was found"
270− node1 (Just Publish) testAgentPublication)
271− , testCase "Clearing Global Properties"
272− (delayedAssertion
273− "expected (Just 1024, Nothing): invalid table entry found!"
274− node1 (Just 1024, Nothing) (testAgentTableClear 1024))
275− , testCase "Purging Global Tables"
276− (delayedAssertion
277− "expected Nothing, but a table entry was found"
278− node1 Nothing testAgentTablePurge)
279− , testCase "Deleting and (Re)Creating Global Tables"
280− (delayedAssertion
281− "expected (Just 15, Nothing, Just 15): invalid table entry found!"
282− node1 (Just 15, Nothing, Just 15) (testAgentTableDelete 15))
283− -- Wait for other tests to finish.
284− , testCase "Wait" $
285− threadDelay 100000
286− ]]
205+ ]]
distributed-process.cabal−1
@@ -75,7 +75,6 @@ Library
7575 Control.Distributed.Process.UnsafePrimitives
7676 Control.Distributed.Process.Management.Internal.Agent,
7777 Control.Distributed.Process.Management.Internal.Bus,
78− Control.Distributed.Process.Management.Internal.Table,
7978 Control.Distributed.Process.Management.Internal.Types,
8079 Control.Distributed.Process.Management.Internal.Trace.Primitives,
8180 Control.Distributed.Process.Management.Internal.Trace.Remote,
src/Control/Distributed/Process/Management.hs+4−79
@@ -83,28 +83,8 @@
8383-- system information. Agents maintain their own internal state privately (via a
8484-- state transformer - see 'mxGetLocal' et al), however it is possible for
8585-- agents to share additional data with each other (and the outside world)
86−-- using /data tables/.
87−--
88−-- Each agent is assigned its own data table, which acts as a shared map, where
89−-- the keys are @String@s and the values are @Serializable@ datum of whatever
90−-- type the agent or its clients stores.
91−--
92−-- Because an agent's /data table/ stores its values in raw 'Message' format,
93−-- it works effectively as an /un-typed dictionary/, into which data of varying
94−-- types can be fed and later retrieved. The upside of this is that different
95−-- keys can be mapped to various types without any additional work on the part
96−-- of the developer. The downside is that the code reading these values must
97−-- know in advance what type(s) to expect, and the API provides no additional
98−-- support for handling that.
99−--
100−-- Publishing is accomplished using the 'mxPublish' and 'mxSet' APIs, whilst
101−-- querying and deletion are handled by 'mxGet', 'mxClear', 'mxPurgeTable' and
102−-- 'mxDropTable' respectively.
103−--
104−-- When a management agent terminates, their tables are left in memory despite
105−-- termination, such that an agent may resume its role (by restarting) or have
106−-- its 'MxAgentId' taken over by another subsequent agent, leaving the data
107−-- originally captured in place.
86+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
87+-- primitives.
10888--
10989-- [Defining Agents]
11090--
@@ -194,10 +174,6 @@
194174-- the event bus /and/ their own mailboxes, plus searching through the set of
195175-- event sinks (for each agent) to determine the right handler for the event.
196176--
197−-- Each management agent requires not only its own @Process@ (in which the agent
198−-- code is run), but also a peer process that provides its /data table/. These
199−-- data tables also have to be coordinated and manaaged on each agent's behalf.
200−--
201177-- [Architecture Overview]
202178--
203179-- The architecture of the management event bus is internal and subject to
@@ -264,13 +240,6 @@ module Control.Distributed.Process.Management
264240 , mxGetLocal
265241 , mxUpdateLocal
266242 , liftMX
267− -- * Mx Data API
268− , mxPublish
269− , mxSet
270− , mxGet
271− , mxClear
272− , mxPurgeTable
273− , mxDropTable
274243 ) where
275244
276245import Control.Applicative ((<$>))
@@ -281,10 +250,7 @@ import Control.Concurrent.STM.TChan
281250 , TChan
282251 )
283252import Control.Distributed.Process.Internal.Primitives
284− ( newChan
285− , nsend
286− , receiveWait
287− , matchChan
253+ ( receiveWait
288254 , matchAny
289255 , matchSTM
290256 , unwrapMessage
@@ -303,14 +269,12 @@ import Control.Distributed.Process.Internal.Types
303269 , unsafeCreateUnencodedMessage
304270 )
305271import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
306−import qualified Control.Distributed.Process.Management.Internal.Table as Table
307272import Control.Distributed.Process.Management.Internal.Types
308273 ( MxAgentId(..)
309274 , MxAgent(..)
310275 , MxAction(..)
311276 , ChannelSelector(..)
312277 , MxAgentState(..)
313− , MxAgentStart(..)
314278 , MxSink
315279 , MxEvent(..)
316280 )
@@ -334,42 +298,6 @@ mxNotify msg = do
334298 bus <- localEventBus . processNode <$> ask
335299 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
336300
337−-- | Publish an arbitrary @Message@ as a property in the management database.
338−--
339−-- For publishing @Serializable@ data, use 'mxSet' instead.
340−--
341−mxPublish :: MxAgentId -> String -> Message -> Process ()
342−mxPublish a k v = Table.set k v (Table.MxForAgent a)
343−
344−-- | Sets an arbitrary @Serializable@ datum against a key in the management
345−-- database. Note that /no attempt is made to force the argument/, therefore
346−-- it is very important that you do not pass unevaluated thunks that might
347−-- crash some other, arbitrary process (or management agent!) that obtains
348−-- and attempts to force the value later on.
349−--
350−mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()
351−mxSet mxId key msg = do
352− Table.set key (unsafeCreateUnencodedMessage msg) (Table.MxForAgent mxId)
353−
354−-- | Fetches a property from the management database for the given key.
355−-- If the property is not set, or does not match the expected type when
356−-- typechecked (at runtime), returns @Nothing@.
357−mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)
358−mxGet = Table.fetch . Table.MxForAgent
359−
360−-- | Clears a property from the management database using the given key.
361−-- If the key does not exist in the database, this is a noop.
362−mxClear :: MxAgentId -> String -> Process ()
363−mxClear mxId key = Table.clear key (Table.MxForAgent mxId)
364−
365−-- | Purges a table in the management database of all its stored properties.
366−mxPurgeTable :: MxAgentId -> Process ()
367−mxPurgeTable = Table.purge . Table.MxForAgent
368−
369−-- | Deletes a table from the management database.
370−mxDropTable :: MxAgentId -> Process ()
371−mxDropTable = Table.delete . Table.MxForAgent
372−
373301--------------------------------------------------------------------------------
374302-- API for writing user defined management extensions (i.e., agents) --
375303--------------------------------------------------------------------------------
@@ -489,10 +417,7 @@ mxAgentWithFinalize mxId initState handlers dtor = do
489417 return pid
490418 where
491419 start (sendTChan, recvTChan) = do
492− (sp, rp) <- newChan
493− nsend Table.mxTableCoordinator (MxAgentStart sp mxId)
494− tablePid <- receiveWait [ matchChan rp (\(p :: ProcessId) -> return p) ]
495− let nState = MxAgentState mxId sendTChan tablePid initState
420+ let nState = MxAgentState mxId sendTChan initState
496421 runAgent dtor handlers InputChan recvTChan nState
497422
498423 runAgent :: MxAgent s ()
src/Control/Distributed/Process/Management/Internal/Table.hsdeleted−222
@@ -1,222 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE ScopedTypeVariables #-}
3−{-# LANGUAGE RankNTypes #-}
4−{-# LANGUAGE DeriveGeneric #-}
5−{-# LANGUAGE RecordWildCards #-}
6−
7−module Control.Distributed.Process.Management.Internal.Table
8− ( MxTableRequest(..)
9− , MxTableId(..)
10− , mxTableCoordinator
11− , startTableCoordinator
12− , delete
13− , purge
14− , clear
15− , set
16− , get
17− , fetch
18− ) where
19−
20−import Control.Distributed.Process.Internal.Primitives
21− ( receiveWait
22− , receiveChan
23− , match
24− , matchAny
25− , matchIf
26− , matchChan
27− , send
28− , nsend
29− , sendChan
30− , getSelfPid
31− , link
32− , monitor
33− , unwrapMessage
34− , newChan
35− , withMonitor
36− )
37−import Control.Distributed.Process.Internal.Types
38− ( Process
39− , ProcessId
40− , ProcessMonitorNotification(..)
41− , SendPort
42− , ReceivePort
43− , Message
44− , unsafeCreateUnencodedMessage
45− )
46−import Control.Distributed.Process.Management.Internal.Types
47− ( MxTableId(..)
48− , MxAgentId(..)
49− , MxAgentStart(..)
50− , Fork)
51−import Control.Distributed.Process.Serializable (Serializable)
52−import Control.Monad.IO.Class (liftIO)
53−import Data.Accessor (Accessor, accessor, (^=), (^:))
54−import Data.Binary (Binary)
55−import Data.Map (Map)
56−import qualified Data.Map as Map
57−import Data.Typeable (Typeable)
58−
59−import GHC.Generics
60−
61−-- An extremely lightweight shared Map implementation, for use
62−-- by /management agents/ and their cohorts. Each agent is assigned
63−-- a table, into which any serializable @Message@ can be inserted.
64−-- Data are inserted, removed and searched for via their key, which
65−-- is a string. Tables can be purged, values can be set, fetched or
66−-- cleared/removed.
67−--
68−
69−data MxTableRequest =
70− Delete
71− | Purge
72− | Clear !String
73− | Set !String !Message
74− | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]
75− deriving (Typeable, Generic)
76−instance Binary MxTableRequest where
77−
78−data MxTableState = MxTableState { _name :: !String
79− , _entries :: !(Map String Message)
80− }
81−
82−type MxTables = Map MxAgentId ProcessId
83−
84−mxTableCoordinator :: String
85−mxTableCoordinator = "mx.table.coordinator"
86−
87−delete :: MxTableId -> Process ()
88−delete = sendReq Delete
89−
90−purge :: MxTableId -> Process ()
91−purge = sendReq Purge
92−
93−clear :: String -> MxTableId -> Process ()
94−clear k = sendReq (Clear k)
95−
96−set :: String -> Message -> MxTableId -> Process ()
97−set k v = sendReq (Set k v)
98−
99−fetch :: forall a. (Serializable a)
100− => MxTableId
101− -> String
102− -> Process (Maybe a)
103−fetch (MxForPid pid) key = get pid key
104−fetch mxId@(MxForAgent _) key = do
105− (sp, rp) <- newChan :: Process (SendPort (Maybe Message),
106− ReceivePort (Maybe Message))
107− sendReq (Get key sp) mxId
108− receiveChan rp >>= maybe (return Nothing)
109− (unwrapMessage :: Message -> Process (Maybe a))
110−
111−-- [note: un-typed send port]
112−-- Here, fetch uses a typed channel over a raw Message to obtain
113−-- its result, so type checking is deferred until receipt and will
114−-- be handled in the caller's thread. This is necessary because
115−-- the server portion of the code knows nothing about the types
116−-- involved, nor should it, since these tables can be used to
117−-- store arbitrary serializable data.
118−
119−get :: forall a. (Serializable a)
120− => ProcessId
121− -> String
122− -> Process (Maybe a)
123−get pid key = do
124− safeFetch pid key >>= maybe (return Nothing)
125− (unwrapMessage :: Message -> Process (Maybe a))
126−
127−safeFetch :: ProcessId -> String -> Process (Maybe Message)
128−safeFetch pid key = do
129− (sp, rp) <- newChan
130− send pid $ Get key sp
131− withMonitor pid $ do
132− receiveWait [
133− matchChan rp return
134− , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)
135− (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))
136− ]
137−
138−sendReq :: MxTableRequest -> MxTableId -> Process ()
139−sendReq req tid = (resolve tid) req
140−
141−resolve :: Serializable a => MxTableId -> (a -> Process ())
142−resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)
143−resolve (MxForPid pid) = \msg -> send pid msg
144−
145−startTableCoordinator :: Fork -> Process ()
146−startTableCoordinator fork = run Map.empty
147− where
148− run :: MxTables -> Process ()
149− run tables =
150− receiveWait [
151− -- note that this state change can race with MxAgentStart requests
152− match (\(ProcessMonitorNotification _ pid _) -> do
153− return $ Map.filter (/= pid) tables)
154− , match (\(MxAgentStart ch agent) -> do
155− lookupAgent tables agent >>= \(p, t) -> do
156− sendChan ch p >> return t)
157− , match (\req@(agent, tReq :: MxTableRequest) -> do
158− case tReq of
159− Get k sp -> do
160− lookupAgent tables agent >>= \(p, t) -> do
161− safeFetch p k >>= sendChan sp >> return t
162− _ -> do
163− handleRequest tables req)
164− , matchAny (\_ -> return tables) -- unrecognised messages are dropped
165− ] >>= run
166−
167− handleRequest :: MxTables
168− -> (MxAgentId, MxTableRequest)
169− -> Process MxTables
170− handleRequest tables' (agent, req) = do
171− lookupAgent tables' agent >>= \(p, t) -> send p req >> return t
172−
173− lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)
174− lookupAgent tables' agentId' = do
175− case Map.lookup agentId' tables' of
176− Nothing -> launchNew agentId' tables'
177− Just p -> return (p, tables')
178−
179− launchNew :: MxAgentId
180− -> MxTables
181− -> Process (ProcessId, MxTables)
182− launchNew mxId tblMap = do
183− let initState = MxTableState { _name = (agentId mxId)
184− , _entries = Map.empty
185− }
186− (pid, _) <- spawnSup $ tableHandler initState
187− return $ (pid, mxId `seq` pid `seq` Map.insert mxId pid tblMap)
188−
189− spawnSup proc = do
190− us <- getSelfPid
191− -- we need to use that passed in "fork", in order to
192− -- break an import cycle with Node.hs courtesy of the
193− -- management agent, API and tracing modules
194− them <- liftIO $ fork $ 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 (handleTableRequest state)
202− , matchAny (\_ -> return (Just state))
203− ]
204− case ns of
205− Nothing -> return ()
206− Just s' -> tableHandler s'
207− where
208− handleTableRequest _ Delete = return Nothing
209− handleTableRequest st Purge = return $ Just $ (entries ^= Map.empty) $ st
210− handleTableRequest st (Clear k) = return $ Just $ (entries ^: (k `seq` Map.delete k)) $ st
211− handleTableRequest st (Set k v) = return $ Just $ (entries ^: (k `seq` v `seq` Map.insert k v)) st
212− handleTableRequest st (Get k c) = getEntry k c st >> return (Just st)
213−
214−getEntry :: String
215− -> SendPort (Maybe Message)
216− -> MxTableState
217− -> Process ()
218−getEntry k m MxTableState{..} = do
219− sendChan m =<< return (Map.lookup k _entries)
220−
221−entries :: Accessor MxTableState (Map String Message)
222−entries = accessor _entries (\ls st -> st { _entries = ls })
src/Control/Distributed/Process/Management/Internal/Types.hs−17
@@ -5,12 +5,10 @@
55{-# LANGUAGE DeriveGeneric #-}
66module Control.Distributed.Process.Management.Internal.Types
77 ( MxAgentId(..)
8− , MxTableId(..)
98 , MxAgentState(..)
109 , MxAgent(..)
1110 , MxAction(..)
1211 , ChannelSelector(..)
13− , MxAgentStart(..)
1412 , Fork
1513 , MxSink
1614 , MxEvent(..)
@@ -100,17 +98,10 @@ type Fork = (Process () -> IO ProcessId)
10098newtype MxAgentId = MxAgentId { agentId :: String }
10199 deriving (Typeable, Binary, Eq, Ord)
102100
103−data MxTableId =
104− MxForAgent !MxAgentId
105− | MxForPid !ProcessId
106− deriving (Typeable, Generic)
107−instance Binary MxTableId where
108−
109101data MxAgentState s = MxAgentState
110102 {
111103 mxAgentId :: !MxAgentId
112104 , mxBus :: !(TChan Message)
113− , mxSharedTable :: !ProcessId
114105 , mxLocalState :: !s
115106 }
116107
@@ -128,14 +119,6 @@ newtype MxAgent s a =
128119 , Applicative
129120 )
130121
131−data MxAgentStart = MxAgentStart
132− {
133− mxAgentTableChan :: SendPort ProcessId
134− , mxAgentIdStart :: MxAgentId
135− }
136− deriving (Typeable, Generic)
137−instance Binary MxAgentStart where
138−
139122data ChannelSelector = InputChan | Mailbox
140123
141124-- | Represents the actions a management agent can take
src/Control/Distributed/Process/Node.hs−6
@@ -177,10 +177,6 @@ import Control.Distributed.Process.Internal.Types
177177import Control.Distributed.Process.Management.Internal.Agent
178178 ( mxAgentController
179179 )
180−import qualified Control.Distributed.Process.Management.Internal.Table as Table
181− ( mxTableCoordinator
182− , startTableCoordinator
183− )
184180import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
185181 ( remoteTable
186182 )
@@ -304,8 +300,6 @@ startServiceProcesses node = do
304300 -- before /that/ process has started - this is a totally harmless race
305301 -- however, so we deliberably ignore it
306302 startDefaultTracer node
307− tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
308− runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
309303 logger <- forkProcess node loop
310304 runProcess node $ do
311305 register "logger" logger