git.iko.soy/distributed-process
on master help sign in
Split the node management event bus up from the trace controller
commite2d0cf7ea17be5d12e60bdb7b1de98561b11753c authorTim Watson <watson.timothy@gmail.com> dateTue 16 Jul 2013, 17:53 (13 years ago) parent5a3da9c
11 files changed+370−140
distributed-process.cabal+11−2 src/Control/Distributed/Process/Internal/CQueue.hs+6−1 src/Control/Distributed/Process/Internal/Primitives.hs+2−2 src/Control/Distributed/Process/Internal/Trace/Primitives.hs+22−12 src/Control/Distributed/Process/Internal/Trace/Tracer.hs+11−27 src/Control/Distributed/Process/Internal/Trace/Types.hs+30−33 src/Control/Distributed/Process/Internal/Types.hs+19−16 src/Control/Distributed/Process/Management.hs+75 src/Control/Distributed/Process/Management/Agent.hs+141 src/Control/Distributed/Process/Management/Bus.hs+21 src/Control/Distributed/Process/Node.hs+32−47
distributed-process.cabal+11−2
@@ -64,10 +64,13 @@ Library
6464 Control.Distributed.Process.Closure,
6565 Control.Distributed.Process.Debug,
6666 Control.Distributed.Process.Internal.Types,
67+ Control.Distributed.Process.Management,
68+ Control.Distributed.Process.Management.Agent,
6769 Control.Distributed.Process.Node,
6870 Control.Distributed.Process.Serializable,
6971 Control.Distributed.Process.UnsafePrimitives
7072 Other-modules: Control.Distributed.Process.Internal.Primitives,
73+ Control.Distributed.Process.Management.Bus,
7174 Control.Distributed.Process.Internal.Spawn,
7275 Control.Distributed.Process.Internal.Primitives,
7376 Control.Distributed.Process.Internal.CQueue,
@@ -180,10 +183,16 @@ Test-Suite TestTracing
180183 test-framework >= 0.6 && < 0.9,
181184 test-framework-hunit >= 0.2.0 && < 0.4,
182185 unix >= 2.5.0.0
183− Extensions: CPP,
186+ Extensions: RankNTypes,
184187 ScopedTypeVariables,
188+ FlexibleInstances,
189+ UndecidableInstances,
190+ ExistentialQuantification,
191+ GADTs,
192+ GeneralizedNewtypeDeriving,
185193 DeriveDataTypeable,
186− GeneralizedNewtypeDeriving
194+ CPP,
195+ BangPatterns
187196 ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
188197 HS-Source-Dirs: tests
189198
src/Control/Distributed/Process/Internal/CQueue.hs+6−1
@@ -6,6 +6,7 @@ module Control.Distributed.Process.Internal.CQueue
66 , MatchOn(..)
77 , newCQueue
88 , enqueue
9+ , enqueueSTM
910 , dequeue
1011 , mkWeakCQueue
1112 ) where
@@ -52,7 +53,11 @@ newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan
5253--
5354-- Enqueue is strict.
5455enqueue :: CQueue a -> a -> IO ()
55−enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
56+enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
57+
58+-- | Variant of enqueue for use in the STM monad.
59+enqueueSTM :: CQueue a -> a -> STM ()
60+enqueueSTM (CQueue _arrived incoming) !a = writeTChan incoming a
5661
5762data BlockSpec =
5863 NonBlocking
src/Control/Distributed/Process/Internal/Primitives.hs+2−2
@@ -226,7 +226,7 @@ send them msg = do
226226 msg
227227 -- We do not fire the trace event until after the sending is complete;
228228 -- In the remote case, 'sendMessage' can block in the networking stack.
229− liftIO $ traceEvent (localTracer node)
229+ liftIO $ traceEvent (localEventBus node)
230230 (TraceEvSent them us (createUnencodedMessage msg))
231231
232232-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
@@ -410,7 +410,7 @@ forward msg them = do
410410 (messageToPayload msg)
411411 -- We do not fire the trace event until after the sending is complete;
412412 -- In the remote case, 'sendMessage' can block in the networking stack.
413− liftIO $ traceEvent (localTracer node)
413+ liftIO $ traceEvent (localEventBus node)
414414 (TraceEvSent them us msg)
415415
416416
src/Control/Distributed/Process/Internal/Trace/Primitives.hs+22−12
@@ -3,6 +3,8 @@
33-- the messaging primitives that rely on it, and also between the node
44-- controller (which requires access to the tracing related elements of
55-- our RemoteTable) and the Debug module, which requires @forkProcess@.
6+-- This module is also used by the management agent, which relies on the
7+-- tracing infrastructure's messaging fabric.
68module Control.Distributed.Process.Internal.Trace.Primitives
79 ( Tracer
810 , TraceEvent(..)
@@ -59,21 +61,23 @@ import Control.Distributed.Process.Internal.Types
5961 , Process
6062 , ProcessId
6163 , LocalProcess(..)
62− , LocalNode(localTracer)
64+ , LocalNode(localEventBus)
6365 , SendPort
66+ , MxEventBus(..)
6467 )
6568import Control.Distributed.Process.Serializable
6669import Control.Monad.IO.Class (liftIO)
6770import Control.Monad.Reader (ask)
6871
72+import Data.Maybe (isJust)
6973import qualified Data.Set as Set (fromList)
7074
7175--------------------------------------------------------------------------------
7276-- Main API --
7377--------------------------------------------------------------------------------
7478
75−-- | Maps a list of identifiers that can be mapped to process ids,
76−-- to a 'TraceSubject'.
79+-- | Converts a list of identifiers (that can be
80+-- mapped to process ids), to a 'TraceSubject'.
7781class Traceable a where
7882 uod :: [a] -> TraceSubject
7983
@@ -98,10 +102,10 @@ traceOff = Nothing
98102-- | Determine whether or not tracing has been enabled.
99103isTracingEnabled :: Process Bool
100104isTracingEnabled = do
101− node <- processNode <$> ask
102− case (localTracer node) of
103− (ActiveTracer _ _) -> return True
104− _ -> return False
105+ processNode <$> ask >>= return . isActive . tracer . localEventBus
106+ where
107+ isActive InactiveTracer = False
108+ isActive (ActiveTracer _ _) = True
105109
106110-- | Enable tracing to the supplied process.
107111enableTraceAsync :: ProcessId -> Process ()
@@ -157,22 +161,28 @@ traceLogFmt d ls = withLocalTracer $ \t -> liftIO $ Tracer.traceLogFmt t d ls
157161traceMessage :: Serializable m => m -> Process ()
158162traceMessage msg = withLocalTracer $ \t -> liftIO $ Tracer.traceMessage t msg
159163
160−withLocalTracer :: (Tracer -> Process ()) -> Process ()
164+withLocalTracer :: (MxEventBus -> Process ()) -> Process ()
161165withLocalTracer act = do
162166 node <- processNode <$> ask
163− act (localTracer node)
167+ act (localEventBus node)
164168
165−withLocalTracerSync :: (Tracer -> SendPort TraceOk -> IO ()) -> Process ()
169+withLocalTracerSync :: (MxEventBus -> SendPort TraceOk -> IO ()) -> Process ()
166170withLocalTracerSync act = do
167171 (sp, rp) <- newChan
168− withLocalTracer $ \t -> liftIO $ (act t sp)
172+ withLocalTracer $ \t -> do
173+ case (tracer t) of
174+ InactiveTracer -> error "TraceDisabled"
175+ _ -> liftIO $ (act t sp)
169176 TraceOk <- receiveChan rp
170177 return ()
171178
172179withRegisteredTracer :: (ProcessId -> Process a) -> Process a
173180withRegisteredTracer act = do
174181 (sp, rp) <- newChan
175− withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
182+ withLocalTracer $ \t -> do
183+ case (tracer t) of
184+ InactiveTracer -> error "TraceDisabled"
185+ _ -> liftIO $ Tracer.getCurrentTraceClient t sp
176186 currentTracer <- receiveChan rp
177187 case currentTracer of
178188 Nothing -> do { (Just p') <- whereis "tracer.initial"; act p' }
src/Control/Distributed/Process/Internal/Trace/Tracer.hs+11−27
@@ -1,6 +1,6 @@
11-- | Tracing/Debugging support - Trace Implementation
22module Control.Distributed.Process.Internal.Trace.Tracer
3− ( -- * API for the Node Controller
3+ ( -- * API for the Management Agent
44 traceController
55 -- * Built in tracers
66 , defaultTracer
@@ -89,7 +89,9 @@ import System.Mem.Weak
8989 ( Weak
9090 )
9191
92−data TracerState = TracerST {
92+data TracerState =
93+ TracerST
94+ {
9395 client :: !(Maybe ProcessId)
9496 , flags :: !TraceFlags
9597 , regNames :: !(Map ProcessId (Set String))
@@ -119,7 +121,7 @@ defaultEventLogTracer =
119121checkEnv :: String -> Process String
120122checkEnv s = liftIO $ getEnv s
121123
122−-- This trace client is (intentionally) useful - it simply provides
124+-- This trace client is (intentionally) a noop - it simply provides
123125-- an intial client for the trace controller to talk to, until some
124126-- other (hopefully more useful) client is installed over the top of
125127-- it. This is the default trace client.
@@ -136,8 +138,7 @@ systemLoggerTracer = do
136138 sendTraceMsg :: LocalNode -> TraceEvent -> Process ()
137139 sendTraceMsg node ev = do
138140 now <- liftIO $ getCurrentTime
139− txt <- buildTxt ev
140− msg <- return $ (formatTime defaultTimeLocale "%c" now, txt)
141+ msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)
141142 emptyPid <- return $ (nullProcessId (localNodeId node))
142143 traceMsg <- return $ NCMsg {
143144 ctrlMsgSender = ProcessIdentifier (emptyPid)
@@ -146,9 +147,9 @@ systemLoggerTracer = do
146147 }
147148 liftIO $ writeChan (localCtrlChan node) traceMsg
148149
149− buildTxt :: TraceEvent -> Process String
150− buildTxt (TraceEvLog msg) = return msg
151− buildTxt ev = return $ show ev
150+ buildTxt :: TraceEvent -> String
151+ buildTxt (TraceEvLog msg) = msg
152+ buildTxt ev = show ev
152153
153154eventLogTracer :: Process ()
154155eventLogTracer =
@@ -245,7 +246,6 @@ traceController mv = do
245246 catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" >>= return . parseFlags)
246247 (\(_ :: IOError) -> return defaultTraceFlags)
247248
248− -- quick and dirty fold-while
249249 parseFlags :: String -> TraceFlags
250250 parseFlags s = parseFlags' s defaultTraceFlags
251251 where parseFlags' :: String -> TraceFlags -> TraceFlags
@@ -260,22 +260,6 @@ traceController mv = do
260260 | x == 'l' = parseFlags' xs parsedFlags { traceNodes = True }
261261 | otherwise = parseFlags' xs parsedFlags
262262
263−-- note [runtime tracer control]
264−--
265−-- The trace mechanism is designed to put as little stress on the system, and
266−-- in particular the node controller, as possible. The LocalNode's @tracer@
267−-- field is therefore immutable, since we don't want to be blocking the node
268−-- controller when writing trace information out. The runtime cost of enabling
269−-- tracing is therefore the cost of enqueue for all trace-able operations at
270−-- all times. If tracing is not explicitly enabled, the trace record stored in
271−-- the local node state is matched and ignored in API calls, reducing the cost
272−-- to a single function call.
273−--
274−-- To /disable/ tracing facilities in a runtime system that has tracing enabled
275−-- then, is to instruct the tracer process not to forward any trace event data
276−-- and it is not possible to remove the runtime overhead (of enqueue in the
277−-- caller and dequeue + noop in the tracer process) once the node is started.
278−
279263applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState
280264applyTraceFlags flags' state = return state { flags = flags' }
281265
@@ -334,8 +318,8 @@ traceEv ev msg (Just (TraceProcs pids)) st = do
334318 True -> sendTrace st msg
335319 False -> return ()
336320traceEv ev msg (Just (TraceNames names)) st = do
337− -- TODO: if we have recorded regnames for p, then we
338− -- forward the trace iif there are overlapping trace targets
321+ -- if we have recorded regnames for p, then we forward the trace iif
322+ -- there are overlapping trace targets
339323 node <- processNode <$> ask
340324 let p = case resolveToPid ev of
341325 Nothing -> (nullProcessId (localNodeId node))
src/Control/Distributed/Process/Internal/Trace/Types.hs+30−33
@@ -1,6 +1,6 @@
11-- | Tracing/Debugging support - Types
22module Control.Distributed.Process.Internal.Trace.Types
3− ( Tracer(..)
3+ ( MxEventBus(..)
44 , TraceEvent(..)
55 , SetTrace(..)
66 , Addressable(..)
@@ -26,13 +26,16 @@ module Control.Distributed.Process.Internal.Trace.Types
2626import Control.Applicative ((<$>), (<*>))
2727import Control.Distributed.Process.Internal.CQueue (enqueue)
2828import Control.Distributed.Process.Internal.Types
29− ( Tracer(..)
29+ ( MxEventBus(..)
3030 , NodeId
3131 , ProcessId
3232 , DiedReason
3333 , Message
3434 , SendPort
35− , createUnencodedMessage
35+ , unsafeCreateUnencodedMessage
36+ )
37+import Control.Distributed.Process.Management.Bus
38+ ( publishEvent
3639 )
3740import Control.Distributed.Process.Serializable
3841import Data.Binary
@@ -145,10 +148,10 @@ data TraceOk = TraceOk
145148-- Internal/Common API --
146149--------------------------------------------------------------------------------
147150
148−traceLog :: Tracer -> String -> IO ()
149−traceLog tr s = traceIt tr (createUnencodedMessage $ TraceEvLog s)
151+traceLog :: MxEventBus -> String -> IO ()
152+traceLog tr s = publishEvent tr (unsafeCreateUnencodedMessage $ TraceEvLog s)
150153
151−traceLogFmt :: Tracer
154+traceLogFmt :: MxEventBus
152155 -> String
153156 -> [TraceArg]
154157 -> IO ()
@@ -158,43 +161,43 @@ traceLogFmt t d ls =
158161 toS (TraceStr s) = s
159162 toS (Trace a) = show a
160163
161−traceEvent :: Tracer -> TraceEvent -> IO ()
162−traceEvent tr ev = traceIt tr (createUnencodedMessage ev)
164+traceEvent :: MxEventBus -> TraceEvent -> IO ()
165+traceEvent tr ev = publishEvent tr (unsafeCreateUnencodedMessage ev)
163166
164−traceMessage :: Serializable m => Tracer -> m -> IO ()
165−traceMessage tr msg = traceEvent tr (TraceEvUser (createUnencodedMessage msg))
167+traceMessage :: Serializable m => MxEventBus -> m -> IO ()
168+traceMessage tr msg = traceEvent tr (TraceEvUser (unsafeCreateUnencodedMessage msg))
166169
167−enableTrace :: Tracer -> ProcessId -> IO ()
170+enableTrace :: MxEventBus -> ProcessId -> IO ()
168171enableTrace t p =
169− traceIt t (createUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
170− (TraceEnable p)))
172+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
173+ (TraceEnable p)))
171174
172−enableTraceSync :: Tracer -> SendPort TraceOk -> ProcessId -> IO ()
175+enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()
173176enableTraceSync t s p =
174− traceIt t (createUnencodedMessage ((Just s), (TraceEnable p)))
177+ publishEvent t (unsafeCreateUnencodedMessage (Just s, TraceEnable p))
175178
176−disableTrace :: Tracer -> IO ()
179+disableTrace :: MxEventBus -> IO ()
177180disableTrace t =
178− traceIt t (createUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
181+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
179182 TraceDisable))
180183
181−disableTraceSync :: Tracer -> SendPort TraceOk -> IO ()
184+disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()
182185disableTraceSync t s =
183− traceIt t (createUnencodedMessage ((Just s), TraceDisable))
186+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), TraceDisable))
184187
185−setTraceFlags :: Tracer -> TraceFlags -> IO ()
188+setTraceFlags :: MxEventBus -> TraceFlags -> IO ()
186189setTraceFlags t f =
187− traceIt t (createUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))
190+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))
188191
189−setTraceFlagsSync :: Tracer -> SendPort TraceOk -> TraceFlags -> IO ()
192+setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()
190193setTraceFlagsSync t s f =
191− traceIt t (createUnencodedMessage ((Just s), f))
194+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), f))
192195
193−getTraceFlags :: Tracer -> SendPort TraceFlags -> IO ()
194−getTraceFlags t s = traceIt t (createUnencodedMessage s)
196+getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()
197+getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
195198
196−getCurrentTraceClient :: Tracer -> SendPort (Maybe ProcessId) -> IO ()
197−getCurrentTraceClient t s = traceIt t (createUnencodedMessage s)
199+getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
200+getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
198201
199202class Traceable a where
200203 uod :: [a] -> TraceSubject
@@ -205,12 +208,6 @@ instance Traceable ProcessId where
205208instance Traceable String where
206209 uod = TraceNames . Set.fromList
207210
208−traceIt :: Tracer -> Message -> IO ()
209−traceIt InactiveTracer _ = return ()
210−traceIt (ActiveTracer _ wqRef) msg = do
211− mQueue <- deRefWeak wqRef
212− forM_ mQueue $ \queue -> enqueue queue msg
213−
214211--------------------------------------------------------------------------------
215212-- Binary Instances --
216213--------------------------------------------------------------------------------
src/Control/Distributed/Process/Internal/Types.hs+19−16
@@ -17,6 +17,7 @@ module Control.Distributed.Process.Internal.Types
1717 -- * Local nodes and processes
1818 , LocalNode(..)
1919 , Tracer(..)
20+ , MxEventBus(..)
2021 , LocalNodeState(..)
2122 , LocalProcess(..)
2223 , LocalProcessState(..)
@@ -198,22 +199,24 @@ nullProcessId nid =
198199-- Local nodes and processes --
199200--------------------------------------------------------------------------------
200201
201−-- | Required for system tracing in the node controller
202202data Tracer =
203− ActiveTracer !ProcessId !(Weak (CQueue Message)) -- note [tracer longevity]
204− | InactiveTracer
205−
206−-- note [tracer longevity]
207−--
208−-- If the trace controller process crashes, we make no attempt to restart it
209−-- or handle the failure. Tracing is done on a 'best effort' basis, because
210−-- to make stronger guarantees (e.g., handling failures) would introduce
211−-- significant complexity and adversely affect performance. As such, we access
212−-- the process' mailbox directly via a weak reference, and calls to a dead
213−-- process have no effect just as with normal process/send interactions.
214−--
215−-- Also note that the ProcessId stored in an active tracer is that of the first
216−-- (initially registered) system tracer process.
203+ InactiveTracer
204+ | ActiveTracer
205+ {
206+ tracerPid :: !ProcessId
207+ , weakQ :: !(Weak (CQueue Message))
208+ }
209+
210+-- | Required for the system management and tracing facilities
211+data MxEventBus =
212+ MxEventBusInitialising
213+ | MxEventBus
214+ {
215+ agent :: !ProcessId
216+ , tracer :: !Tracer
217+ , evbuss :: !(Weak (CQueue Message))
218+ , mxNew :: !((Message -> Process ()) -> IO ProcessId)
219+ }
217220
218221-- | Local nodes
219222data LocalNode = LocalNode
@@ -226,7 +229,7 @@ data LocalNode = LocalNode
226229 -- | Channel for the node controller
227230 , localCtrlChan :: !(Chan NCMsg)
228231 -- | Current active system debug/trace log
229− , localTracer :: !Tracer
232+ , localEventBus :: !MxEventBus
230233 -- | Runtime lookup table for supporting closures
231234 -- TODO: this should be part of the CH state, not the local endpoint state
232235 , remoteTable :: !RemoteTable
src/Control/Distributed/Process/Management.hsnew file+75
@@ -0,0 +1,75 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE StandaloneDeriving #-}
4+{-# LANGUAGE ExistentialQuantification #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE PatternGuards #-}
7+{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE FlexibleInstances #-}
9+{-# LANGUAGE UndecidableInstances #-}
10+
11+-----------------------------------------------------------------------------
12+-- |
13+-- Module : Control.Distributed.Process.Management
14+-- Copyright : (c) Well-Typed / Tim Watson
15+-- License : BSD3 (see the file LICENSE)
16+--
17+-- Maintainer : Tim Watson <watson.timothy@gmail.com>
18+-- Stability : experimental
19+-- Portability : non-portable (requires concurrency)
20+--
21+-- /Management Extensions/ for Cloud Haskell.
22+--
23+-- This module provides a standard API for monitoring and/or responding to
24+-- system events in a Cloud Haskell node.
25+--
26+-- [Architecture Overview]
27+--
28+-- /Management Extensions/ can be used by infrastructure or application code
29+-- to provide access to runtime instrumentation data, expose control planes
30+-- and/or provide remote access to external clients.
31+--
32+-- The management infrastructure is broken down into three components, which
33+-- roughly correspond to the modules it exposes:
34+--
35+-- 1. @Agent@ - registration and management of /Management Agents/
36+-- 2. @Instrumentation@ - provides an API for publishing /instrumentation data/
37+-- 3. @Remote@ - provides an API for remote management and administration
38+--
39+--
40+--
41+-----------------------------------------------------------------------------
42+module Control.Distributed.Process.Management where
43+
44+import Control.Applicative ((<$>))
45+import Control.Distributed.Process.Internal.Types
46+ ( Process
47+ , ProcessId
48+ , Message
49+ , LocalProcess(..)
50+ , LocalNode(..)
51+ , MxEventBus(..)
52+ , unsafeCreateUnencodedMessage
53+ )
54+import Control.Distributed.Process.Management.Bus (publishEvent)
55+import Control.Distributed.Process.Serializable (Serializable)
56+import Control.Monad.IO.Class (liftIO)
57+import Control.Monad.Reader (ask)
58+
59+-- | Publishes an arbitrary @Serializable@ message to the management event bus.
60+-- Note that /no attempt is made to force the argument/, therefore it is very
61+-- important that you do not pass unevaluated thunks that might crash the
62+-- receiving process via this API, since /all/ registered agents will gain
63+-- access to the data structure once it is broadcast by the agent controller.
64+mxNotify :: (Serializable a) => a -> Process ()
65+mxNotify msg = do
66+ bus <- localEventBus . processNode <$> ask
67+ liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
68+
69+mxAgent :: (Message -> Process ()) -> Process ProcessId
70+mxAgent handler = do
71+ node <- processNode <$> ask
72+ pid <- liftIO $ mxNew (localEventBus node) handler
73+ -- registerAgent pid
74+ return pid
75+
src/Control/Distributed/Process/Management/Agent.hsnew file+141
@@ -0,0 +1,141 @@
1+{-# LANGUAGE RecordWildCards #-}
2+
3+module Control.Distributed.Process.Management.Agent where
4+
5+import Control.Applicative ((<$>))
6+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
7+import Control.Concurrent.STM (atomically)
8+import Control.Concurrent.STM.TChan
9+ ( TChan
10+ , newBroadcastTChanIO
11+ , readTChan
12+ , writeTChan
13+ , dupTChan
14+ )
15+import Control.Distributed.Process.Internal.Primitives
16+ ( receiveWait
17+ , matchAny
18+ , die
19+ , catches
20+ , Handler(..)
21+ )
22+import Control.Distributed.Process.Internal.CQueue
23+ ( enqueueSTM
24+ , CQueue
25+ )
26+import Control.Distributed.Process.Internal.Trace.Tracer
27+ ( traceController
28+ )
29+import Control.Distributed.Process.Internal.Types
30+ ( Process
31+ , SendPort
32+ , Message
33+ , MxEventBus(..)
34+ , Tracer(..)
35+ , LocalNode(..)
36+ , LocalProcess(..)
37+ , ProcessId
38+ , forever'
39+ )
40+import Control.Exception (AsyncException(ThreadKilled), SomeException)
41+import qualified Control.Exception as Ex (catch, finally)
42+import Control.Monad (void)
43+import Control.Monad.IO.Class (liftIO)
44+import Control.Monad.Reader (ask)
45+import GHC.Weak (Weak(Weak), deRefWeak)
46+import System.Environment (getEnv)
47+
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+
54+-- | A triple containing a configured tracer, weak pointer to the
55+-- agent controller's mailbox (CQueue) and an expression used to
56+-- instantiate new agents on the current node.
57+type AgentConfig =
58+ (Tracer, Weak (CQueue Message), ((Message -> Process ()) -> IO ProcessId))
59+
60+-- | Starts a management agent for the current node. The agent process
61+-- must not crash or be killed, so we generally avoid publishing its
62+-- 'ProcessId' where possible.
63+--
64+-- Our process is also responsible for forwarding messages to the trace
65+-- controller, since having two /special processes/ handled via the
66+-- @LocalNode@ would be inelegant. We forward messages directly to the
67+-- trace controller's message queue, just as the @MxEventBus@ that's
68+-- set up on the @LocalNode@ forwards messages directly to us. This
69+-- also optimises the code path for tracing and avoids overloading the
70+-- node controller with additional routing, at the cost of a little
71+-- more complexity for us here.
72+--
73+mxAgentController :: Fork
74+ -> MVar AgentConfig
75+ -> Process ()
76+mxAgentController forkProcess mv = do
77+ node <- processNode <$> ask
78+ trc <- liftIO $ startTracing forkProcess node
79+ sigbus <- liftIO $ newBroadcastTChanIO
80+ weakQueue <- processWeakQ <$> ask
81+ liftIO $ putMVar mv (trc, weakQueue, mxAgent forkProcess sigbus)
82+ go sigbus trc forkProcess
83+ where
84+ go bus tracer fork = forever' $ do
85+ void $ receiveWait [
86+ -- This is exactly what it appears to be: a "catch all" handler.
87+ -- Since mxNotify can potentially pass an unevaluated thunk to
88+ -- our mailbox, the dequeue (i.e., matchMessage) can fail and
89+ -- crash this process, which we DO NOT want. Alternatively,
90+ -- we handle IO exceptions here explicitly, since we don't want
91+ -- this process to every crash, and the assumption we therefore
92+ -- make is thus:
93+ --
94+ -- 1. only ThreadKilled can tell this thread to terminate
95+ -- 2. all other exceptions are invalid and should be ignored
96+ --
97+ -- The outcome of course, is that /bad/ calls to mxNotify will
98+ -- be silently ignored.
99+ --
100+ matchAny (\msg -> liftIO $ broadcast bus tracer msg)
101+ ] `catches` [Handler (\ThreadKilled -> die "Killed"),
102+ Handler (\(_ :: SomeException) -> return ())]
103+
104+ broadcast :: TChan Message -> Tracer -> Message -> IO ()
105+ broadcast ch tr msg = do
106+ tmQueue <- tracerQueue tr
107+ atomicBroadcast ch tmQueue msg
108+
109+ tracerQueue :: Tracer -> IO (Maybe (CQueue Message))
110+ tracerQueue InactiveTracer = return Nothing
111+ tracerQueue (ActiveTracer _ wQ) = deRefWeak wQ
112+
113+ atomicBroadcast :: TChan Message
114+ -> Maybe (CQueue Message)
115+ -> Message -> IO ()
116+ atomicBroadcast ch Nothing msg = liftIO $ atomically $ writeTChan ch msg
117+ atomicBroadcast ch (Just q) msg = do
118+ liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
119+
120+-- | Forks a new process in which an mxAgent is run.
121+mxAgent :: Fork -> TChan Message -> (Message -> Process ()) -> IO ProcessId
122+mxAgent fork chan handler = (atomically (dupTChan chan)) >>= fork . run handler
123+ where
124+ run :: (Message -> Process ()) -> TChan Message -> Process ()
125+ run handler' chan' = do
126+ (liftIO $ atomically $ readTChan chan') >>= handler' >> run handler' chan'
127+
128+startTracing :: Fork -> LocalNode -> IO Tracer
129+startTracing forkProcess node =
130+ -- TODO: use tryGetEnv once we drop support for GHC 7.2.x
131+ Ex.catch
132+ (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> startTracing' forkProcess node)
133+ (\(_ :: IOError) -> return InactiveTracer)
134+ where
135+ startTracing' :: Fork -> LocalNode -> IO Tracer
136+ startTracing' fork' n = do
137+ mv <- newEmptyMVar
138+ pid <- fork' $ traceController mv
139+ wQ <- liftIO $ takeMVar mv
140+ return $ ActiveTracer pid wQ
141+
src/Control/Distributed/Process/Management/Bus.hsnew file+21
@@ -0,0 +1,21 @@
1+-- | Interface to the management event bus.
2+module Control.Distributed.Process.Management.Bus
3+ ( publishEvent
4+ ) where
5+
6+import Control.Distributed.Process.Internal.CQueue
7+ ( enqueue
8+ )
9+import Control.Distributed.Process.Internal.Types
10+ ( MxEventBus(..)
11+ , Message
12+ )
13+import Data.Foldable (forM_)
14+import System.Mem.Weak (deRefWeak)
15+
16+publishEvent :: MxEventBus -> Message -> IO ()
17+publishEvent MxEventBusInitialising _ = return ()
18+publishEvent (MxEventBus _ _ wqRef _) msg = do
19+ mQueue <- deRefWeak wqRef
20+ forM_ mQueue $ \queue -> enqueue queue msg
21+
src/Control/Distributed/Process/Node.hs+32−47
@@ -106,7 +106,7 @@ import Control.Distributed.Process.Internal.Types
106106 , LocalProcessId(..)
107107 , ProcessId(..)
108108 , LocalNode(..)
109− , Tracer(..)
109+ , MxEventBus(..)
110110 , LocalNodeState(..)
111111 , LocalProcess(..)
112112 , LocalProcessState(..)
@@ -160,8 +160,7 @@ import Control.Distributed.Process.Internal.Trace.Types
160160 , enableTrace
161161 )
162162import Control.Distributed.Process.Internal.Trace.Tracer
163− ( traceController
164− , defaultTracer
163+ ( defaultTracer
165164 )
166165import Control.Distributed.Process.Serializable (Serializable)
167166import Control.Distributed.Process.Internal.Messaging
@@ -179,13 +178,16 @@ import Control.Distributed.Process.Internal.Primitives
179178 , catch
180179 , unwrapMessage
181180 )
182−import Control.Distributed.Process.Internal.Types (SendPort)
181+import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
183182import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
184183import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
185184import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
186185 ( mapMaybe
187186 , mapDefault
188187 )
188+import Control.Distributed.Process.Management.Agent
189+ ( mxAgentController
190+ )
189191import System.Environment (getEnv)
190192import Unsafe.Coerce
191193
@@ -222,10 +224,10 @@ createBareLocalNode endPoint rtable = do
222224 , localEndPoint = endPoint
223225 , localState = state
224226 , localCtrlChan = ctrlChan
225− , localTracer = InactiveTracer
227+ , localEventBus = MxEventBusInitialising
226228 , remoteTable = rtable
227229 }
228− tracedNode <- startTracing node
230+ tracedNode <- startMxAgent node
229231
230232 -- Once the NC terminates, the endpoint isn't much use,
231233 void $ forkIO $ Exception.finally (runNodeController tracedNode)
@@ -243,38 +245,25 @@ createBareLocalNode endPoint rtable = do
243245 , ctrlMsgSignal = SigShutdown
244246 }
245247
246−startTracing :: LocalNode -> IO LocalNode
247−startTracing node =
248− -- TODO: use tryGetEnv once we drop support for GHC 7.2.x
249− Exception.catch
250− (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
251− (\(_ :: IOError) -> return node)
252− where procTracer :: LocalNode -> IO LocalNode
253− procTracer n = do
254− -- see note [tracer/forkProcess races]
255− mv <- MVar.newEmptyMVar
256− pid <- forkProcess n (traceController mv)
257− wqRef <- MVar.takeMVar mv
258− return n { localTracer = (ActiveTracer pid wqRef) }
259−
260−startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
261−startDefaultTracer node =
262− Exception.catch
263− (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
264− (\(_ :: IOError) -> return Nothing)
265− where go :: LocalNode -> IO (Maybe ProcessId)
266− go node' = do
267− registerTraceController node'
268− pid <- forkProcess node' defaultTracer
269− enableTrace (localTracer node') pid
270− return (Just pid)
271−
272− registerTraceController :: LocalNode -> IO ()
273− registerTraceController n =
274− let t = localTracer n in
275− case t of
276− InactiveTracer -> return ()
277− (ActiveTracer pid _) -> runProcess n $ register "trace.controller" pid
248+startMxAgent :: LocalNode -> IO LocalNode
249+startMxAgent node = do
250+ -- see note [tracer/forkProcess races]
251+ let fork = forkProcess node
252+ mv <- MVar.newEmptyMVar
253+ pid <- fork $ mxAgentController fork mv
254+ (tracer, wqRef, mxNew) <- MVar.takeMVar mv
255+ return node { localEventBus = (MxEventBus pid tracer wqRef mxNew) }
256+
257+startDefaultTracer :: LocalNode -> IO ()
258+startDefaultTracer node' = do
259+ let t = localEventBus node'
260+ case t of
261+ MxEventBus _ (ActiveTracer pid _) _ _ -> do
262+ runProcess node' $ register "trace.controller" pid
263+ pid' <- forkProcess node' defaultTracer
264+ enableTrace (localEventBus node') pid'
265+ runProcess node' $ register "tracer.initial" pid'
266+ _ -> return ()
278267
279268-- | Start and register the service processes on a node
280269startServiceProcesses :: LocalNode -> IO ()
@@ -283,11 +272,7 @@ startServiceProcesses node = do
283272 -- the default tracer first, even though it might @nsend@ to the logger
284273 -- before /that/ process has started - this is a totally harmless race
285274 -- however, so we deliberably ignore it
286− mPid <- startDefaultTracer node
287− case mPid of
288− Nothing -> return ()
289− Just pid -> runProcess node $ register "tracer.initial" pid
290−
275+ startDefaultTracer node
291276 logger <- forkProcess node loop
292277 runProcess node $ register "logger" logger
293278 where
@@ -401,7 +386,7 @@ forkProcess node proc = modifyMVar (localState node) startProcess
401386--
402387-- Our startTracing function uses forkProcess to start the trace controller
403388-- process, and of course forkProcess attempts to call traceEvent once the
404−-- process has started. This is harmless, as the localTracer is not updated
389+-- process has started. This is harmless, as the localEventBus is not updated
405390-- until /after/ the initial forkProcess completes, so the first call to
406391-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
407392--
@@ -607,7 +592,7 @@ instance Show ProcessKillException where
607592-- Tracing/Debugging --
608593--------------------------------------------------------------------------------
609594
610−-- [Issue #104]
595+-- [Issue #104 / DP-13]
611596
612597traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
613598traceNotifyDied node ident reason =
@@ -628,8 +613,8 @@ traceEventFmtIO node fmt args =
628613trace :: LocalNode -> TraceEvent -> IO ()
629614trace node ev = withLocalTracer node $ \t -> traceEvent t ev
630615
631−withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
632−withLocalTracer node act = act (localTracer node)
616+withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
617+withLocalTracer node act = act (localEventBus node)
633618
634619--------------------------------------------------------------------------------
635620-- Core functionality --