git.iko.soy/distributed-process
on master help sign in
Merge pull request #276 from haskell-distributed/remove-table-api
286c54cFacundo Domínguez
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+1−82
@@ -22,11 +22,6 @@ import Control.Distributed.Process.Management
22 , mxNotify
22 , mxNotify
23 , mxBroadcast
23 , mxBroadcast
24 , mxGetId
24 , mxGetId
25− , mxGet
26− , mxSet
27− , mxClear
28− , mxPurgeTable
29− , mxDropTable
30 )
25 )
31import Control.Monad (void)
26import Control.Monad (void)
32import Data.Binary
27import Data.Binary
@@ -180,61 +175,6 @@ testAgentEventHandling result = do
180
175
181 stash result $ seenAlive && seenDead
176 stash result $ seenAlive && seenDead
182
177
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−
238tests :: TestTransport -> IO [Test]
178tests :: TestTransport -> IO [Test]
239tests TestTransport{..} = do
179tests TestTransport{..} = do
240 node1 <- newLocalNode testTransport initRemoteTable
180 node1 <- newLocalNode testTransport initRemoteTable
@@ -262,25 +202,4 @@ tests TestTransport{..} = do
262 node1 (sort ["first", "second",
202 node1 (sort ["first", "second",
263 "third", "fourth",
203 "third", "fourth",
264 "fifth"]) testAgentPrioritisation)
204 "fifth"]) testAgentPrioritisation)
265− ],
205+ ]]
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− ]]
distributed-process.cabal−1
@@ -76,7 +76,6 @@ Library
76 Control.Distributed.Process.UnsafePrimitives
76 Control.Distributed.Process.UnsafePrimitives
77 Control.Distributed.Process.Management.Internal.Agent,
77 Control.Distributed.Process.Management.Internal.Agent,
78 Control.Distributed.Process.Management.Internal.Bus,
78 Control.Distributed.Process.Management.Internal.Bus,
79− Control.Distributed.Process.Management.Internal.Table,
80 Control.Distributed.Process.Management.Internal.Types,
79 Control.Distributed.Process.Management.Internal.Types,
81 Control.Distributed.Process.Management.Internal.Trace.Primitives,
80 Control.Distributed.Process.Management.Internal.Trace.Primitives,
82 Control.Distributed.Process.Management.Internal.Trace.Remote,
81 Control.Distributed.Process.Management.Internal.Trace.Remote,
src/Control/Distributed/Process/Management.hs+4−79
@@ -83,28 +83,8 @@
83-- system information. Agents maintain their own internal state privately (via a
83-- system information. Agents maintain their own internal state privately (via a
84-- state transformer - see 'mxGetLocal' et al), however it is possible for
84-- state transformer - see 'mxGetLocal' et al), however it is possible for
85-- agents to share additional data with each other (and the outside world)
85-- agents to share additional data with each other (and the outside world)
86−-- using /data tables/.
86+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
87−--
87+-- primitives.
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.
108--
88--
109-- [Defining Agents]
89-- [Defining Agents]
110--
90--
@@ -194,10 +174,6 @@
194-- the event bus /and/ their own mailboxes, plus searching through the set of
174-- the event bus /and/ their own mailboxes, plus searching through the set of
195-- event sinks (for each agent) to determine the right handler for the event.
175-- event sinks (for each agent) to determine the right handler for the event.
196--
176--
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−--
201-- [Architecture Overview]
177-- [Architecture Overview]
202--
178--
203-- The architecture of the management event bus is internal and subject to
179-- The architecture of the management event bus is internal and subject to
@@ -264,13 +240,6 @@ module Control.Distributed.Process.Management
264 , mxGetLocal
240 , mxGetLocal
265 , mxUpdateLocal
241 , mxUpdateLocal
266 , liftMX
242 , liftMX
267− -- * Mx Data API
268− , mxPublish
269− , mxSet
270− , mxGet
271− , mxClear
272− , mxPurgeTable
273− , mxDropTable
274 ) where
243 ) where
275
244
276import Control.Applicative ((<$>))
245import Control.Applicative ((<$>))
@@ -281,10 +250,7 @@ import Control.Concurrent.STM.TChan
281 , TChan
250 , TChan
282 )
251 )
283import Control.Distributed.Process.Internal.Primitives
252import Control.Distributed.Process.Internal.Primitives
284− ( newChan
285− , nsend
286− , receiveWait
253+ ( receiveWait
287− , matchChan
288 , matchAny
254 , matchAny
289 , matchSTM
255 , matchSTM
290 , unwrapMessage
256 , unwrapMessage
@@ -303,14 +269,12 @@ import Control.Distributed.Process.Internal.Types
303 , unsafeCreateUnencodedMessage
269 , unsafeCreateUnencodedMessage
304 )
270 )
305import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
271import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
306−import qualified Control.Distributed.Process.Management.Internal.Table as Table
307import Control.Distributed.Process.Management.Internal.Types
272import Control.Distributed.Process.Management.Internal.Types
308 ( MxAgentId(..)
273 ( MxAgentId(..)
309 , MxAgent(..)
274 , MxAgent(..)
310 , MxAction(..)
275 , MxAction(..)
311 , ChannelSelector(..)
276 , ChannelSelector(..)
312 , MxAgentState(..)
277 , MxAgentState(..)
313− , MxAgentStart(..)
314 , MxSink
278 , MxSink
315 , MxEvent(..)
279 , MxEvent(..)
316 )
280 )
@@ -334,42 +298,6 @@ mxNotify msg = do
334 bus <- localEventBus . processNode <$> ask
298 bus <- localEventBus . processNode <$> ask
335 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
299 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
336
300
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−
373--------------------------------------------------------------------------------
301--------------------------------------------------------------------------------
374-- API for writing user defined management extensions (i.e., agents) --
302-- API for writing user defined management extensions (i.e., agents) --
375--------------------------------------------------------------------------------
303--------------------------------------------------------------------------------
@@ -489,10 +417,7 @@ mxAgentWithFinalize mxId initState handlers dtor = do
489 return pid
417 return pid
490 where
418 where
491 start (sendTChan, recvTChan) = do
419 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
496 runAgent dtor handlers InputChan recvTChan nState
421 runAgent dtor handlers InputChan recvTChan nState
497
422
498 runAgent :: MxAgent s ()
423 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 @@
5{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE DeriveGeneric #-}
6module Control.Distributed.Process.Management.Internal.Types
6module Control.Distributed.Process.Management.Internal.Types
7 ( MxAgentId(..)
7 ( MxAgentId(..)
8− , MxTableId(..)
9 , MxAgentState(..)
8 , MxAgentState(..)
10 , MxAgent(..)
9 , MxAgent(..)
11 , MxAction(..)
10 , MxAction(..)
12 , ChannelSelector(..)
11 , ChannelSelector(..)
13− , MxAgentStart(..)
14 , Fork
12 , Fork
15 , MxSink
13 , MxSink
16 , MxEvent(..)
14 , MxEvent(..)
@@ -100,17 +98,10 @@ type Fork = (Process () -> IO ProcessId)
100newtype MxAgentId = MxAgentId { agentId :: String }
98newtype MxAgentId = MxAgentId { agentId :: String }
101 deriving (Typeable, Binary, Eq, Ord)
99 deriving (Typeable, Binary, Eq, Ord)
102
100
103−data MxTableId =
104− MxForAgent !MxAgentId
105− | MxForPid !ProcessId
106− deriving (Typeable, Generic)
107−instance Binary MxTableId where
108−
109data MxAgentState s = MxAgentState
101data MxAgentState s = MxAgentState
110 {
102 {
111 mxAgentId :: !MxAgentId
103 mxAgentId :: !MxAgentId
112 , mxBus :: !(TChan Message)
104 , mxBus :: !(TChan Message)
113− , mxSharedTable :: !ProcessId
114 , mxLocalState :: !s
105 , mxLocalState :: !s
115 }
106 }
116
107
@@ -128,14 +119,6 @@ newtype MxAgent s a =
128 , Applicative
119 , Applicative
129 )
120 )
130
121
131−data MxAgentStart = MxAgentStart
132− {
133− mxAgentTableChan :: SendPort ProcessId
134− , mxAgentIdStart :: MxAgentId
135− }
136− deriving (Typeable, Generic)
137−instance Binary MxAgentStart where
138−
139data ChannelSelector = InputChan | Mailbox
122data ChannelSelector = InputChan | Mailbox
140
123
141-- | Represents the actions a management agent can take
124-- | 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
177import Control.Distributed.Process.Management.Internal.Agent
177import Control.Distributed.Process.Management.Internal.Agent
178 ( mxAgentController
178 ( mxAgentController
179 )
179 )
180−import qualified Control.Distributed.Process.Management.Internal.Table as Table
181− ( mxTableCoordinator
182− , startTableCoordinator
183− )
184import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
180import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
185 ( remoteTable
181 ( remoteTable
186 )
182 )
@@ -304,8 +300,6 @@ startServiceProcesses node = do
304 -- before /that/ process has started - this is a totally harmless race
300 -- before /that/ process has started - this is a totally harmless race
305 -- however, so we deliberably ignore it
301 -- however, so we deliberably ignore it
306 startDefaultTracer node
302 startDefaultTracer node
307− tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
308− runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
309 logger <- forkProcess node loop
303 logger <- forkProcess node loop
310 runProcess node $ do
304 runProcess node $ do
311 register "logger" logger
305 register "logger" logger