1{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE RecordWildCards #-}
4module Control.Distributed.Process.Management.Internal.Agent where
6import Control.Applicative
7import Control.Concurrent (forkIO)
8import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
9import Control.Concurrent.STM (atomically)
10import Control.Concurrent.STM.TChan
17import Control.Distributed.Process.Internal.Primitives
24import Control.Distributed.Process.Internal.CQueue
28import Control.Distributed.Process.Management.Internal.Types
31import Control.Distributed.Process.Management.Internal.Trace.Tracer
34import Control.Distributed.Process.Internal.Types
42import Control.Exception (AsyncException(ThreadKilled), SomeException)
43import Control.Monad (void)
44import Control.Monad.IO.Class (liftIO)
45import Control.Monad.Reader (ask)
46import GHC.Weak (Weak, deRefWeak)
49--------------------------------------------------------------------------------
50-- Agent Controller Implementation --
51--------------------------------------------------------------------------------
53-- | A triple containing a configured tracer, weak pointer to the
54-- agent controller's mailbox (CQueue) and an expression used to
55-- instantiate new agents on the current node.
57 (Tracer, Weak (CQueue Message),
58 (((TChan Message, TChan Message) -> Process ()) -> IO ProcessId))
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.
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-- optimises the code path for tracing and avoids overloading the node
70-- node controller's internal control plane with additional routing, at the
71-- cost of a little more complexity and two cases where we break
74mxAgentController :: Fork
77mxAgentController forkProcess mv = do
78 trc <- liftIO $ startTracing forkProcess
79 sigbus <- liftIO $ newBroadcastTChanIO
80 liftIO $ startDeadLetterQueue sigbus
81 weakQueue <- processWeakQ <$> ask
82 liftIO $ putMVar mv (trc, weakQueue, mxStartAgent forkProcess sigbus)
85 go bus tracer = forever' $ do
87 -- This is exactly what it appears to be: a "catch all" handler.
88 -- Since mxNotify can potentially pass an unevaluated thunk to
89 -- our mailbox, the dequeue (i.e., matchMessage) can fail and
90 -- crash this process, which we DO NOT want. Alternatively,
91 -- we handle IO exceptions here explicitly, since we don't want
92 -- this process to ever crash, and the assumption we therefore
95 -- 1. only ThreadKilled can tell this process to terminate
96 -- 2. all other exceptions are invalid and should be ignored
98 -- The outcome of course, is that /bad/ calls to mxNotify
99 -- (e.g., passing unevaluated thunks that will crash when
100 -- they're eventually forced) are thus silently ignored.
102 matchAny (liftIO . broadcast bus tracer)
103 ] `catches` [Handler (\ThreadKilled -> die "Killed"),
104 Handler (\(_ :: SomeException) -> return ())]
106 broadcast :: TChan Message -> Tracer -> Message -> IO ()
107 broadcast ch tr msg = do
108 tmQueue <- tracerQueue tr
109 atomicBroadcast ch tmQueue msg
111 tracerQueue :: Tracer -> IO (Maybe (CQueue Message))
112 tracerQueue (Tracer _ wQ) = deRefWeak wQ
114 atomicBroadcast :: TChan Message
115 -> Maybe (CQueue Message)
117 atomicBroadcast ch Nothing msg = liftIO $ atomically $ writeTChan ch msg
118 atomicBroadcast ch (Just q) msg = do
119 -- liftIO $ putStrLn $ "broadcasting " ++ (show msg)
120 liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
122-- | Forks a new process in which an mxAgent is run.
126 -> ((TChan Message, TChan Message) -> Process ())
128mxStartAgent fork chan handler = do
129 chan' <- atomically (dupTChan chan)
130 let proc = handler (chan, chan')
133-- | Start the tracer controller.
135startTracing :: Fork -> IO Tracer
136startTracing forkProcess = do
138 pid <- forkProcess $ traceController mv
139 wQ <- liftIO $ takeMVar mv
140 return $ Tracer pid wQ
142-- | Start a dead letter (agent) queue.
144-- If no agents are registered on the system, the management
145-- event bus will fill up and its data won't be GC'ed until someone
146-- comes along and reads from the broadcast channel (via dupTChan
147-- of course). This is effectively a leak, so to mitigate it, we
148-- start a /dead letter queue/ that drains the event bus continuously,
149-- thus ensuring if there are no other consumers that we won't use
150-- up heap space unnecessarily.
152startDeadLetterQueue :: TChan Message
154startDeadLetterQueue sigbus = do
155 chan' <- atomically (dupTChan sigbus)
156 void $ forkIO $ forever' $ do
157 void $ atomically $ readTChan chan'
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