@@ -40,7 +40,7 @@ import qualified Data.Set as Set
4040 , toList
4141 )
4242import Data.Foldable (forM_)
43−import Data.Maybe (isJust, isNothing, catMaybes)
43+import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
4444import Data.Typeable (Typeable)
4545import Control.Category ((>>>))
4646import Control.Applicative ((<$>))
@@ -68,6 +68,7 @@ import Control.Distributed.Process.Internal.StrictMVar
6868 , takeMVar
6969 )
7070import Control.Concurrent.Chan (newChan, writeChan, readChan)
71+import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
7172import Control.Concurrent.STM
7273 ( atomically
7374 )
@@ -105,7 +106,7 @@ import Control.Distributed.Process.Internal.Types
105106 , LocalProcessId(..)
106107 , ProcessId(..)
107108 , LocalNode(..)
108− , Tracer(InactiveTracer)
109+ , Tracer(..)
109110 , LocalNodeState(..)
110111 , LocalProcess(..)
111112 , LocalProcessState(..)
@@ -148,11 +149,17 @@ import Control.Distributed.Process.Internal.Types
148149 , firstNonReservedProcessId
149150 , ImplicitReconnect(WithImplicitReconnect)
150151 )
151−import Control.Distributed.Process.Internal.Trace
152+import Control.Distributed.Process.Internal.Trace.Primitives
153+ ( traceLogFmt
154+ , traceEvent
155+ )
156+import Control.Distributed.Process.Internal.Trace.Types
152157 ( TraceArg(..)
153− , traceFormat
154− , startTracing
155− , stopTracer
158+ , TraceEvent(..)
159+ )
160+import Control.Distributed.Process.Internal.Trace.Tracer
161+ ( traceController
162+ , defaultTracer
156163 )
157164import Control.Distributed.Process.Serializable (Serializable)
158165import Control.Distributed.Process.Internal.Messaging
@@ -175,7 +182,7 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
175182 ( mapMaybe
176183 , mapDefault
177184 )
178−
185+import System.Environment (getEnv)
179186import Unsafe.Coerce
180187
181188--------------------------------------------------------------------------------
@@ -232,10 +239,41 @@ createBareLocalNode endPoint rtable = do
232239 , ctrlMsgSignal = SigShutdown
233240 }
234241
242+startTracing :: LocalNode -> IO LocalNode
243+startTracing node =
244+ Exception.catch
245+ (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
246+ (\(_ :: IOError) -> return node)
247+ where procTracer :: LocalNode -> IO LocalNode
248+ procTracer n = do
249+ -- see note [tracer/forkProcess races]
250+ mv <- MVar.newEmptyMVar
251+ pid <- forkProcess n (traceController mv)
252+ wqRef <- MVar.takeMVar mv
253+ return n { localTracer = (ActiveTracer pid wqRef) }
254+
255+startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
256+startDefaultTracer node =
257+ Exception.catch
258+ (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
259+ (\(_ :: IOError) -> return Nothing)
260+ where go :: LocalNode -> IO (Maybe ProcessId)
261+ go node' = do
262+ pid <- forkProcess node' defaultTracer
263+ return (Just pid)
264+
235265-- | Start and register the service processes on a node
236−-- (for now, this is only the logger)
237266startServiceProcesses :: LocalNode -> IO ()
238267startServiceProcesses node = do
268+ -- tracing /spawns/ relies on the tracer being enabled, but we start
269+ -- the default tracer first, even though it might @nsend@ to the logger
270+ -- before /that/ process has started - this is a totally harmless race
271+ mPid <- startDefaultTracer node
272+ case mPid of
273+ Nothing -> return ()
274+ Just pid ->
275+ runProcess node (register "tracer" pid >> register "tracer.initial" pid)
276+
239277 logger <- forkProcess node loop
240278 runProcess node $ register "logger" logger
241279 where
@@ -257,6 +295,7 @@ startServiceProcesses node = do
257295-- TODO: for now we just close the associated endpoint
258296closeLocalNode :: LocalNode -> IO ()
259297closeLocalNode node =
298+ -- TODO: close all our processes, surely!?
260299 NT.closeEndPoint (localEndPoint node)
261300
262301-- | Run a process on a local node and wait for it to finish
@@ -306,6 +345,10 @@ forkProcess node proc = modifyMVar (localState node) startProcess
306345 }
307346 return (tid', lproc)
308347
348+
349+ -- see note [tracer/forkProcess races]
350+ trace node (TraceEvSpawned pid)
351+
309352 if lpidCounter lpid == maxBound
310353 then do
311354 newUnique <- randomIO
@@ -331,6 +374,15 @@ forkProcess node proc = modifyMVar (localState node) startProcess
331374 . (localConnections ^= unaffected)
332375 $ st
333376
377+-- note [tracer/forkProcess races]
378+--
379+-- Our startTracing function uses forkProcess to start the trace controller
380+-- process, and of course forkProcess attempts to call traceEvent once the
381+-- process has started. This is harmless, as the localTracer is not updated
382+-- until /after/ the initial forkProcess completes, so the first call to
383+-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
384+--
385+
334386--------------------------------------------------------------------------------
335387-- Handle incoming messages --
336388--------------------------------------------------------------------------------
@@ -339,7 +391,7 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
339391
340392data IncomingTarget =
341393 Uninit
342− | ToProc (Weak (CQueue Message))
394+ | ToProc ProcessId (Weak (CQueue Message))
343395 | ToChan TypedChannel
344396 | ToNode
345397
@@ -374,20 +426,24 @@ handleIncomingMessages node = go initConnectionState
374426 case event of
375427 NT.ConnectionOpened cid rel theirAddr ->
376428 if rel == NT.ReliableOrdered
377− then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
429+ then
430+ trace node (TraceEvConnected cid)
431+ >> go (
432+ (incomingAt cid ^= Just (theirAddr, Uninit))
378433 . (incomingFrom theirAddr ^: Set.insert cid)
379434 $ st
380435 )
381436 else invalidRequest cid st
382437 NT.Received cid payload ->
383438 case st ^. incomingAt cid of
384− Just (_, ToProc weakQueue) -> do
439+ Just (_, ToProc pid weakQueue) -> do
385440 mQueue <- deRefWeak weakQueue
386441 forM_ mQueue $ \queue -> do
387442 -- TODO: if we find that the queue is Nothing, should we remove
388443 -- it from the NC state? (and same for channels, below)
389444 let msg = payloadToMessage payload
390445 enqueue queue msg -- 'enqueue' is strict
446+ trace node (TraceEvReceived pid msg)
391447 go st
392448 Just (_, ToChan (TypedChannel chan')) -> do
393449 mChan <- deRefWeak chan'
@@ -408,7 +464,7 @@ handleIncomingMessages node = go initConnectionState
408464 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
409465 case mProc of
410466 Just proc ->
411− go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
467+ go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
412468 Nothing ->
413469 invalidRequest cid st
414470 SendPortIdentifier chId -> do
@@ -435,7 +491,8 @@ handleIncomingMessages node = go initConnectionState
435491 case st ^. incomingAt cid of
436492 Nothing ->
437493 invalidRequest cid st
438− Just (src, _) ->
494+ Just (src, _) -> do
495+ trace node (TraceEvDisconnected cid)
439496 go ( (incomingAt cid ^= Nothing)
440497 . (incomingFrom src ^: Set.delete cid)
441498 $ st
@@ -454,18 +511,15 @@ handleIncomingMessages node = go initConnectionState
454511 $ st
455512 )
456513 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
457− fatal $ "Cloud Haskell fatal error: end point failed: " ++ str
514+ fail $ "Cloud Haskell fatal error: end point failed: " ++ str
458515 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
459− fatal $ "Cloud Haskell fatal error: transport failed: " ++ str
516+ fail $ "Cloud Haskell fatal error: transport failed: " ++ str
460517 NT.EndPointClosed ->
461− stopTracer (localTracer node) >> return ()
518+ return ()
462519 NT.ReceivedMulticast _ _ ->
463520 -- If we received a multicast message, something went horribly wrong
464521 -- and we just give up
465− fatal "Cloud Haskell fatal error: received unexpected multicast"
466−
467− fatal :: String -> IO ()
468− fatal msg = stopTracer (localTracer node) >> fail msg
522+ fail "Cloud Haskell fatal error: received unexpected multicast"
469523
470524 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
471525 invalidRequest cid st = do
@@ -473,7 +527,7 @@ handleIncomingMessages node = go initConnectionState
473527 -- node. That is, we should report the remote node as having died, and we
474528 -- should close incoming connections (this requires a Transport layer
475529 -- extension).
476− traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),
530+ traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
477531 (Trace cid)]
478532 go ( incomingAt cid ^= Nothing
479533 $ st
@@ -534,23 +588,22 @@ instance Show ProcessKillException where
534588
535589traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
536590traceNotifyDied node ident reason =
537− case reason of
538− DiedNormal -> return ()
539− _ -> traceNcEventFmt node " "
540− [(TraceStr "[node-controller]"),
541− (Trace ident),
542− (Trace reason)]
543−
544−traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()
545−traceNcEventFmt node fmt args =
546− liftIO $ traceEventFmtIO node fmt args
591+ -- TODO: sendPortDied notifications
592+ liftIO $ withLocalTracer node $ \t ->
593+ case ident of
594+ (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
595+ (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
596+ _ -> return ()
547597
548598traceEventFmtIO :: LocalNode
549599 -> String
550600 -> [TraceArg]
551601 -> IO ()
552602traceEventFmtIO node fmt args =
553− withLocalTracer node $ \t -> traceFormat t fmt args
603+ withLocalTracer node $ \t -> traceLogFmt t fmt args
604+
605+trace :: LocalNode -> TraceEvent -> IO ()
606+trace node ev = withLocalTracer node $ \t -> traceEvent t ev
554607
555608withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
556609withLocalTracer node act = act (localTracer node)
@@ -597,7 +650,7 @@ nodeController = do
597650 NCMsg _ (NamedSend label msg') ->
598651 ncEffectNamedSend label msg'
599652 NCMsg _ (LocalSend to msg') ->
600− ncEffectLocalSend to msg'
653+ ncEffectLocalSend node to msg'
601654 NCMsg _ (LocalPortSend to msg') ->
602655 ncEffectLocalPortSend to msg'
603656 NCMsg (ProcessIdentifier from) (Kill to reason) ->
@@ -720,6 +773,7 @@ ncEffectSpawn pid cProc ref = do
720773 mProc <- unClosure cProc
721774 -- If the closure does not exist, we spawn a process that throws an exception
722775 -- This allows the remote node to find out what's happening
776+ -- TODO:
723777 let proc = case mProc of
724778 Left err -> fail $ "Error: Could not resolve closure: " ++ err
725779 Right p -> p
@@ -750,6 +804,9 @@ ncEffectRegister from label atnode mPid reregistration = do
750804 then do when (isOk) $
751805 do modify' $ registeredHereFor label ^= mPid
752806 updateRemote node currentVal mPid
807+ case mPid of
808+ (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
809+ Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
753810 liftIO $ sendMessage node
754811 (NodeIdentifier (localNodeId node))
755812 (ProcessIdentifier from)
@@ -772,6 +829,7 @@ ncEffectRegister from label atnode mPid reregistration = do
772829 updateRemote _ _ _ = return ()
773830 maybeify f Nothing = unmaybeify $ f []
774831 maybeify f (Just x) = unmaybeify $ f x
832+
775833 unmaybeify [] = Nothing
776834 unmaybeify x = Just x
777835 incList [] tag = [(tag,1)]
@@ -812,16 +870,19 @@ ncEffectNamedSend label msg = do
812870 forM_ mPid $ \pid -> postMessage pid msg
813871
814872-- [Issue #DP-20]
815−ncEffectLocalSend :: ProcessId -> Message -> NC ()
816−ncEffectLocalSend = postMessage
873+ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
874+ncEffectLocalSend node to msg =
875+ liftIO $ withLocalProc node to $ \p -> do
876+ enqueue (processQueue p) msg
877+ trace node (TraceEvReceived to msg)
817878
818879-- [Issue #DP-20]
819880ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
820−ncEffectLocalPortSend from msg =
881+ncEffectLocalPortSend from msg = do
882+ node <- ask
821883 let pid = sendPortProcessId from
822884 cid = sendPortLocalId from
823− in do
824− withLocalProc pid $ \proc -> do
885+ liftIO $ withLocalProc node pid $ \proc -> do
825886 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
826887 case mChan of
827888 -- in the unlikely event we know nothing about this channel id,
@@ -995,21 +1056,21 @@ postAsMessage pid = postMessage pid . createUnencodedMessage
9951056
9961057postMessage :: ProcessId -> Message -> NC ()
9971058postMessage pid msg = do
998− withLocalProc pid $ \p -> enqueue (processQueue p) msg
1059+ node <- ask
1060+ liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
9991061
10001062throwException :: Exception e => ProcessId -> e -> NC ()
1001−throwException pid e = withLocalProc pid $ \p ->
1002− throwTo (processThread p) e
1003−
1004−withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
1005−withLocalProc pid p = do
1063+throwException pid e = do
10061064 node <- ask
1007− liftIO $ do
1008− -- By [Unified: table 6, rule missing_process] messages to dead processes
1009− -- can silently be dropped
1010− let lpid = processLocalId pid
1011− mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1012− forM_ mProc p
1065+ liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1066+
1067+withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1068+withLocalProc node pid p =
1069+ -- By [Unified: table 6, rule missing_process] messages to dead processes
1070+ -- can silently be dropped
1071+ let lpid = processLocalId pid in do
1072+ mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1073+ forM_ mProc p
10131074
10141075--------------------------------------------------------------------------------
10151076-- Accessors --