@@ -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,19 @@ import Control.Distributed.Process.Internal.Types
148149 , firstNonReservedProcessId
149150 , ImplicitReconnect(WithImplicitReconnect)
150151 )
151−import Control.Distributed.Process.Internal.Trace
152+import qualified Control.Distributed.Process.Internal.Trace.Remote as Trace
153+ ( remoteTable
154+ )
155+import Control.Distributed.Process.Internal.Trace.Types
152156 ( TraceArg(..)
153− , traceFormat
154− , startTracing
155− , stopTracer
157+ , TraceEvent(..)
158+ , traceEvent
159+ , traceLogFmt
160+ , enableTrace
161+ )
162+import Control.Distributed.Process.Internal.Trace.Tracer
163+ ( traceController
164+ , defaultTracer
156165 )
157166import Control.Distributed.Process.Serializable (Serializable)
158167import Control.Distributed.Process.Internal.Messaging
@@ -175,7 +184,7 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
175184 ( mapMaybe
176185 , mapDefault
177186 )
178−
187+import System.Environment (getEnv)
179188import Unsafe.Coerce
180189
181190--------------------------------------------------------------------------------
@@ -183,7 +192,7 @@ import Unsafe.Coerce
183192--------------------------------------------------------------------------------
184193
185194initRemoteTable :: RemoteTable
186−initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
195+initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
187196
188197-- | Initialize a new local node.
189198newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
@@ -232,10 +241,51 @@ createBareLocalNode endPoint rtable = do
232241 , ctrlMsgSignal = SigShutdown
233242 }
234243
244+startTracing :: LocalNode -> IO LocalNode
245+startTracing node =
246+ -- TODO: use tryGetEnv once we drop support for GHC 7.2.x
247+ Exception.catch
248+ (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
249+ (\(_ :: IOError) -> return node)
250+ where procTracer :: LocalNode -> IO LocalNode
251+ procTracer n = do
252+ -- see note [tracer/forkProcess races]
253+ mv <- MVar.newEmptyMVar
254+ pid <- forkProcess n (traceController mv)
255+ wqRef <- MVar.takeMVar mv
256+ return n { localTracer = (ActiveTracer pid wqRef) }
257+
258+startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
259+startDefaultTracer node =
260+ Exception.catch
261+ (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
262+ (\(_ :: IOError) -> return Nothing)
263+ where go :: LocalNode -> IO (Maybe ProcessId)
264+ go node' = do
265+ registerTraceController node'
266+ pid <- forkProcess node' defaultTracer
267+ enableTrace (localTracer node') pid
268+ return (Just pid)
269+
270+ registerTraceController :: LocalNode -> IO ()
271+ registerTraceController n =
272+ let t = localTracer n in
273+ case t of
274+ InactiveTracer -> return ()
275+ (ActiveTracer pid _) -> runProcess n $ register "trace.controller" pid
276+
235277-- | Start and register the service processes on a node
236−-- (for now, this is only the logger)
237278startServiceProcesses :: LocalNode -> IO ()
238279startServiceProcesses node = do
280+ -- tracing /spawns/ relies on the tracer being enabled, but we start
281+ -- the default tracer first, even though it might @nsend@ to the logger
282+ -- before /that/ process has started - this is a totally harmless race
283+ -- however, so we deliberably ignore it
284+ mPid <- startDefaultTracer node
285+ case mPid of
286+ Nothing -> return ()
287+ Just pid -> runProcess node $ register "tracer.initial" pid
288+
239289 logger <- forkProcess node loop
240290 runProcess node $ register "logger" logger
241291 where
@@ -257,6 +307,7 @@ startServiceProcesses node = do
257307-- TODO: for now we just close the associated endpoint
258308closeLocalNode :: LocalNode -> IO ()
259309closeLocalNode node =
310+ -- TODO: close all our processes, surely!?
260311 NT.closeEndPoint (localEndPoint node)
261312
262313-- | Run a process on a local node and wait for it to finish
@@ -306,6 +357,10 @@ forkProcess node proc = modifyMVar (localState node) startProcess
306357 }
307358 return (tid', lproc)
308359
360+
361+ -- see note [tracer/forkProcess races]
362+ trace node (TraceEvSpawned pid)
363+
309364 if lpidCounter lpid == maxBound
310365 then do
311366 newUnique <- randomIO
@@ -331,6 +386,15 @@ forkProcess node proc = modifyMVar (localState node) startProcess
331386 . (localConnections ^= unaffected)
332387 $ st
333388
389+-- note [tracer/forkProcess races]
390+--
391+-- Our startTracing function uses forkProcess to start the trace controller
392+-- process, and of course forkProcess attempts to call traceEvent once the
393+-- process has started. This is harmless, as the localTracer is not updated
394+-- until /after/ the initial forkProcess completes, so the first call to
395+-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
396+--
397+
334398--------------------------------------------------------------------------------
335399-- Handle incoming messages --
336400--------------------------------------------------------------------------------
@@ -339,7 +403,7 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
339403
340404data IncomingTarget =
341405 Uninit
342− | ToProc (Weak (CQueue Message))
406+ | ToProc ProcessId (Weak (CQueue Message))
343407 | ToChan TypedChannel
344408 | ToNode
345409
@@ -374,20 +438,24 @@ handleIncomingMessages node = go initConnectionState
374438 case event of
375439 NT.ConnectionOpened cid rel theirAddr ->
376440 if rel == NT.ReliableOrdered
377− then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
441+ then
442+ trace node (TraceEvConnected cid)
443+ >> go (
444+ (incomingAt cid ^= Just (theirAddr, Uninit))
378445 . (incomingFrom theirAddr ^: Set.insert cid)
379446 $ st
380447 )
381448 else invalidRequest cid st
382449 NT.Received cid payload ->
383450 case st ^. incomingAt cid of
384− Just (_, ToProc weakQueue) -> do
451+ Just (_, ToProc pid weakQueue) -> do
385452 mQueue <- deRefWeak weakQueue
386453 forM_ mQueue $ \queue -> do
387454 -- TODO: if we find that the queue is Nothing, should we remove
388455 -- it from the NC state? (and same for channels, below)
389456 let msg = payloadToMessage payload
390457 enqueue queue msg -- 'enqueue' is strict
458+ trace node (TraceEvReceived pid msg)
391459 go st
392460 Just (_, ToChan (TypedChannel chan')) -> do
393461 mChan <- deRefWeak chan'
@@ -408,7 +476,7 @@ handleIncomingMessages node = go initConnectionState
408476 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
409477 case mProc of
410478 Just proc ->
411− go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
479+ go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
412480 Nothing ->
413481 invalidRequest cid st
414482 SendPortIdentifier chId -> do
@@ -435,7 +503,8 @@ handleIncomingMessages node = go initConnectionState
435503 case st ^. incomingAt cid of
436504 Nothing ->
437505 invalidRequest cid st
438− Just (src, _) ->
506+ Just (src, _) -> do
507+ trace node (TraceEvDisconnected cid)
439508 go ( (incomingAt cid ^= Nothing)
440509 . (incomingFrom src ^: Set.delete cid)
441510 $ st
@@ -454,18 +523,15 @@ handleIncomingMessages node = go initConnectionState
454523 $ st
455524 )
456525 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
457− fatal $ "Cloud Haskell fatal error: end point failed: " ++ str
526+ fail $ "Cloud Haskell fatal error: end point failed: " ++ str
458527 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
459− fatal $ "Cloud Haskell fatal error: transport failed: " ++ str
528+ fail $ "Cloud Haskell fatal error: transport failed: " ++ str
460529 NT.EndPointClosed ->
461− stopTracer (localTracer node) >> return ()
530+ return ()
462531 NT.ReceivedMulticast _ _ ->
463532 -- If we received a multicast message, something went horribly wrong
464533 -- 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
534+ fail "Cloud Haskell fatal error: received unexpected multicast"
469535
470536 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
471537 invalidRequest cid st = do
@@ -473,7 +539,7 @@ handleIncomingMessages node = go initConnectionState
473539 -- node. That is, we should report the remote node as having died, and we
474540 -- should close incoming connections (this requires a Transport layer
475541 -- extension).
476− traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),
542+ traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
477543 (Trace cid)]
478544 go ( incomingAt cid ^= Nothing
479545 $ st
@@ -534,23 +600,22 @@ instance Show ProcessKillException where
534600
535601traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
536602traceNotifyDied 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
603+ -- TODO: sendPortDied notifications
604+ liftIO $ withLocalTracer node $ \t ->
605+ case ident of
606+ (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
607+ (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
608+ _ -> return ()
547609
548610traceEventFmtIO :: LocalNode
549611 -> String
550612 -> [TraceArg]
551613 -> IO ()
552614traceEventFmtIO node fmt args =
553− withLocalTracer node $ \t -> traceFormat t fmt args
615+ withLocalTracer node $ \t -> traceLogFmt t fmt args
616+
617+trace :: LocalNode -> TraceEvent -> IO ()
618+trace node ev = withLocalTracer node $ \t -> traceEvent t ev
554619
555620withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
556621withLocalTracer node act = act (localTracer node)
@@ -597,7 +662,7 @@ nodeController = do
597662 NCMsg _ (NamedSend label msg') ->
598663 ncEffectNamedSend label msg'
599664 NCMsg _ (LocalSend to msg') ->
600− ncEffectLocalSend to msg'
665+ ncEffectLocalSend node to msg'
601666 NCMsg _ (LocalPortSend to msg') ->
602667 ncEffectLocalPortSend to msg'
603668 NCMsg (ProcessIdentifier from) (Kill to reason) ->
@@ -720,6 +785,7 @@ ncEffectSpawn pid cProc ref = do
720785 mProc <- unClosure cProc
721786 -- If the closure does not exist, we spawn a process that throws an exception
722787 -- This allows the remote node to find out what's happening
788+ -- TODO:
723789 let proc = case mProc of
724790 Left err -> fail $ "Error: Could not resolve closure: " ++ err
725791 Right p -> p
@@ -750,6 +816,9 @@ ncEffectRegister from label atnode mPid reregistration = do
750816 then do when (isOk) $
751817 do modify' $ registeredHereFor label ^= mPid
752818 updateRemote node currentVal mPid
819+ case mPid of
820+ (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
821+ Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
753822 liftIO $ sendMessage node
754823 (NodeIdentifier (localNodeId node))
755824 (ProcessIdentifier from)
@@ -772,6 +841,7 @@ ncEffectRegister from label atnode mPid reregistration = do
772841 updateRemote _ _ _ = return ()
773842 maybeify f Nothing = unmaybeify $ f []
774843 maybeify f (Just x) = unmaybeify $ f x
844+
775845 unmaybeify [] = Nothing
776846 unmaybeify x = Just x
777847 incList [] tag = [(tag,1)]
@@ -812,16 +882,19 @@ ncEffectNamedSend label msg = do
812882 forM_ mPid $ \pid -> postMessage pid msg
813883
814884-- [Issue #DP-20]
815−ncEffectLocalSend :: ProcessId -> Message -> NC ()
816−ncEffectLocalSend = postMessage
885+ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
886+ncEffectLocalSend node to msg =
887+ liftIO $ withLocalProc node to $ \p -> do
888+ enqueue (processQueue p) msg
889+ trace node (TraceEvReceived to msg)
817890
818891-- [Issue #DP-20]
819892ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
820−ncEffectLocalPortSend from msg =
893+ncEffectLocalPortSend from msg = do
894+ node <- ask
821895 let pid = sendPortProcessId from
822896 cid = sendPortLocalId from
823− in do
824− withLocalProc pid $ \proc -> do
897+ liftIO $ withLocalProc node pid $ \proc -> do
825898 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
826899 case mChan of
827900 -- in the unlikely event we know nothing about this channel id,
@@ -995,21 +1068,21 @@ postAsMessage pid = postMessage pid . createUnencodedMessage
9951068
9961069postMessage :: ProcessId -> Message -> NC ()
9971070postMessage pid msg = do
998− withLocalProc pid $ \p -> enqueue (processQueue p) msg
1071+ node <- ask
1072+ liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
9991073
10001074throwException :: 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
1075+throwException pid e = do
10061076 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
1077+ liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1078+
1079+withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1080+withLocalProc node pid p =
1081+ -- By [Unified: table 6, rule missing_process] messages to dead processes
1082+ -- can silently be dropped
1083+ let lpid = processLocalId pid in do
1084+ mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1085+ forM_ mProc p
10131086
10141087--------------------------------------------------------------------------------
10151088-- Accessors --