packages feed

distributed-process 0.4.2 → 0.5.0

raw patch · 31 files changed

+3874/−2533 lines, 31 filesdep +deepseqdep +hashabledep −HUnitdep −ansi-terminaldep −networkdep ~binarydep ~containersdep ~distributed-staticPVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq, hashable

Dependencies removed: HUnit, ansi-terminal, network, test-framework, test-framework-hunit

Dependency ranges changed: binary, containers, distributed-static, mtl, network-transport, network-transport-tcp, rank1dynamic, stm, syb, template-haskell, transformers

API changes (from Hackage documentation)

- Control.Distributed.Process: AbstractMessage :: (ProcessId -> Process ()) -> (forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)) -> AbstractMessage
- Control.Distributed.Process: data AbstractMessage
- Control.Distributed.Process: data NodeId
- Control.Distributed.Process: maybeHandleMessage :: AbstractMessage -> forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)
- Control.Distributed.Process.Internal.Primitives: AbstractMessage :: (ProcessId -> Process ()) -> (forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)) -> AbstractMessage
- Control.Distributed.Process.Internal.Primitives: data AbstractMessage
- Control.Distributed.Process.Internal.Primitives: maybeHandleMessage :: AbstractMessage -> forall a b. Serializable a => (a -> Process b) -> Process (Maybe b)
- Control.Distributed.Process.Internal.Primitives: trace :: String -> Process ()
- Control.Distributed.Process.Internal.Trace: Trace :: a -> TraceArg
- Control.Distributed.Process.Internal.Trace: TraceStr :: String -> TraceArg
- Control.Distributed.Process.Internal.Trace: data TraceArg
- Control.Distributed.Process.Internal.Trace: data Tracer
- Control.Distributed.Process.Internal.Trace: startTracing :: LocalNode -> IO LocalNode
- Control.Distributed.Process.Internal.Trace: stopTracer :: Tracer -> IO ()
- Control.Distributed.Process.Internal.Trace: trace :: Tracer -> String -> IO ()
- Control.Distributed.Process.Internal.Trace: traceFormat :: Tracer -> String -> [TraceArg] -> IO ()
- Control.Distributed.Process.Internal.Types: EventLogTracer :: !(String -> IO ()) -> Tracer
- Control.Distributed.Process.Internal.Types: InactiveTracer :: Tracer
- Control.Distributed.Process.Internal.Types: LocalNodeTracer :: !LocalNode -> Tracer
- Control.Distributed.Process.Internal.Types: LogFileTracer :: !ThreadId -> !(TQueue String) -> !Handle -> Tracer
- Control.Distributed.Process.Internal.Types: Message :: !Fingerprint -> !ByteString -> Message
- Control.Distributed.Process.Internal.Types: instance Binary (SendPort a)
- Control.Distributed.Process.Internal.Types: instance Typeable1 Process
- Control.Distributed.Process.Internal.Types: instance Typeable1 ReceivePort
- Control.Distributed.Process.Internal.Types: instance Typeable1 SendPort
- Control.Distributed.Process.Internal.Types: localTracer :: LocalNode -> !Tracer
- Control.Distributed.Process.Internal.WeakTQueue: instance Typeable1 TQueue
- Control.Distributed.Process.Serializable: instance Typeable1 SerializableDict
+ Control.Distributed.Process: NodeId :: EndPointAddress -> NodeId
+ Control.Distributed.Process: NodeStats :: NodeId -> Int -> Int -> Int -> Int -> NodeStats
+ Control.Distributed.Process: data Message
+ Control.Distributed.Process: data NodeStats
+ Control.Distributed.Process: delegate :: ProcessId -> (Message -> Bool) -> Process ()
+ Control.Distributed.Process: getLocalNodeStats :: Process NodeStats
+ Control.Distributed.Process: getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)
+ Control.Distributed.Process: handleMessage :: (Monad m, Serializable a) => Message -> (a -> m b) -> m (Maybe b)
+ Control.Distributed.Process: handleMessageIf :: (Monad m, Serializable a) => Message -> (a -> Bool) -> (a -> m b) -> m (Maybe b)
+ Control.Distributed.Process: handleMessageIf_ :: (Monad m, Serializable a) => Message -> (a -> Bool) -> (a -> m ()) -> m ()
+ Control.Distributed.Process: handleMessage_ :: (Monad m, Serializable a) => Message -> (a -> m ()) -> m ()
+ Control.Distributed.Process: isEncoded :: Message -> Bool
+ Control.Distributed.Process: matchMessage :: (Message -> Process Message) -> Match Message
+ Control.Distributed.Process: matchMessageIf :: (Message -> Bool) -> (Message -> Process Message) -> Match Message
+ Control.Distributed.Process: matchSTM :: STM a -> (a -> Process b) -> Match b
+ Control.Distributed.Process: newtype NodeId
+ Control.Distributed.Process: nodeAddress :: NodeId -> EndPointAddress
+ Control.Distributed.Process: nodeStatsLinks :: NodeStats -> Int
+ Control.Distributed.Process: nodeStatsMonitors :: NodeStats -> Int
+ Control.Distributed.Process: nodeStatsNode :: NodeStats -> NodeId
+ Control.Distributed.Process: nodeStatsProcesses :: NodeStats -> Int
+ Control.Distributed.Process: nodeStatsRegisteredNames :: NodeStats -> Int
+ Control.Distributed.Process: proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()
+ Control.Distributed.Process: relay :: ProcessId -> Process ()
+ Control.Distributed.Process: unsafeNSend :: Serializable a => String -> a -> Process ()
+ Control.Distributed.Process: unsafeSend :: Serializable a => ProcessId -> a -> Process ()
+ Control.Distributed.Process: unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()
+ Control.Distributed.Process: unsafeWrapMessage :: Serializable a => a -> Message
+ Control.Distributed.Process: unwrapMessage :: (Monad m, Serializable a) => Message -> m (Maybe a)
+ Control.Distributed.Process: wrapMessage :: Serializable a => a -> Message
+ Control.Distributed.Process.Closure: call' :: Serializable a => NodeId -> Closure (Process a) -> Process a
+ Control.Distributed.Process.Closure: class MkTDict a
+ Control.Distributed.Process.Closure: cpRelay :: ProcessId -> Closure (Process ())
+ Control.Distributed.Process.Closure: mkClosureVal :: (Curry (argTuple -> Closure result) closureFunction, MkTDict result, Uncurry HTrue argTuple func result, Typeable result, Serializable argTuple, IsFunction func HTrue) => String -> func -> (closureFunction, RemoteRegister)
+ Control.Distributed.Process.Closure: mkClosureValSingle :: (Serializable a, Typeable b, MkTDict b) => String -> (a -> b) -> (a -> Closure b, RemoteRegister)
+ Control.Distributed.Process.Closure: mkStaticVal :: Serializable a => String -> a -> (Static a, RemoteRegister)
+ Control.Distributed.Process.Closure: mkTDict :: MkTDict a => String -> a -> RemoteRegister
+ Control.Distributed.Process.Closure: type RemoteRegister = RemoteTable -> RemoteTable
+ Control.Distributed.Process.Debug: Trace :: a -> TraceArg
+ Control.Distributed.Process.Debug: TraceAll :: TraceSubject
+ Control.Distributed.Process.Debug: TraceFlags :: !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !Bool -> !Bool -> TraceFlags
+ Control.Distributed.Process.Debug: TraceNames :: !(Set String) -> TraceSubject
+ Control.Distributed.Process.Debug: TraceProcs :: !(Set ProcessId) -> TraceSubject
+ Control.Distributed.Process.Debug: TraceStr :: String -> TraceArg
+ Control.Distributed.Process.Debug: data TraceArg
+ Control.Distributed.Process.Debug: data TraceFlags
+ Control.Distributed.Process.Debug: data TraceSubject
+ Control.Distributed.Process.Debug: defaultTraceFlags :: TraceFlags
+ Control.Distributed.Process.Debug: disableTrace :: Process ()
+ Control.Distributed.Process.Debug: enableTrace :: ProcessId -> Process ()
+ Control.Distributed.Process.Debug: enableTraceAsync :: ProcessId -> Process ()
+ Control.Distributed.Process.Debug: eventLogTracer :: Process ()
+ Control.Distributed.Process.Debug: getTraceFlags :: Process TraceFlags
+ Control.Distributed.Process.Debug: logfileTracer :: FilePath -> Process ()
+ Control.Distributed.Process.Debug: remoteTable :: RemoteTable -> RemoteTable
+ Control.Distributed.Process.Debug: setTraceFlags :: TraceFlags -> Process ()
+ Control.Distributed.Process.Debug: setTraceFlagsAsync :: TraceFlags -> Process ()
+ Control.Distributed.Process.Debug: setTraceFlagsRemote :: TraceFlags -> NodeId -> Process ()
+ Control.Distributed.Process.Debug: startTraceRelay :: NodeId -> Process ProcessId
+ Control.Distributed.Process.Debug: startTracer :: (MxEvent -> Process ()) -> Process ProcessId
+ Control.Distributed.Process.Debug: stopTracer :: Process ()
+ Control.Distributed.Process.Debug: systemLoggerTracer :: Process ()
+ Control.Distributed.Process.Debug: traceConnections :: TraceFlags -> !Bool
+ Control.Distributed.Process.Debug: traceDied :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: traceLog :: String -> Process ()
+ Control.Distributed.Process.Debug: traceLogFmt :: String -> [TraceArg] -> Process ()
+ Control.Distributed.Process.Debug: traceMessage :: Serializable m => m -> Process ()
+ Control.Distributed.Process.Debug: traceNodes :: TraceFlags -> !Bool
+ Control.Distributed.Process.Debug: traceOff :: Maybe TraceSubject
+ Control.Distributed.Process.Debug: traceOn :: Maybe TraceSubject
+ Control.Distributed.Process.Debug: traceOnly :: Traceable a => [a] -> Maybe TraceSubject
+ Control.Distributed.Process.Debug: traceRecv :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: traceRegistered :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: traceSend :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: traceSpawned :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: traceUnregistered :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Debug: withFlags :: TraceFlags -> Process a -> Process (Either SomeException a)
+ Control.Distributed.Process.Debug: withTracer :: (MxEvent -> Process ()) -> Process a -> Process (Either SomeException a)
+ Control.Distributed.Process.Internal.CQueue: enqueueSTM :: CQueue a -> a -> STM ()
+ Control.Distributed.Process.Internal.Closure.BuiltIn: cpEnableTraceRemote :: ProcessId -> Closure (Process ())
+ Control.Distributed.Process.Internal.Closure.BuiltIn: cpRelay :: ProcessId -> Closure (Process ())
+ Control.Distributed.Process.Internal.Closure.BuiltIn: decodeProcessIdStatic :: Static (ByteString -> ProcessId)
+ Control.Distributed.Process.Internal.Closure.Explicit: call' :: Serializable a => NodeId -> Closure (Process a) -> Process a
+ Control.Distributed.Process.Internal.Closure.Explicit: class MkTDict a
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] (IsFunction func b, Uncurry b args func result) => Uncurry'' args func result
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Binary EndOfTuple
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Curry ((a, EndOfTuple) -> b) (a -> b)
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Curry (b -> c) r => Curry ((a, b) -> c) (a -> r)
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] MkTDict a
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Serializable b => MkTDict (Process b)
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Typeable EndOfTuple
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Uncurry HFalse EndOfTuple a a
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] Uncurry'' rest f r => Uncurry HTrue (a, rest) (a -> f) r
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] b ~ HFalse => IsFunction a b
+ Control.Distributed.Process.Internal.Closure.Explicit: instance [overlap ok] b ~ HTrue => IsFunction (a -> c) b
+ Control.Distributed.Process.Internal.Closure.Explicit: mkClosureVal :: (Curry (argTuple -> Closure result) closureFunction, MkTDict result, Uncurry HTrue argTuple func result, Typeable result, Serializable argTuple, IsFunction func HTrue) => String -> func -> (closureFunction, RemoteRegister)
+ Control.Distributed.Process.Internal.Closure.Explicit: mkClosureValSingle :: (Serializable a, Typeable b, MkTDict b) => String -> (a -> b) -> (a -> Closure b, RemoteRegister)
+ Control.Distributed.Process.Internal.Closure.Explicit: mkStaticVal :: Serializable a => String -> a -> (Static a, RemoteRegister)
+ Control.Distributed.Process.Internal.Closure.Explicit: mkTDict :: MkTDict a => String -> a -> RemoteRegister
+ Control.Distributed.Process.Internal.Closure.Explicit: type RemoteRegister = RemoteTable -> RemoteTable
+ Control.Distributed.Process.Internal.Messaging: sendCtrlMsg :: Maybe NodeId -> ProcessSignal -> Process ()
+ Control.Distributed.Process.Internal.Primitives: NodeStats :: NodeId -> Int -> Int -> Int -> Int -> NodeStats
+ Control.Distributed.Process.Internal.Primitives: data NodeStats
+ Control.Distributed.Process.Internal.Primitives: delegate :: ProcessId -> (Message -> Bool) -> Process ()
+ Control.Distributed.Process.Internal.Primitives: getLocalNodeStats :: Process NodeStats
+ Control.Distributed.Process.Internal.Primitives: getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)
+ Control.Distributed.Process.Internal.Primitives: handleMessage :: (Monad m, Serializable a) => Message -> (a -> m b) -> m (Maybe b)
+ Control.Distributed.Process.Internal.Primitives: handleMessageIf :: (Monad m, Serializable a) => Message -> (a -> Bool) -> (a -> m b) -> m (Maybe b)
+ Control.Distributed.Process.Internal.Primitives: handleMessageIf_ :: (Monad m, Serializable a) => Message -> (a -> Bool) -> (a -> m ()) -> m ()
+ Control.Distributed.Process.Internal.Primitives: handleMessage_ :: (Monad m, Serializable a) => Message -> (a -> m ()) -> m ()
+ Control.Distributed.Process.Internal.Primitives: isEncoded :: Message -> Bool
+ Control.Distributed.Process.Internal.Primitives: matchMessage :: (Message -> Process Message) -> Match Message
+ Control.Distributed.Process.Internal.Primitives: matchMessageIf :: (Message -> Bool) -> (Message -> Process Message) -> Match Message
+ Control.Distributed.Process.Internal.Primitives: matchSTM :: STM a -> (a -> Process b) -> Match b
+ Control.Distributed.Process.Internal.Primitives: nodeStatsLinks :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Primitives: nodeStatsMonitors :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Primitives: nodeStatsNode :: NodeStats -> NodeId
+ Control.Distributed.Process.Internal.Primitives: nodeStatsProcesses :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Primitives: nodeStatsRegisteredNames :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Primitives: proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()
+ Control.Distributed.Process.Internal.Primitives: relay :: ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: sendCtrlMsg :: Maybe NodeId -> ProcessSignal -> Process ()
+ Control.Distributed.Process.Internal.Primitives: unsafeNSend :: Serializable a => String -> a -> Process ()
+ Control.Distributed.Process.Internal.Primitives: unsafeSend :: Serializable a => ProcessId -> a -> Process ()
+ Control.Distributed.Process.Internal.Primitives: unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()
+ Control.Distributed.Process.Internal.Primitives: unsafeWrapMessage :: Serializable a => a -> Message
+ Control.Distributed.Process.Internal.Primitives: unwrapMessage :: (Monad m, Serializable a) => Message -> m (Maybe a)
+ Control.Distributed.Process.Internal.Primitives: wrapMessage :: Serializable a => a -> Message
+ Control.Distributed.Process.Internal.Spawn: call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
+ Control.Distributed.Process.Internal.Spawn: spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
+ Control.Distributed.Process.Internal.Spawn: spawnChannel :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (ReceivePort a -> Process ()) -> Process (SendPort a)
+ Control.Distributed.Process.Internal.Spawn: spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId
+ Control.Distributed.Process.Internal.Spawn: spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
+ Control.Distributed.Process.Internal.Spawn: spawnSupervised :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
+ Control.Distributed.Process.Internal.StrictMVar: readMVar :: StrictMVar a -> IO a
+ Control.Distributed.Process.Internal.Types: EncodedMessage :: !Fingerprint -> !ByteString -> Message
+ Control.Distributed.Process.Internal.Types: GetNodeStats :: !NodeId -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: LocalPortSend :: !SendPortId -> !Message -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: LocalSend :: !ProcessId -> !Message -> ProcessSignal
+ Control.Distributed.Process.Internal.Types: MxEventBus :: !ProcessId -> !Tracer -> !(Weak (CQueue Message)) -> !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId) -> MxEventBus
+ Control.Distributed.Process.Internal.Types: MxEventBusInitialising :: MxEventBus
+ Control.Distributed.Process.Internal.Types: NodeStats :: NodeId -> Int -> Int -> Int -> Int -> NodeStats
+ Control.Distributed.Process.Internal.Types: SigShutdown :: ProcessSignal
+ Control.Distributed.Process.Internal.Types: Tracer :: !ProcessId -> !(Weak (CQueue Message)) -> Tracer
+ Control.Distributed.Process.Internal.Types: UnencodedMessage :: !Fingerprint -> !a -> Message
+ Control.Distributed.Process.Internal.Types: agent :: MxEventBus -> !ProcessId
+ Control.Distributed.Process.Internal.Types: createUnencodedMessage :: Serializable a => a -> Message
+ Control.Distributed.Process.Internal.Types: data MxEventBus
+ Control.Distributed.Process.Internal.Types: data NodeStats
+ Control.Distributed.Process.Internal.Types: evbuss :: MxEventBus -> !(Weak (CQueue Message))
+ Control.Distributed.Process.Internal.Types: instance Binary Message
+ Control.Distributed.Process.Internal.Types: instance Binary NodeStats
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0Identifier
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0NodeId
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0ProcessId
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0SendPort
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_0SendPortId
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_1Identifier
+ Control.Distributed.Process.Internal.Types: instance Constructor C1_2Identifier
+ Control.Distributed.Process.Internal.Types: instance Datatype D1Identifier
+ Control.Distributed.Process.Internal.Types: instance Datatype D1LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Datatype D1MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Datatype D1NodeId
+ Control.Distributed.Process.Internal.Types: instance Datatype D1ProcessId
+ Control.Distributed.Process.Internal.Types: instance Datatype D1SendPort
+ Control.Distributed.Process.Internal.Types: instance Datatype D1SendPortId
+ Control.Distributed.Process.Internal.Types: instance Eq NodeStats
+ Control.Distributed.Process.Internal.Types: instance Generic (SendPort a)
+ Control.Distributed.Process.Internal.Types: instance Generic Identifier
+ Control.Distributed.Process.Internal.Types: instance Generic LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Generic MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Generic NodeId
+ Control.Distributed.Process.Internal.Types: instance Generic ProcessId
+ Control.Distributed.Process.Internal.Types: instance Generic SendPortId
+ Control.Distributed.Process.Internal.Types: instance Hashable Identifier
+ Control.Distributed.Process.Internal.Types: instance Hashable LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Hashable MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Hashable NodeId
+ Control.Distributed.Process.Internal.Types: instance Hashable ProcessId
+ Control.Distributed.Process.Internal.Types: instance Hashable SendPortId
+ Control.Distributed.Process.Internal.Types: instance Hashable a => Hashable (SendPort a)
+ Control.Distributed.Process.Internal.Types: instance NFData NodeId
+ Control.Distributed.Process.Internal.Types: instance NFData ProcessId
+ Control.Distributed.Process.Internal.Types: instance NFData SendPortId
+ Control.Distributed.Process.Internal.Types: instance NFData a => NFData (SendPort a)
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0NodeId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0ProcessId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0SendPort
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_0SendPortId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_1LocalProcessId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_1MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_1ProcessId
+ Control.Distributed.Process.Internal.Types: instance Selector S1_0_1SendPortId
+ Control.Distributed.Process.Internal.Types: instance Serializable a => Binary (SendPort a)
+ Control.Distributed.Process.Internal.Types: instance Show NodeStats
+ Control.Distributed.Process.Internal.Types: instance Typeable Message
+ Control.Distributed.Process.Internal.Types: instance Typeable MonitorRef
+ Control.Distributed.Process.Internal.Types: instance Typeable NodeStats
+ Control.Distributed.Process.Internal.Types: instance Typeable Process
+ Control.Distributed.Process.Internal.Types: instance Typeable ReceivePort
+ Control.Distributed.Process.Internal.Types: instance Typeable SendPort
+ Control.Distributed.Process.Internal.Types: instance Typeable SendPortId
+ Control.Distributed.Process.Internal.Types: isEncoded :: Message -> Bool
+ Control.Distributed.Process.Internal.Types: localEventBus :: LocalNode -> !MxEventBus
+ Control.Distributed.Process.Internal.Types: messagePayload :: Message -> !a
+ Control.Distributed.Process.Internal.Types: mxNew :: MxEventBus -> !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)
+ Control.Distributed.Process.Internal.Types: nodeStatsLinks :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Types: nodeStatsMonitors :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Types: nodeStatsNode :: NodeStats -> NodeId
+ Control.Distributed.Process.Internal.Types: nodeStatsProcesses :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Types: nodeStatsRegisteredNames :: NodeStats -> Int
+ Control.Distributed.Process.Internal.Types: tracer :: MxEventBus -> !Tracer
+ Control.Distributed.Process.Internal.Types: tracerPid :: Tracer -> !ProcessId
+ Control.Distributed.Process.Internal.Types: unsafeCreateUnencodedMessage :: Serializable a => a -> Message
+ Control.Distributed.Process.Internal.Types: weakQ :: Tracer -> !(Weak (CQueue Message))
+ Control.Distributed.Process.Internal.WeakTQueue: instance Typeable TQueue
+ Control.Distributed.Process.Management: MxAgentId :: String -> MxAgentId
+ Control.Distributed.Process.Management: MxConnected :: ConnectionId -> EndPointAddress -> MxEvent
+ Control.Distributed.Process.Management: MxDisconnected :: ConnectionId -> EndPointAddress -> MxEvent
+ Control.Distributed.Process.Management: MxLog :: String -> MxEvent
+ Control.Distributed.Process.Management: MxNodeDied :: NodeId -> DiedReason -> MxEvent
+ Control.Distributed.Process.Management: MxProcessDied :: ProcessId -> DiedReason -> MxEvent
+ Control.Distributed.Process.Management: MxReceived :: ProcessId -> Message -> MxEvent
+ Control.Distributed.Process.Management: MxRegistered :: ProcessId -> String -> MxEvent
+ Control.Distributed.Process.Management: MxSent :: ProcessId -> ProcessId -> Message -> MxEvent
+ Control.Distributed.Process.Management: MxSpawned :: ProcessId -> MxEvent
+ Control.Distributed.Process.Management: MxTraceDisable :: MxEvent
+ Control.Distributed.Process.Management: MxTraceTakeover :: ProcessId -> MxEvent
+ Control.Distributed.Process.Management: MxUnRegistered :: ProcessId -> String -> MxEvent
+ Control.Distributed.Process.Management: MxUser :: Message -> MxEvent
+ Control.Distributed.Process.Management: agentId :: MxAgentId -> String
+ Control.Distributed.Process.Management: data MxAction
+ Control.Distributed.Process.Management: data MxAgent s a
+ Control.Distributed.Process.Management: data MxEvent
+ Control.Distributed.Process.Management: liftMX :: Process a -> MxAgent s a
+ Control.Distributed.Process.Management: mxAgent :: MxAgentId -> s -> [MxSink s] -> Process ProcessId
+ Control.Distributed.Process.Management: mxAgentWithFinalize :: MxAgentId -> s -> [MxSink s] -> MxAgent s () -> Process ProcessId
+ Control.Distributed.Process.Management: mxBroadcast :: Serializable m => m -> MxAgent s ()
+ Control.Distributed.Process.Management: mxClear :: MxAgentId -> String -> Process ()
+ Control.Distributed.Process.Management: mxDeactivate :: String -> MxAgent s MxAction
+ Control.Distributed.Process.Management: mxDropTable :: MxAgentId -> Process ()
+ Control.Distributed.Process.Management: mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)
+ Control.Distributed.Process.Management: mxGetId :: MxAgent s MxAgentId
+ Control.Distributed.Process.Management: mxGetLocal :: MxAgent s s
+ Control.Distributed.Process.Management: mxNotify :: Serializable a => a -> Process ()
+ Control.Distributed.Process.Management: mxPublish :: MxAgentId -> String -> Message -> Process ()
+ Control.Distributed.Process.Management: mxPurgeTable :: MxAgentId -> Process ()
+ Control.Distributed.Process.Management: mxReady :: MxAgent s MxAction
+ Control.Distributed.Process.Management: mxReceive :: MxAgent s MxAction
+ Control.Distributed.Process.Management: mxReceiveChan :: MxAgent s MxAction
+ Control.Distributed.Process.Management: mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()
+ Control.Distributed.Process.Management: mxSetLocal :: s -> MxAgent s ()
+ Control.Distributed.Process.Management: mxSink :: Serializable m => (m -> MxAgent s MxAction) -> MxSink s
+ Control.Distributed.Process.Management: mxSkip :: MxAgent s MxAction
+ Control.Distributed.Process.Management: mxUpdateLocal :: (s -> s) -> MxAgent s ()
+ Control.Distributed.Process.Management: newtype MxAgentId
+ Control.Distributed.Process.Management: type MxSink s = Message -> MxAgent s (Maybe MxAction)
+ Control.Distributed.Process.Management.Internal.Agent: mxAgentController :: Fork -> MVar AgentConfig -> Process ()
+ Control.Distributed.Process.Management.Internal.Agent: mxStartAgent :: Fork -> TChan Message -> ((TChan Message, TChan Message) -> Process ()) -> IO ProcessId
+ Control.Distributed.Process.Management.Internal.Agent: startDeadLetterQueue :: TChan Message -> IO ()
+ Control.Distributed.Process.Management.Internal.Agent: startTracing :: Fork -> IO Tracer
+ Control.Distributed.Process.Management.Internal.Agent: type AgentConfig = (Tracer, Weak (CQueue Message), ((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)
+ Control.Distributed.Process.Management.Internal.Bus: publishEvent :: MxEventBus -> Message -> IO ()
+ Control.Distributed.Process.Management.Internal.Table: Clear :: !String -> MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: Delete :: MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: Get :: !String -> !(SendPort (Maybe Message)) -> MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: MxForAgent :: !MxAgentId -> MxTableId
+ Control.Distributed.Process.Management.Internal.Table: MxForPid :: !ProcessId -> MxTableId
+ Control.Distributed.Process.Management.Internal.Table: Purge :: MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: Set :: !String -> !Message -> MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: clear :: String -> MxTableId -> Process ()
+ Control.Distributed.Process.Management.Internal.Table: data MxTableId
+ Control.Distributed.Process.Management.Internal.Table: data MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: delete :: MxTableId -> Process ()
+ Control.Distributed.Process.Management.Internal.Table: fetch :: Serializable a => MxTableId -> String -> Process (Maybe a)
+ Control.Distributed.Process.Management.Internal.Table: get :: Serializable a => ProcessId -> String -> Process (Maybe a)
+ Control.Distributed.Process.Management.Internal.Table: instance Binary MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Constructor C1_0MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Constructor C1_1MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Constructor C1_2MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Constructor C1_3MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Constructor C1_4MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Datatype D1MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Generic MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: instance Typeable MxTableRequest
+ Control.Distributed.Process.Management.Internal.Table: mxTableCoordinator :: String
+ Control.Distributed.Process.Management.Internal.Table: purge :: MxTableId -> Process ()
+ Control.Distributed.Process.Management.Internal.Table: set :: String -> Message -> MxTableId -> Process ()
+ Control.Distributed.Process.Management.Internal.Table: startTableCoordinator :: Fork -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: defaultTraceFlags :: TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: disableTrace :: Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: disableTraceAsync :: Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: enableTrace :: ProcessId -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: enableTraceAsync :: ProcessId -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: getTraceFlags :: Process TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: instance Traceable ProcessId
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: instance Traceable String
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: setTraceFlags :: TraceFlags -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: setTraceFlagsAsync :: TraceFlags -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceLog :: String -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceLogFmt :: String -> [TraceArg] -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceMessage :: Serializable m => m -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceOff :: Maybe TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceOn :: Maybe TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: traceOnly :: Traceable a => [a] -> Maybe TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: withLocalTracer :: (MxEventBus -> Process ()) -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Primitives: withRegisteredTracer :: (ProcessId -> Process a) -> Process a
+ Control.Distributed.Process.Management.Internal.Trace.Remote: remoteTable :: RemoteTable -> RemoteTable
+ Control.Distributed.Process.Management.Internal.Trace.Remote: setTraceFlagsRemote :: TraceFlags -> NodeId -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Remote: startTraceRelay :: NodeId -> Process ProcessId
+ Control.Distributed.Process.Management.Internal.Trace.Tracer: defaultTracer :: Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Tracer: eventLogTracer :: Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Tracer: logfileTracer :: FilePath -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Tracer: systemLoggerTracer :: Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Tracer: traceController :: MVar ((Weak (CQueue Message))) -> Process ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: Trace :: a -> TraceArg
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceAll :: TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceDisable :: SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceEnable :: !ProcessId -> SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceFlags :: !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !(Maybe TraceSubject) -> !Bool -> !Bool -> TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceNames :: !(Set String) -> TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceOk :: TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceProcs :: !(Set ProcessId) -> TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: TraceStr :: String -> TraceArg
+ Control.Distributed.Process.Management.Internal.Trace.Types: data SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: data TraceArg
+ Control.Distributed.Process.Management.Internal.Trace.Types: data TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: data TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: data TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: defaultTraceFlags :: TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: disableTrace :: MxEventBus -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: enableTrace :: MxEventBus -> ProcessId -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Binary SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Binary TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Binary TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Binary TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_0SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_0TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_0TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_0TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_1SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_1TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Constructor C1_2TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Datatype D1SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Datatype D1TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Datatype D1TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Datatype D1TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Eq SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Generic SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Generic TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Generic TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Generic TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_0TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_1TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_2TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_3TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_4TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_5TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_6TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Selector S1_0_7TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Show SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Show TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Show TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Traceable ProcessId
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Traceable String
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Typeable SetTrace
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Typeable TraceFlags
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Typeable TraceOk
+ Control.Distributed.Process.Management.Internal.Trace.Types: instance Typeable TraceSubject
+ Control.Distributed.Process.Management.Internal.Trace.Types: setTraceFlags :: MxEventBus -> TraceFlags -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceConnections :: TraceFlags -> !Bool
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceDied :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceEvent :: MxEventBus -> MxEvent -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceLog :: MxEventBus -> String -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceLogFmt :: MxEventBus -> String -> [TraceArg] -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceMessage :: Serializable m => MxEventBus -> m -> IO ()
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceNodes :: TraceFlags -> !Bool
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceRecv :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceRegistered :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceSend :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceSpawned :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Trace.Types: traceUnregistered :: TraceFlags -> !(Maybe TraceSubject)
+ Control.Distributed.Process.Management.Internal.Types: InputChan :: ChannelSelector
+ Control.Distributed.Process.Management.Internal.Types: Mailbox :: ChannelSelector
+ Control.Distributed.Process.Management.Internal.Types: MxAgent :: StateT (MxAgentState s) Process a -> MxAgent s a
+ Control.Distributed.Process.Management.Internal.Types: MxAgentDeactivate :: !String -> MxAction
+ Control.Distributed.Process.Management.Internal.Types: MxAgentId :: String -> MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: MxAgentPrioritise :: !ChannelSelector -> MxAction
+ Control.Distributed.Process.Management.Internal.Types: MxAgentReady :: MxAction
+ Control.Distributed.Process.Management.Internal.Types: MxAgentSkip :: MxAction
+ Control.Distributed.Process.Management.Internal.Types: MxAgentStart :: SendPort ProcessId -> MxAgentId -> MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: MxAgentState :: !MxAgentId -> !(TChan Message) -> !ProcessId -> !s -> MxAgentState s
+ Control.Distributed.Process.Management.Internal.Types: MxConnected :: ConnectionId -> EndPointAddress -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxDisconnected :: ConnectionId -> EndPointAddress -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxForAgent :: !MxAgentId -> MxTableId
+ Control.Distributed.Process.Management.Internal.Types: MxForPid :: !ProcessId -> MxTableId
+ Control.Distributed.Process.Management.Internal.Types: MxLog :: String -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxNodeDied :: NodeId -> DiedReason -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxProcessDied :: ProcessId -> DiedReason -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxReceived :: ProcessId -> Message -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxRegistered :: ProcessId -> String -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxSent :: ProcessId -> ProcessId -> Message -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxSpawned :: ProcessId -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxTraceDisable :: MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxTraceTakeover :: ProcessId -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxUnRegistered :: ProcessId -> String -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: MxUser :: Message -> MxEvent
+ Control.Distributed.Process.Management.Internal.Types: agentId :: MxAgentId -> String
+ Control.Distributed.Process.Management.Internal.Types: class Addressable a
+ Control.Distributed.Process.Management.Internal.Types: data ChannelSelector
+ Control.Distributed.Process.Management.Internal.Types: data MxAction
+ Control.Distributed.Process.Management.Internal.Types: data MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: data MxAgentState s
+ Control.Distributed.Process.Management.Internal.Types: data MxEvent
+ Control.Distributed.Process.Management.Internal.Types: data MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Addressable MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Applicative (MxAgent s)
+ Control.Distributed.Process.Management.Internal.Types: instance Binary MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: instance Binary MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Binary MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Binary MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_0MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_0MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_0MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_10MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_11MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_12MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_1MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_1MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_2MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_3MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_4MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_5MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_6MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_7MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_8MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Constructor C1_9MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Datatype D1MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Datatype D1MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Datatype D1MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Eq MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: instance Functor (MxAgent s)
+ Control.Distributed.Process.Management.Internal.Types: instance Generic MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Generic MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Generic MxTableId
+ Control.Distributed.Process.Management.Internal.Types: instance Monad (MxAgent s)
+ Control.Distributed.Process.Management.Internal.Types: instance MonadIO (MxAgent s)
+ Control.Distributed.Process.Management.Internal.Types: instance MonadState (MxAgentState s) (MxAgent s)
+ Control.Distributed.Process.Management.Internal.Types: instance Ord MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: instance Selector S1_0_0MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Selector S1_0_1MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Show MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Typeable MxAgent
+ Control.Distributed.Process.Management.Internal.Types: instance Typeable MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: instance Typeable MxAgentStart
+ Control.Distributed.Process.Management.Internal.Types: instance Typeable MxEvent
+ Control.Distributed.Process.Management.Internal.Types: instance Typeable MxTableId
+ Control.Distributed.Process.Management.Internal.Types: mxAgentId :: MxAgentState s -> !MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: mxAgentIdStart :: MxAgentStart -> MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: mxAgentTableChan :: MxAgentStart -> SendPort ProcessId
+ Control.Distributed.Process.Management.Internal.Types: mxBus :: MxAgentState s -> !(TChan Message)
+ Control.Distributed.Process.Management.Internal.Types: mxLocalState :: MxAgentState s -> !s
+ Control.Distributed.Process.Management.Internal.Types: mxSharedTable :: MxAgentState s -> !ProcessId
+ Control.Distributed.Process.Management.Internal.Types: newtype MxAgent s a
+ Control.Distributed.Process.Management.Internal.Types: newtype MxAgentId
+ Control.Distributed.Process.Management.Internal.Types: resolveToPid :: Addressable a => a -> Maybe ProcessId
+ Control.Distributed.Process.Management.Internal.Types: type Fork = Process () -> IO ProcessId
+ Control.Distributed.Process.Management.Internal.Types: type MxSink s = Message -> MxAgent s (Maybe MxAction)
+ Control.Distributed.Process.Management.Internal.Types: unAgent :: MxAgent s a -> StateT (MxAgentState s) Process a
+ Control.Distributed.Process.Serializable: instance Typeable SerializableDict
+ Control.Distributed.Process.UnsafePrimitives: nsend :: Serializable a => String -> a -> Process ()
+ Control.Distributed.Process.UnsafePrimitives: send :: Serializable a => ProcessId -> a -> Process ()
+ Control.Distributed.Process.UnsafePrimitives: sendChan :: Serializable a => SendPort a -> a -> Process ()
+ Control.Distributed.Process.UnsafePrimitives: wrapMessage :: Serializable a => a -> Message
- Control.Distributed.Process: catchesExit :: Process b -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))] -> Process b
+ Control.Distributed.Process: catchesExit :: Process b -> [(ProcessId -> Message -> (Process (Maybe b)))] -> Process b
- Control.Distributed.Process: forward :: AbstractMessage -> ProcessId -> Process ()
+ Control.Distributed.Process: forward :: Message -> ProcessId -> Process ()
- Control.Distributed.Process: matchAny :: (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process: matchAny :: (Message -> Process b) -> Match b
- Control.Distributed.Process: matchAnyIf :: Serializable a => (a -> Bool) -> (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process: matchAnyIf :: Serializable a => (a -> Bool) -> (Message -> Process b) -> Match b
- Control.Distributed.Process: spawnChannel :: Typeable a => Static (SerializableDict a) -> NodeId -> Closure (ReceivePort a -> Process ()) -> Process (SendPort a)
+ Control.Distributed.Process: spawnChannel :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (ReceivePort a -> Process ()) -> Process (SendPort a)
- Control.Distributed.Process.Internal.Primitives: catchesExit :: Process b -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))] -> Process b
+ Control.Distributed.Process.Internal.Primitives: catchesExit :: Process b -> [(ProcessId -> Message -> (Process (Maybe b)))] -> Process b
- Control.Distributed.Process.Internal.Primitives: forward :: AbstractMessage -> ProcessId -> Process ()
+ Control.Distributed.Process.Internal.Primitives: forward :: Message -> ProcessId -> Process ()
- Control.Distributed.Process.Internal.Primitives: matchAny :: (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process.Internal.Primitives: matchAny :: (Message -> Process b) -> Match b
- Control.Distributed.Process.Internal.Primitives: matchAnyIf :: Serializable a => (a -> Bool) -> (AbstractMessage -> Process b) -> Match b
+ Control.Distributed.Process.Internal.Primitives: matchAnyIf :: Serializable a => (a -> Bool) -> (Message -> Process b) -> Match b
- Control.Distributed.Process.Internal.Types: LocalNode :: !NodeId -> !EndPoint -> !(StrictMVar LocalNodeState) -> !(Chan NCMsg) -> !Tracer -> !RemoteTable -> LocalNode
+ Control.Distributed.Process.Internal.Types: LocalNode :: !NodeId -> !EndPoint -> !(StrictMVar LocalNodeState) -> !(Chan NCMsg) -> !MxEventBus -> !RemoteTable -> LocalNode

Files

+ ChangeLog view
@@ -0,0 +1,120 @@+2014-30-05  Tim Watson  <watson.timothy@gmail.com>  0.5.0++* Dependency on STM implicitly changed from 1.3 to 1.4, but was not reflected in the cabal file+* Race condition in local monitoring when using call+* mask now works correctly if unmask is called by another process+* Improve efficiency of local message passing+* nsend uses local communication channels+* Link Node Controller and Network Listener+* Label spawned processes using labelThread+* Relaxed upper bound on syb in the cabal manifest+* Bump binary version to include 0.7.*+* Exposed process info+* Exposed node statistics+* Moved tests to https://github.com/haskell-distributed/distributed-process-tests+* Added "polymorphic expect"+* Exposed Message type and broaden scope of polymorphic expect+* Added Management API (for working with internal/system events)+* Tracing can no longer be disabled+* We now report node statistics for monitoring/management+* Node.runProcess now propagates exceptions to its caller+* Added simple micro benchmarks++2013-01-27  Tim Watson  <watson.timothy@gmail.com>  0.4.2++* Improved exception handling for deferred type checked exit reasons+* Add matchChan primitive (thanks Simon Marlow)+* Expose deferred message handling/checking for AbstractMessage+* Add `getProcessInfo' API+* Add `trace' API backed by the GHC eventlog++2012-11-22  Edsko de Vries  <edsko@well-typed.com>  0.4.1++* Make behaviour of 'register' more Erlang-like (register will now fail if the+name is already registered).  Patch by Jeff Epstein.+* Functor, Applicative, Alternative and Monad instances for ReceivePort+* Add support for receiveChanTimeout+* Improved documentation+* Avoid name clashes in the TH generation for closures+* Relax package bounds to allow for Binary 0.6++2012-10-23  Edsko de Vries  <edsko@well-typed.com>  0.4.0.2++* Fix race condition in spawn++2012-10-04  Edsko de Vries  <edsko@well-typed.com>  0.4.0.1++* Relax package boundaries++2012-10-03  Edsko de Vries  <edsko@well-typed.com>  0.4.0++* Improved treatment of network failure, using new failure semantics of+Network.Transport.+* Make NodeId Typeable+* Extend Template Haskell support with "remotableDec" so that you can refer to+$(mkClosure 'f) within the body of "f".+* Fix bug in spawnChannelLocal+* Numerous memory leaks plugged+* Relax upper bound on dependency on 'network'+* New primitive 'matchAny'+* Remove 'whereisRemote' (see comment of 'whereisRemoteAsync') ++2012-08-16  Edsko de Vries  <edsko@well-typed.com>  0.3.1++* Fix memory leaks+* Make Template Haskell support optional+* Relax dependency constraints++2012-08-07  Edsko de Vries  <edsko@well-typed.com>  0.3.0++* Extract 'static' into a separate package (C.D.Static)+* Use new package rank1dynamic to proper runtime checks for polymorphic values++2012-08-02  Edsko de Vries  <edsko@well-typed.com>  0.2.3.0++* Expose the constructors of Closure+* Add instance (Typeable a => Serializable (Static a)) and make sure we only+use the internal representation of Static where really necessary+* Improved docs++2012-07-31  Edsko de Vries  <edsko@well-typed.com>  0.2.2.0++* Add exception handling primitives+* Fix runProcess: if the process threw an exception, a 'waiting indefinitely on+MVar' exception would be thrown.++2012-07-21  Edsko de Vries  <edsko@well-typed.com>  0.2.1.4++* Bugfix in the node controller+(one way this bug materialized: when using the SimpleLocalnet backend,+slave nodes could not be reused)+* Improved documentation in Control.Distributed.Process.Closure++2012-07-20  Edsko de Vries  <edsko@well-typed.com>  0.2.1.3++* Improve docs+* Local versions of spawn++2012-07-16  Edsko de Vries  <edsko@well-typed.com>  0.2.1.2++* Base 4.6 compatibility+* Relax constraints on bytestring and containers++2012-07-16  Edsko de Vries  <edsko@well-typed.com>  0.2.1.1++* Relax upper bound on 'time' dependency++2012-07-11  Edsko de Vries  <edsko@well-typed.com>  0.2.1++* Complete redesign of the underlying implementation of static values and+closures. ++* Add support for 'spawnChannel' ++2012-07-09  Edsko de Vries  <edsko@well-typed.com>  0.2.0.1++* Bugfix: Continue processing messages when a connection breaks.++2012-07-07  Edsko de Vries  <edsko@well-typed.com>  0.2.0++* Initial release.
+ benchmarks/ProcessRing.hs view
@@ -0,0 +1,113 @@+{- ProcessRing benchmarks.++To run the benchmarks, select a value for the ring size (sz) and+the number of times to send a message around the ring++-}+import Control.Monad+import Control.Distributed.Process hiding (catch)+import Control.Distributed.Process.Node+import Control.Exception (catch, SomeException)+import Network.Transport.TCP (createTransport, defaultTCPParameters)+import System.Environment+import System.Console.GetOpt++data Options = Options+  { optRingSize   :: Int+  , optIterations :: Int+  , optForward    :: Bool+  , optParallel   :: Bool+  , optUnsafe     :: Bool+  } deriving Show++initialProcess :: Options -> Process ()+initialProcess op =+  let ringSz = optRingSize op+      msgCnt = optIterations op+      fwd    = optForward op+      unsafe = optUnsafe op+      msg    = ("foobar", "baz")+  in do+    self <- getSelfPid+    ring <- makeRing fwd unsafe ringSz self+    forM_ [1..msgCnt] (\_ -> send ring msg)+    collect msgCnt+  where relay fsend pid = do+          msg <- expect :: Process (String, String)+          fsend pid msg+          relay fsend pid++        forward' pid =+          receiveWait [ matchAny (\m -> forward m pid) ] >> forward' pid++        makeRing :: Bool -> Bool -> Int -> ProcessId -> Process ProcessId+        makeRing !f !u !n !pid+          | n == 0    = go f u pid+          | otherwise = go f u pid >>= makeRing f u (n - 1)++        go :: Bool -> Bool -> ProcessId -> Process ProcessId+        go False False next = spawnLocal $ relay send next+        go False True  next = spawnLocal $ relay unsafeSend next+        go True  _     next = spawnLocal $ forward' next++        collect :: Int -> Process ()+        collect !n+          | n == 0    = return ()+          | otherwise = do+                receiveWait [+                    matchIf    (\(a, b) -> a == "foobar" && b == "baz")+                               (\_ -> return ())+                  , matchAny   (\_ -> error "unexpected input!")+                  ]+                collect (n - 1)++defaultOptions :: Options+defaultOptions = Options+  { optRingSize   = 10+  , optIterations = 100+  , optForward    = False+  , optParallel   = False+  , optUnsafe     = False+  }++options :: [OptDescr (Options -> Options)]+options =+    [ Option ['s'] ["ring-size"] (OptArg optSz "SIZE") "# of processes in ring"+    , Option ['i'] ["iterations"] (OptArg optMsgCnt "ITER") "# of times to send"+    , Option ['f'] ["forward"]+        (NoArg (\opts -> opts { optForward = True }))+        "use `forward' instead of send - default = False"+    , Option ['u'] ["unsafe-send"]+        (NoArg (\opts -> opts { optUnsafe = True }))+        "use 'unsafeSend' (ignored with -f) - default = False"+    , Option ['p'] ["parallel"]+        (NoArg (\opts -> opts { optParallel = True }))+        "send in parallel and consume sequentially - default = False"+    ]++optMsgCnt :: Maybe String -> Options -> Options+optMsgCnt Nothing  opts = opts+optMsgCnt (Just c) opts = opts { optIterations = ((read c) :: Int) }++optSz :: Maybe String -> Options -> Options+optSz Nothing  opts = opts+optSz (Just s) opts = opts { optRingSize = ((read s) :: Int) }++parseArgv :: [String] -> IO (Options, [String])+parseArgv argv = do+  pn <- getProgName+  case getOpt Permute options argv of+    (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)+    (_,_,errs) -> ioError (userError (concat errs ++ usageInfo (header pn) options))+  where header pn' = "Usage: " ++ pn' ++ " [OPTION...]"++main :: IO ()+main = do+  argv <- getArgs+  (opt, _) <- parseArgv argv+  putStrLn $ "options: " ++ (show opt)+  Right transport <- createTransport "127.0.0.1" "8090" defaultTCPParameters+  node <- newLocalNode transport initRemoteTable+  catch (void $ runProcess node $ initialProcess opt)+        (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))+
distributed-process.cabal view
@@ -1,5 +1,5 @@ Name:          distributed-process-Version:       0.4.2+Version:       0.5.0 Cabal-Version: >=1.8 Build-Type:    Simple License:       BSD3@@ -8,8 +8,8 @@ Author:        Duncan Coutts, Nicolas Wu, Edsko de Vries Maintainer:    watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com Stability:     experimental-Homepage:      http://github.com/haskell-distributed/distributed-process-Bug-Reports:   http://github.com/haskell-distributed/distributed-process/issues+Homepage:      http://haskell-distributed.github.com/+Bug-Reports:   http://cloud-haskell.atlassian.net Synopsis:      Cloud Haskell: Erlang-style concurrency in Haskell Description:   This is an implementation of Cloud Haskell, as described in                /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,@@ -21,8 +21,9 @@                 You will probably also want to install a Cloud Haskell backend such                as distributed-process-simplelocalnet.-Tested-With:   GHC==7.2.2 GHC==7.4.1 GHC==7.4.2+Tested-With:   GHC==7.2.2 GHC==7.4.1 GHC==7.4.2 GHC==7.6.2 Category:      Control+extra-source-files: ChangeLog  Source-Repository head   Type:     git@@ -33,171 +34,127 @@   description: Build with Template Haskell support   default: True -flag benchmarks-  description: Build benchmarks+flag prof+  description: Compiling with profiling enabled   default: False  Library   Build-Depends:     base >= 4.4 && < 5,-                     binary >= 0.5 && < 0.7,-                     network-transport >= 0.3 && < 0.4,-                     stm >= 2.3 && < 2.5,-                     transformers >= 0.2 && < 0.4,-                     mtl >= 2.0 && < 2.2,+                     binary >= 0.6.3 && < 0.8,+                     hashable >= 1.2.0.5 && < 1.3,+                     network-transport >= 0.4.0.0 && < 0.5,+                     stm >= 2.4 && < 2.5,+                     transformers >= 0.2 && < 0.5,+                     mtl >= 2.0 && < 2.3,                      data-accessor >= 0.2 && < 0.3,                      bytestring >= 0.9 && < 0.11,-                     containers >= 0.4 && < 0.6,                      old-locale >= 1.0 && < 1.1,                      time >= 1.2 && < 1.5,                      random >= 1.0 && < 1.1,                      ghc-prim >= 0.2 && < 0.4,-                     distributed-static >= 0.2 && < 0.3,-                     rank1dynamic >= 0.1 && < 0.2,-                     syb >= 0.3 && < 0.4+                     distributed-static >= 0.2 && < 0.4,+                     rank1dynamic >= 0.1 && < 0.3,+                     syb >= 0.3 && < 0.5   Exposed-modules:   Control.Distributed.Process,-                     Control.Distributed.Process.Serializable,                      Control.Distributed.Process.Closure,-                     Control.Distributed.Process.Node,-                     Control.Distributed.Process.Internal.Primitives,-                     Control.Distributed.Process.Internal.CQueue,-                     Control.Distributed.Process.Internal.Types,-                     Control.Distributed.Process.Internal.Trace,+                     Control.Distributed.Process.Debug,                      Control.Distributed.Process.Internal.Closure.BuiltIn,+                     Control.Distributed.Process.Internal.Closure.Explicit,+                     Control.Distributed.Process.Internal.CQueue,                      Control.Distributed.Process.Internal.Messaging,+                     Control.Distributed.Process.Internal.Primitives,+                     Control.Distributed.Process.Internal.Spawn,+                     Control.Distributed.Process.Internal.StrictContainerAccessors,                      Control.Distributed.Process.Internal.StrictList,                      Control.Distributed.Process.Internal.StrictMVar,-                     Control.Distributed.Process.Internal.WeakTQueue-                     Control.Distributed.Process.Internal.StrictContainerAccessors-  Extensions:        RankNTypes,-                     ScopedTypeVariables,-                     FlexibleInstances,-                     UndecidableInstances,-                     ExistentialQuantification,-                     GADTs,-                     GeneralizedNewtypeDeriving,-                     DeriveDataTypeable,-                     CPP,-                     BangPatterns+                     Control.Distributed.Process.Internal.Types,+                     Control.Distributed.Process.Internal.WeakTQueue,+                     Control.Distributed.Process.Management,+                     Control.Distributed.Process.Node,+                     Control.Distributed.Process.Serializable,+                     Control.Distributed.Process.UnsafePrimitives+                     Control.Distributed.Process.Management.Internal.Agent,+                     Control.Distributed.Process.Management.Internal.Bus,+                     Control.Distributed.Process.Management.Internal.Table,+                     Control.Distributed.Process.Management.Internal.Types,+                     Control.Distributed.Process.Management.Internal.Trace.Primitives,+                     Control.Distributed.Process.Management.Internal.Trace.Remote,+                     Control.Distributed.Process.Management.Internal.Trace.Types,+                     Control.Distributed.Process.Management.Internal.Trace.Tracer+  -- Extensions:     -- we use explicit per-file LANGUAGE pragmas   ghc-options:       -Wall   HS-Source-Dirs:    src+  if impl(ghc <= 7.4.2)+     Build-Depends:   containers >= 0.4 && < 0.5,+                      deepseq == 1.3.0.0+  else+     Build-Depends:   containers >= 0.4 && < 0.6,+                      deepseq >= 1.3.0.1 && < 1.4   if flag(th)-     Build-Depends:   template-haskell >= 2.6 && < 2.9+     if impl(ghc <= 7.4.2)+       Build-Depends: template-haskell >= 2.7 && < 2.8+     else+       Build-Depends: template-haskell >= 2.6 && < 2.10      Exposed-modules: Control.Distributed.Process.Internal.Closure.TH      CPP-Options:     -DTemplateHaskellSupport -Test-Suite TestCH-  Type:              exitcode-stdio-1.0-  Main-Is:           TestCH.hs-  Build-Depends:     base >= 4.4 && < 5,-                     random >= 1.0 && < 1.1,-                     ansi-terminal >= 0.5 && < 0.6,-                     distributed-process,-                     network-transport >= 0.3 && < 0.4,-                     network-transport-tcp >= 0.3 && < 0.4,-                     binary >= 0.5 && < 0.7,-                     network >= 2.3 && < 2.5,-                     HUnit >= 1.2 && < 1.3,-                     test-framework >= 0.6 && < 0.9,-                     test-framework-hunit >= 0.2.0 && < 0.4-  Extensions:        CPP,-                     ScopedTypeVariables,-                     DeriveDataTypeable,-                     GeneralizedNewtypeDeriving-  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind-  HS-Source-Dirs:    tests--Test-Suite TestClosure-  Type:              exitcode-stdio-1.0-  Main-Is:           TestClosure.hs-  Build-Depends:     base >= 4.4 && < 5,-                     random >= 1.0 && < 1.1,-                     ansi-terminal >= 0.5 && < 0.6,-                     distributed-static >= 0.2 && < 0.3,-                     distributed-process,-                     network-transport >= 0.3 && < 0.4,-                     network-transport-tcp >= 0.3 && < 0.4,-                     bytestring >= 0.9 && < 0.11,-                     network >= 2.3 && < 2.5,-                     HUnit >= 1.2 && < 1.3,-                     test-framework >= 0.6 && < 0.9,-                     test-framework-hunit >= 0.2.0 && < 0.4-  Extensions:        CPP,-                     ScopedTypeVariables-  ghc-options:       -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind-  HS-Source-Dirs:    tests--Test-Suite TestStats-  Type:              exitcode-stdio-1.0-  Main-Is:           TestStats.hs-  Build-Depends:     base >= 4.4 && < 5,-                     random >= 1.0 && < 1.1,-                     ansi-terminal >= 0.5 && < 0.6,-                     containers >= 0.4 && < 0.6,-                     stm >= 2.3 && < 2.5,-                     distributed-process,-                     network-transport >= 0.3 && < 0.4,-                     network-transport-tcp >= 0.3 && < 0.4,-                     binary >= 0.5 && < 0.7,-                     network >= 2.3 && < 2.5,-                     HUnit >= 1.2 && < 1.3,-                     test-framework >= 0.6 && < 0.9,-                     test-framework-hunit >= 0.2.0 && < 0.4-  Extensions:        CPP,-                     ScopedTypeVariables,-                     DeriveDataTypeable,-                     GeneralizedNewtypeDeriving-  ghc-options:       -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind-  HS-Source-Dirs:    tests +-- Tests are in distributed-process-test package, for convenience. +benchmark distributed-process-throughput+  Type:            exitcode-stdio-1.0        +  Build-Depends:   base >= 4.4 && < 5,+                   distributed-process,+                   network-transport-tcp >= 0.3 && < 0.5,+                   bytestring >= 0.9 && < 0.11,+                   binary >= 0.6.3 && < 0.8+  Main-Is:         benchmarks/Throughput.hs+  ghc-options:     -Wall+  Extensions:      BangPatterns -Executable distributed-process-throughput -  if flag(benchmarks)-    Build-Depends:   base >= 4.4 && < 5,-                     distributed-process,-                     network-transport-tcp >= 0.3 && < 0.4,-                     bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.7-  else-    buildable: False-  Main-Is:           benchmarks/Throughput.hs-  ghc-options:       -Wall-  Extensions:        BangPatterns+benchmark distributed-process-latency+  Type:            exitcode-stdio-1.0+  Build-Depends:   base >= 4.4 && < 5,+                   distributed-process,+                   network-transport-tcp >= 0.3 && < 0.5,+                   bytestring >= 0.9 && < 0.11,+                   binary >= 0.6.3 && < 0.8+  Main-Is:         benchmarks/Latency.hs+  ghc-options:     -Wall+  Extensions:      BangPatterns -Executable distributed-process-latency-  if flag(benchmarks)-    Build-Depends:   base >= 4.4 && < 5,-                     distributed-process,-                     network-transport-tcp >= 0.3 && < 0.4,-                     bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.7-  else-    buildable: False-  Main-Is:           benchmarks/Latency.hs-  ghc-options:       -Wall-  Extensions:        BangPatterns+benchmark distributed-process-channels+  Type:            exitcode-stdio-1.0+  Build-Depends:   base >= 4.4 && < 5,+                   distributed-process,+                   network-transport-tcp >= 0.3 && < 0.5,+                   bytestring >= 0.9 && < 0.11,+                   binary >= 0.6.3 && < 0.8+  Main-Is:         benchmarks/Channels.hs+  ghc-options:     -Wall+  Extensions:      BangPatterns -Executable distributed-process-channels-  if flag(benchmarks)-    Build-Depends:   base >= 4.4 && < 5,-                     distributed-process,-                     network-transport-tcp >= 0.3 && < 0.4,-                     bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.7-  else-    buildable: False-  Main-Is:           benchmarks/Channels.hs-  ghc-options:       -Wall-  Extensions:        BangPatterns+benchmark distributed-process-spawns+  Type:            exitcode-stdio-1.0+  Build-Depends:   base >= 4.4 && < 5,+                   distributed-process,+                   network-transport-tcp >= 0.3 && < 0.5,+                   bytestring >= 0.9 && < 0.11,+                   binary >= 0.6.3 && < 0.8+  Main-Is:         benchmarks/Spawns.hs+  ghc-options:     -Wall+  Extensions:      BangPatterns -Executable distributed-process-spawns-  if flag(benchmarks)-    Build-Depends:   base >= 4.4 && < 5,-                     distributed-process,-                     network-transport-tcp >= 0.3 && < 0.4,-                     bytestring >= 0.9 && < 0.11,-                     binary >= 0.5 && < 0.7+benchmark distributed-process-ring+  Type:            exitcode-stdio-1.0+  Build-Depends:   base >= 4.4 && < 5,+                   distributed-process,+                   network-transport-tcp >= 0.3 && < 0.5,+                   bytestring >= 0.9 && < 0.11,+                   binary >= 0.6.3 && < 0.8+  Main-Is:         benchmarks/ProcessRing.hs+  if flag(prof)+    ghc-options:   -Wall -threaded -prof -auto-all -fno-prof-count-entries -rtsopts   else-    buildable: False-  Main-Is:           benchmarks/Spawns.hs-  ghc-options:       -Wall-  Extensions:        BangPatterns+    ghc-options:   -Wall -threaded -O2 -rtsopts+  Extensions:      BangPatterns,+                   ScopedTypeVariables
src/Control/Distributed/Process.hs view
@@ -1,8 +1,10 @@+{-# LANGUAGE CPP  #-} {- | [Cloud Haskell]  This is an implementation of Cloud Haskell, as described in /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon-Peyton Jones (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),+Peyton Jones (see+<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>), although some of the details are different. The precise message passing semantics are based on /A unified semantics for future Erlang/ by Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.@@ -15,7 +17,7 @@ module Control.Distributed.Process   ( -- * Basic types     ProcessId-  , NodeId+  , NodeId(..)   , Process   , SendPortId   , processNodeId@@ -35,6 +37,11 @@   , receiveChanTimeout   , mergePortsBiased   , mergePortsRR+    -- * Unsafe messaging variants+  , unsafeSend+  , unsafeSendChan+  , unsafeNSend+  , unsafeWrapMessage     -- * Advanced messaging   , Match   , receiveWait@@ -42,10 +49,24 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..)   , matchAny   , matchAnyIf   , matchChan+  , matchSTM+  , Message+  , matchMessage+  , matchMessageIf+  , isEncoded+  , wrapMessage+  , unwrapMessage+  , handleMessage+  , handleMessageIf+  , handleMessage_+  , handleMessageIf_+  , forward+  , delegate+  , relay+  , proxy     -- * Process management   , spawn   , call@@ -62,6 +83,9 @@   , getSelfNode   , ProcessInfo(..)   , getProcessInfo+  , NodeStats(..)+  , getNodeStats+  , getLocalNodeStats     -- * Monitoring and linking   , link   , linkNode@@ -133,7 +157,6 @@ import Prelude hiding (catch) #endif -import Data.Typeable (Typeable) import Control.Monad.IO.Class (liftIO) import Control.Applicative ((<$>)) import Control.Monad.Reader (ask)@@ -143,8 +166,6 @@   , closure   , Static   , RemoteTable-  , closureCompose-  , staticClosure   ) import Control.Distributed.Process.Internal.Types   ( NodeId(..)@@ -167,21 +188,9 @@   , WhereIsReply(..)   , RegisterReply(..)   , LocalProcess(processNode)-  , nullProcessId-  )-import Control.Distributed.Process.Serializable (Serializable, SerializableDict)-import Control.Distributed.Process.Internal.Closure.BuiltIn-  ( sdictSendPort-  , sndStatic-  , idCP-  , seqCP-  , bindCP-  , splitCP-  , cpLink-  , cpSend-  , cpNewChan-  , cpDelay+  , Message   )+import Control.Distributed.Process.Serializable (Serializable) import Control.Distributed.Process.Internal.Primitives   ( -- Basic messaging     send@@ -192,6 +201,9 @@   , receiveChan   , mergePortsBiased   , mergePortsRR+  , unsafeSend+  , unsafeSendChan+  , unsafeNSend     -- Advanced messaging   , Match   , receiveWait@@ -199,10 +211,24 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..)   , matchAny   , matchAnyIf   , matchChan+  , matchSTM+  , matchMessage+  , matchMessageIf+  , isEncoded+  , wrapMessage+  , unsafeWrapMessage+  , unwrapMessage+  , handleMessage+  , handleMessageIf+  , handleMessage_+  , handleMessageIf_+  , forward+  , delegate+  , relay+  , proxy     -- Process management   , terminate   , ProcessTerminationException(..)@@ -215,6 +241,9 @@   , getSelfNode   , ProcessInfo(..)   , getProcessInfo+  , NodeStats(..)+  , getNodeStats+  , getLocalNodeStats     -- Monitoring and linking   , link   , linkNode@@ -262,6 +291,15 @@   , reconnectPort   ) import Control.Distributed.Process.Node (forkProcess)+import Control.Distributed.Process.Internal.Spawn+  ( -- Spawning Processes/Channels+    spawn+  , spawnLink+  , spawnMonitor+  , spawnChannel+  , spawnSupervised+  , call+  )  -- INTERNAL NOTES --@@ -273,7 +311,11 @@ -- 2.  'send' may block (when the system TCP buffers are full, while we are --     trying to establish a connection to the remote endpoint, etc.) but its --     return does not imply that the remote process received the message (much---     less processed it)+--     less processed it). When 'send' targets a local process, it is dispatched+--     via the node controller however, in which cases it will /not/ block. This+--     isn't part of the semantics, so it should be ok. The ordering is maintained+--     because the ctrlChannel still has FIFO semantics with regards interactions+--     between two disparate forkIO threads. -- -- 3.  Message delivery is reliable and ordered. That means that if process A --     sends messages m1, m2, m3 to process B, B will either arrive all three@@ -321,116 +363,6 @@ --       http://www.erlang.org/faq/academic.html -- [6] "Delivery of Messages", post on erlang-questions --       http://erlang.org/pipermail/erlang-questions/2012-February/064767.html------------------------------------------------------------------------------------- Primitives are defined in a separate module; here we only define derived   ----- constructs                                                                 --------------------------------------------------------------------------------------- | Spawn a process------ For more information about 'Closure', see--- "Control.Distributed.Process.Closure".------ See also 'call'.-spawn :: NodeId -> Closure (Process ()) -> Process ProcessId-spawn nid proc = do-  us   <- getSelfPid-  mRef <- monitorNode nid-  sRef <- spawnAsync nid (cpDelay us proc)-  receiveWait [-      matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do-        unmonitor mRef-        send pid ()-        return pid-    , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->-        return (nullProcessId nid)-    ]---- | Spawn a process and link to it------ Note that this is just the sequential composition of 'spawn' and 'link'.--- (The "Unified" semantics that underlies Cloud Haskell does not even support--- a synchronous link operation)-spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId-spawnLink nid proc = do-  pid <- spawn nid proc-  link pid-  return pid---- | Like 'spawnLink', but monitor the spawned process-spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)-spawnMonitor nid proc = do-  pid <- spawn nid proc-  ref <- monitor pid-  return (pid, ref)---- | Run a process remotely and wait for it to reply------ We monitor the remote process: if it dies before it can send a reply, we die--- too.------ For more information about 'Static', 'SerializableDict', and 'Closure', see--- "Control.Distributed.Process.Closure".------ See also 'spawn'.-call :: Serializable a-        => Static (SerializableDict a)-        -> NodeId-        -> Closure (Process a)-        -> Process a-call dict nid proc = do-  us <- getSelfPid-  (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)-  -- We are guaranteed to receive the reply before the monitor notification-  -- (if a reply is sent at all)-  -- NOTE: This might not be true if we switch to unreliable delivery.-  mResult <- receiveWait-    [ match (return . Right)-    , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)-              (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))-    ]-  case mResult of-    Right a  -> do-      -- Wait for the monitor message so that we the mailbox doesn't grow-      receiveWait-        [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)-                  (\(ProcessMonitorNotification {}) -> return ())-        ]-      -- Clean up connection to pid-      reconnect pid-      return a-    Left err ->-      fail $ "call: remote process died: " ++ show err---- | Spawn a child process, have the child link to the parent and the parent--- monitor the child-spawnSupervised :: NodeId-                -> Closure (Process ())-                -> Process (ProcessId, MonitorRef)-spawnSupervised nid proc = do-  us   <- getSelfPid-  them <- spawn nid (cpLink us `seqCP` proc)-  ref  <- monitor them-  return (them, ref)---- | Spawn a new process, supplying it with a new 'ReceivePort' and return--- the corresponding 'SendPort'.-spawnChannel :: forall a. Typeable a => Static (SerializableDict a)-             -> NodeId-             -> Closure (ReceivePort a -> Process ())-             -> Process (SendPort a)-spawnChannel dict nid proc = do-    us <- getSelfPid-    _ <- spawn nid (go us)-    expect-  where-    go :: ProcessId -> Closure (Process ())-    go pid = cpNewChan dict-           `bindCP`-             (cpSend (sdictSendPort dict) pid `splitCP` proc)-           `bindCP`-             (idCP `closureCompose` staticClosure sndStatic)  -------------------------------------------------------------------------------- -- Local versions of spawn                                                    --
src/Control/Distributed/Process/Closure.hs view
@@ -173,9 +173,17 @@     -- * CP versions of Cloud Haskell primitives   , cpLink   , cpUnlink+  , cpRelay   , cpSend   , cpExpect   , cpNewChan+    -- * Working with static values and closures (without Template Haskell)+  , RemoteRegister+  , MkTDict(..)+  , mkStaticVal+  , mkClosureValSingle+  , mkClosureVal+  , call' #ifdef TemplateHaskellSupport     -- * Template Haskell support for creating static values and closures   , remotable@@ -205,9 +213,19 @@     -- CP versions of Cloud Haskell primitives   , cpLink   , cpUnlink+  , cpRelay   , cpSend   , cpExpect   , cpNewChan+  )+import Control.Distributed.Process.Internal.Closure.Explicit+  (+    RemoteRegister+  , MkTDict(..)+  , mkStaticVal+  , mkClosureValSingle+  , mkClosureVal+  , call'   ) #ifdef TemplateHaskellSupport import Control.Distributed.Process.Internal.Closure.TH
+ src/Control/Distributed/Process/Debug.hs view
@@ -0,0 +1,279 @@+{-# LANGUAGE CPP  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE RankNTypes  #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Distributed.Process.Debug+-- Copyright   :  (c) Well-Typed / Tim Watson+-- License     :  BSD3 (see the file LICENSE)+--+-- Maintainer  :  Tim Watson <watson.timothy@gmail.com>+-- Stability   :  experimental+-- Portability :  non-portable (requires concurrency)+--+-- [Tracing/Debugging Facilities]+--+-- Cloud Haskell provides a general purpose tracing mechanism, allowing a+-- user supplied /tracer process/ to receive messages when certain classes of+-- system events occur. It's possible to use this facility to aid in debugging+-- and/or perform other diagnostic tasks to a program at runtime.+--+-- [Enabling Tracing]+--+-- Throughout the lifecycle of a local node, the distributed-process runtime+-- generates /trace events/, describing internal runtime activities such as+-- the spawning and death of processes, message sending, delivery and so on.+-- See the 'MxEvent' type's documentation for a list of all the published+-- event types, which correspond directly to the types of /management/ events.+-- Users can additionally publish custom trace events in the form of+-- 'MxLog' log messages or pass custom (i.e., completely user defined)+-- event data using the 'traceMessage' function.+--+-- All published traces are forwarded to a /tracer process/, which can be+-- specified (and changed) at runtime using 'traceEnable'. Some pre-defined+-- tracer processes are provided for conveniently printing to stderr, a log file+-- or the GHC eventlog.+--+-- If a tracer process crashes, no attempt is made to restart it.+--+-- [Working with multiple tracer processes]+--+-- The tracing facility only ever writes to a single tracer process. This+-- invariant insulates the tracer controller and ensures a fast path for+-- handling all trace events. /This/ module provides facilities for layering+-- trace handlers using Cloud Haskell's built-in delegation primitives.+--+-- The 'startTracer' function wraps the registered @tracer@ process with the+-- supplied handler and also forwards trace events to the original tracer.+-- The corresponding 'stopTracer' function terminates tracer processes in+-- reverse of the order in which they were started, and re-registers the+-- previous tracer process.+--+-- [Built in tracers]+--+-- The built in tracers provide a simple /logging/ facility that writes trace+-- events out to either a log file, @stderr@ or the GHC eventlog. These tracers+-- can be configured using environment variables, or specified manually using+-- the 'traceEnable' function.+--+-- When a new local node is started, the contents of several environment+-- variables are checked to determine which default tracer process is selected.+-- If none of these variables is set, a no-op tracer process is installed,+-- which effectively ignores all trace messages. Note that in this case,+-- trace events are still generated and passed through the system.+-- Only one default tracer will be chosen - the first that contains a (valid)+-- value. These environment variables, in the order they're examined, are:+--+-- 1. @DISTRIBUTED_PROCESS_TRACE_FILE@+-- This is checked for a valid file path. If it exists and the file can be+-- opened for writing, all trace output will be directed thence. If the supplied+-- path is invalid, or the file is unavailable for writing, this tracer will not+-- be selected.+--+-- 2. @DISTRIBUTED_PROCESS_TRACE_CONSOLE@+-- This is checked for /any/ non-empty value. If set, then all trace output will+-- be directed to the system logger process.+--+-- 3. @DISTRIBUTED_PROCESS_TRACE_EVENTLOG@+-- This is checked for /any/ non-empty value. If set, all internal traces are+-- written to the GHC eventlog.+--+-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that+-- because the trace file option only supports trace output from a single node+-- (so as to avoid interleaving), a file trace configured for the master node+-- will prevent slaves from tracing to the file. They will need to fall back to+-- the console or eventlog tracers instead, which can be accomplished by setting+-- one of these environment variables /as well/, since the latter will only be+-- selected on slaves (when the file tracer selection fails).+--+-- Support for writing to the eventlog requires specific intervention to work,+-- without which, written traces are silently dropped/ignored and no output will+-- be generated. The GHC eventlog documentation provides information about+-- enabling, viewing and working with event traces at+-- <http://hackage.haskell.org/trac/ghc/wiki/EventLog>.+--+module Control.Distributed.Process.Debug+  ( -- * Exported Data Types+    TraceArg(..)+  , TraceFlags(..)+  , TraceSubject(..)+    -- * Configuring Tracing+  , enableTrace+  , enableTraceAsync+  , disableTrace+  , withTracer+  , withFlags+  , getTraceFlags+  , setTraceFlags+  , setTraceFlagsAsync+  , defaultTraceFlags+  , traceOn+  , traceOnly+  , traceOff+    -- * Debugging+  , startTracer+  , stopTracer+    -- * Sending Custom Trace Data+  , traceLog+  , traceLogFmt+  , traceMessage+    -- * Working with remote nodes+  , Remote.remoteTable+  , Remote.startTraceRelay+  , Remote.setTraceFlagsRemote+    -- * Built in tracers+  , systemLoggerTracer+  , logfileTracer+  , eventLogTracer+  )+  where++import Control.Applicative ((<$>))+import Control.Distributed.Process.Internal.Primitives+  ( proxy+  , finally+  , die+  , whereis+  , send+  , receiveWait+  , matchIf+  , finally+  , try+  , monitor+  )+import Control.Distributed.Process.Internal.Types+  ( ProcessId+  , Process+  , LocalProcess(..)+  , ProcessMonitorNotification(..)+  )+import Control.Distributed.Process.Management.Internal.Types+  ( MxEvent(..)+  )+import Control.Distributed.Process.Management.Internal.Trace.Types+  ( TraceArg(..)+  , TraceFlags(..)+  , TraceSubject(..)+  , defaultTraceFlags+  )+import Control.Distributed.Process.Management.Internal.Trace.Tracer+  ( systemLoggerTracer+  , logfileTracer+  , eventLogTracer+  )+import Control.Distributed.Process.Management.Internal.Trace.Primitives+  ( withRegisteredTracer+  , enableTrace+  , enableTraceAsync+  , disableTrace+  , setTraceFlags+  , setTraceFlagsAsync+  , getTraceFlags+  , traceOn+  , traceOff+  , traceOnly+  , traceLog+  , traceLogFmt+  , traceMessage+  )+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Remote+import Control.Distributed.Process.Node++import Control.Exception (SomeException)++import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask)++import Data.Binary()++#if ! MIN_VERSION_base(4,6,0)+import Prelude hiding (catch)+#endif++--------------------------------------------------------------------------------+-- Debugging/Tracing API                                                      --+--------------------------------------------------------------------------------++-- | Starts a new tracer, using the supplied trace function.+-- Only one tracer can be registered at a time, however /this/ function overlays+-- the registered tracer with the supplied handler, allowing the user to layer+-- multiple tracers on top of one another, with trace events forwarded down+-- through all the layers in turn. Once the top layer is stopped, the user+-- is responsible for re-registering the original (prior) tracer pid before+-- terminating. See 'withTracer' for a mechanism that handles that.+startTracer :: (MxEvent -> Process ()) -> Process ProcessId+startTracer handler = do+  withRegisteredTracer $ \pid -> do+    node <- processNode <$> ask+    newPid <- liftIO $ forkProcess node $ traceProxy pid handler+    enableTrace newPid  -- invokes sync + registration+    return newPid++-- | Evaluate @proc@ with tracing enabled via @handler@, and immediately+-- disable tracing thereafter, before giving the result (or exception+-- in case of failure).+withTracer :: forall a.+              (MxEvent -> Process ())+           -> Process a+           -> Process (Either SomeException a)+withTracer handler proc = do+    previous <- whereis "tracer"+    tracer <- startTracer handler+    finally (try proc)+            (stopTracing tracer previous)+  where+    stopTracing :: ProcessId -> Maybe ProcessId -> Process ()+    stopTracing tracer previousTracer = do+      case previousTracer of+        Nothing -> return ()+        Just _  -> do+          ref <- monitor tracer+          send tracer MxTraceDisable+          receiveWait [+              matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')+                      (\_ -> return ())+            ]++-- | Evaluate @proc@ with the supplied flags enabled. Any previously set+-- trace flags are restored immediately afterwards.+withFlags :: forall a.+             TraceFlags+          -> Process a+          -> Process (Either SomeException a)+withFlags flags proc = do+  oldFlags <- getTraceFlags+  finally (setTraceFlags flags >> try proc)+          (setTraceFlags oldFlags)++traceProxy :: ProcessId -> (MxEvent -> Process ()) -> Process ()+traceProxy pid act = do+  proxy pid $ \(ev :: MxEvent) ->+    case ev of+      (MxTraceTakeover _) -> return False+      MxTraceDisable      -> die "disabled"+      _                   -> act ev >> return True++-- | Stops a user supplied tracer started with 'startTracer'.+-- Note that only one tracer process can be active at any given time.+-- This process will stop the last process started with 'startTracer'.+-- If 'startTracer' is called multiple times, successive calls to this+-- function will stop the tracers in the reverse order which they were+-- started.+--+-- This function will never stop the system tracer (i.e., the tracer+-- initially started when the node is created), therefore once all user+-- supplied tracers (i.e., processes started via 'startTracer') have exited,+-- subsequent calls to this function will have no effect.+--+-- If the last tracer to have been registered was not started+-- with 'startTracer' then the behaviour of this function is /undefined/.+stopTracer :: Process ()+stopTracer =+  withRegisteredTracer $ \pid -> do+    -- we need to avoid killing the initial (base) tracer, as+    -- nothing we rely on having exactly 1 registered tracer+    -- process at all times.+    basePid <- whereis "tracer.initial"+    case basePid == (Just pid) of+      True  -> return ()+      False -> send pid MxTraceDisable
src/Control/Distributed/Process/Internal/CQueue.hs view
@@ -1,11 +1,13 @@+{-# LANGUAGE BangPatterns  #-}+{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-} -- | Concurrent queue for single reader, single writer-{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards #-} module Control.Distributed.Process.Internal.CQueue   ( CQueue   , BlockSpec(..)   , MatchOn(..)   , newCQueue   , enqueue+  , enqueueSTM   , dequeue   , mkWeakCQueue   ) where@@ -52,7 +54,11 @@ -- -- Enqueue is strict. enqueue :: CQueue a -> a -> IO ()-enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a +enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a++-- | Variant of enqueue for use in the STM monad.+enqueueSTM :: CQueue a -> a -> STM ()+enqueueSTM (CQueue _arrived incoming) !a = writeTChan incoming a  data BlockSpec =     NonBlocking
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE RankNTypes  #-} module Control.Distributed.Process.Internal.Closure.BuiltIn   ( -- * Remote table     remoteTable@@ -16,13 +18,16 @@   , bindCP   , seqCP     -- * CP versions of Cloud Haskell primitives+  , decodeProcessIdStatic   , cpLink   , cpUnlink+  , cpRelay   , cpSend   , cpExpect   , cpNewChan     -- * Support for some CH operations   , cpDelay+  , cpEnableTraceRemote   ) where  import Data.ByteString.Lazy (ByteString)@@ -56,6 +61,7 @@ import Control.Distributed.Process.Internal.Primitives   ( link   , unlink+  , relay   , send   , expect   , newChan@@ -82,6 +88,7 @@     . registerStatic "$decodeProcessId" (toDynamic (decode           :: ByteString -> ProcessId))     . registerStatic "$link"            (toDynamic link)     . registerStatic "$unlink"          (toDynamic unlink)+    . registerStatic "$relay"           (toDynamic relay)     . registerStatic "$sendDict"        (toDynamic (sendDict         :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))     . registerStatic "$expectDict"      (toDynamic (expectDict       :: SerializableDict ANY -> Process ANY))     . registerStatic "$newChanDict"     (toDynamic (newChanDict      :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))@@ -245,6 +252,22 @@     newChanDictStatic :: Typeable a                       => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))     newChanDictStatic = staticLabel "$newChanDict"++-- | 'CP' version of 'relay'+cpRelay :: ProcessId -> Closure (Process ())+cpRelay = closure (relayStatic `staticCompose` decodeProcessIdStatic) . encode+  where+    relayStatic :: Static (ProcessId -> Process ())+    relayStatic = staticLabel "$relay"++-- TODO: move cpEnableTraceRemote into Trace/Primitives.hs++cpEnableTraceRemote :: ProcessId -> Closure (Process ())+cpEnableTraceRemote =+    closure (enableTraceStatic `staticCompose` decodeProcessIdStatic) . encode+  where+    enableTraceStatic :: Static (ProcessId -> Process ())+    enableTraceStatic = staticLabel "$enableTraceRemote"  -------------------------------------------------------------------------------- -- Support for spawn                                                          --
+ src/Control/Distributed/Process/Internal/Closure/Explicit.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE ScopedTypeVariables+  , MultiParamTypeClasses+  , FlexibleInstances+  , FunctionalDependencies+  , FlexibleContexts+  , UndecidableInstances+  , KindSignatures+  , GADTs+  , OverlappingInstances+  , EmptyDataDecls+  , DeriveDataTypeable #-}+module Control.Distributed.Process.Internal.Closure.Explicit+  (+    RemoteRegister+  , MkTDict(..)+  , mkStaticVal+  , mkClosureValSingle+  , mkClosureVal+  , call'+  ) where++import Control.Distributed.Static+import Control.Distributed.Process.Serializable+import Control.Distributed.Process.Internal.Closure.BuiltIn+  ( -- Static dictionaries and associated operations+    staticDecode+  )+import Control.Distributed.Process+import Data.Rank1Dynamic+import Data.Rank1Typeable+import Data.Binary(encode,put,get,Binary)+import qualified Data.ByteString.Lazy as B++-- | A RemoteRegister is a trasformer on a RemoteTable to register additional static values.+type RemoteRegister = RemoteTable -> RemoteTable++-- | This takes an explicit name and a value, and produces both a static reference to the name and a RemoteRegister for it.+mkStaticVal :: Serializable a => String -> a -> (Static a, RemoteRegister)+mkStaticVal n v = (staticLabel n_s, registerStatic n_s (toDynamic v))+    where n_s = n++class MkTDict a where+    mkTDict :: String -> a -> RemoteRegister++instance (Serializable b) => MkTDict (Process b) where+    mkTDict _ _ = registerStatic (show (typeOf (undefined :: b)) ++ "__staticDict") (toDynamic (SerializableDict :: SerializableDict b))++instance MkTDict a where+    mkTDict _ _ = id++-- | This takes an explicit name, a function of arity one, and creates a creates a function yielding a closure and a remote register for it.+mkClosureValSingle :: forall a b. (Serializable a, Typeable b, MkTDict b) => String -> (a -> b) -> (a -> Closure b, RemoteRegister)+mkClosureValSingle n v = (c, registerStatic n_s (toDynamic v) .+                             registerStatic n_sdict (toDynamic sdict) .+                             mkTDict n_tdict (undefined :: b)+                         ) where+    n_s = n+    n_sdict = n ++ "__sdict"+    n_tdict = n ++ "__tdict"++    c = closure decoder . encode++    decoder = (staticLabel n_s :: Static (a -> b)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict a))++    sdict :: (SerializableDict a)+    sdict = SerializableDict++-- | This takes an explict name, a function of any arity, and creates a function yielding a closure and a remote register for it.+mkClosureVal :: forall func argTuple result closureFunction.+                (Curry (argTuple -> Closure result) closureFunction,+                 MkTDict result,+                 Uncurry HTrue argTuple func result,+                 Typeable result, Serializable argTuple, IsFunction func HTrue) =>+                String -> func -> (closureFunction, RemoteRegister)+mkClosureVal n v = (curryFun c, rtable)+    where+      uv :: argTuple -> result+      uv = uncurry' reify v++      n_s = n+      n_sdict = n ++ "__sdict"+      n_tdict = n ++ "__tdict"++      c :: argTuple -> Closure result+      c = closure decoder . encode++      decoder :: Static (B.ByteString -> result)+      decoder = (staticLabel n_s :: Static (argTuple -> result)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict argTuple))++      rtable = registerStatic n_s (toDynamic uv) .+               registerStatic n_sdict (toDynamic sdict) .+               mkTDict n_tdict (undefined :: result)+++      sdict :: (SerializableDict argTuple)+      sdict = SerializableDict++-- | Works just like standard call, but with a simpler signature.+call' :: forall a. Serializable a => NodeId -> Closure (Process a) -> Process a+call' = call (staticLabel $ (show $ typeOf $ (undefined :: a)) ++ "__staticDict")+++data EndOfTuple deriving Typeable+instance Binary EndOfTuple where+    put _ = return ()+    get = return undefined++-- This generic curry is straightforward+class Curry a b | a -> b where+    curryFun :: a -> b++instance Curry ((a,EndOfTuple) -> b) (a -> b) where+    curryFun f = \x -> f (x,undefined)++instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where+    curryFun f = \x -> curryFun (\y -> (f (x,y)))+++-- This generic uncurry courtesy Andrea Vezzosi+data HTrue+data HFalse+data Fun :: * -> * -> * -> * where+  Done :: Fun EndOfTuple r r+  Moar :: Fun xs f r -> Fun (x,xs) (x -> f) r++class Uncurry'' args func result | func -> args, func -> result, args result -> func where+    reify :: Fun args func result++class Uncurry flag args func result | flag func -> args, flag func -> result, args result -> func where+    reify' :: flag -> Fun args func result++instance Uncurry'' rest f r => Uncurry HTrue (a,rest) (a -> f) r where+    reify' _ = Moar reify++instance Uncurry HFalse EndOfTuple a a where+    reify' _ = Done++instance (IsFunction func b, Uncurry b args func result) => Uncurry'' args func result where+    reify = reify' (undefined :: b)++uncurry' :: Fun args func result -> func -> args -> result+uncurry' Done r _ = r+uncurry' (Moar fun) f (x,xs) = uncurry' fun (f x) xs++class IsFunction t b | t -> b+instance (b ~ HTrue) => IsFunction (a -> c) b+instance (b ~ HFalse) => IsFunction a b+
src/Control/Distributed/Process/Internal/Messaging.hs view
@@ -5,6 +5,7 @@   , disconnect   , closeImplicitReconnections   , impliesDeathOf+  , sendCtrlMsg   ) where  import Data.Accessor ((^.), (^=))@@ -13,8 +14,12 @@ import qualified Data.ByteString.Lazy as BSL (toChunks) import qualified Data.ByteString as BSS (ByteString) import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)+import Control.Distributed.Process.Serializable ()+ import Control.Concurrent.Chan (writeChan) import Control.Monad (unless)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Reader (ask) import qualified Network.Transport as NT   ( Connection   , send@@ -36,8 +41,11 @@   , ProcessSignal(Died)   , DiedReason(DiedDisconnect)   , ImplicitReconnect(WithImplicitReconnect)-  , NodeId-  , ProcessId(processNodeId)+  , NodeId(..)+  , ProcessId(..)+  , LocalNode(..)+  , LocalProcess(..)+  , Process(..)   , SendPortId(sendPortProcessId)   , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)   )@@ -167,3 +175,24 @@   cid' == cid _ `impliesDeathOf` _ =   False+++-- Send a control message+sendCtrlMsg :: Maybe NodeId  -- ^ Nothing for the local node+            -> ProcessSignal -- ^ Message to send+            -> Process ()+sendCtrlMsg mNid signal = do+  proc <- ask+  let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)+                  , ctrlMsgSignal = signal+                  }+  case mNid of+    Nothing -> do+      liftIO $ writeChan (localCtrlChan (processNode proc)) msg+    Just nid ->+      liftIO $ sendBinary (processNode proc)+                          (ProcessIdentifier (processId proc))+                          (NodeIdentifier nid)+                          WithImplicitReconnect+                          msg+
src/Control/Distributed/Process/Internal/Primitives.hs view
@@ -1,3 +1,10 @@+{-# LANGUAGE CPP  #-}+{-# LANGUAGE RankNTypes  #-}+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE BangPatterns #-}+ -- | Cloud Haskell primitives -- -- We define these in a separate module so that we don't have to rely on@@ -12,6 +19,10 @@   , receiveChan   , mergePortsBiased   , mergePortsRR+    -- * Unsafe messaging variants+  , unsafeSend+  , unsafeSendChan+  , unsafeNSend     -- * Advanced messaging   , Match   , receiveWait@@ -19,10 +30,24 @@   , match   , matchIf   , matchUnknown-  , AbstractMessage(..)   , matchAny   , matchAnyIf   , matchChan+  , matchSTM+  , matchMessage+  , matchMessageIf+  , isEncoded+  , wrapMessage+  , unsafeWrapMessage+  , unwrapMessage+  , handleMessage+  , handleMessageIf+  , handleMessage_+  , handleMessageIf_+  , forward+  , delegate+  , relay+  , proxy     -- * Process management   , terminate   , ProcessTerminationException(..)@@ -38,6 +63,9 @@   , getSelfNode   , ProcessInfo(..)   , getProcessInfo+  , NodeStats(..)+  , getNodeStats+  , getLocalNodeStats     -- * Monitoring and linking   , link   , unlink@@ -83,8 +111,8 @@     -- * Reconnecting   , reconnect   , reconnectPort-    -- * Tracing/Debugging-  , trace+    -- * Internal Exports+  , sendCtrlMsg   ) where  #if ! MIN_VERSION_base(4,6,0)@@ -107,7 +135,6 @@   , modifyMVar   , modifyMVar_   )-import Control.Concurrent.Chan (writeChan) import Control.Concurrent.STM   ( STM   , TVar@@ -124,9 +151,13 @@   ) import Control.Distributed.Process.Serializable (Serializable, fingerprint) import Data.Accessor ((^.), (^:), (^=))-import Control.Distributed.Static (Closure, Static)+import Control.Distributed.Static+  ( Static+  , Closure+  ) import Data.Rank1Typeable (Typeable) import qualified Control.Distributed.Static as Static (unstatic, unclosure)+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe import Control.Distributed.Process.Internal.Types   ( NodeId(..)   , ProcessId(..)@@ -136,8 +167,8 @@   , Message(..)   , MonitorRef(..)   , SpawnRef(..)-  , NCMsg(..)   , ProcessSignal(..)+  , NodeMonitorNotification(..)   , monitorCounter   , spawnCounter   , SendPort(..)@@ -148,6 +179,7 @@   , SendPortId(..)   , Identifier(..)   , ProcessExitException(..)+  , DiedReason(..)   , DidUnmonitor(..)   , DidUnlinkProcess(..)   , DidUnlinkNode(..)@@ -157,9 +189,12 @@   , ProcessRegistrationException(..)   , ProcessInfo(..)   , ProcessInfoNone(..)+  , NodeStats(..)+  , isEncoded   , createMessage+  , createUnencodedMessage   , runLocalProcess-  , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)+  , ImplicitReconnect( NoImplicitReconnect)   , LocalProcessState   , LocalSendPortId   , messageToPayload@@ -169,14 +204,22 @@   , sendBinary   , sendPayload   , disconnect+  , sendCtrlMsg   )-import qualified Control.Distributed.Process.Internal.Trace as Trace+import Control.Distributed.Process.Management.Internal.Types+  ( MxEvent(..)+  )+import Control.Distributed.Process.Management.Internal.Trace.Types+  ( traceEvent+  ) import Control.Distributed.Process.Internal.WeakTQueue   ( newTQueueIO   , readTQueue   , mkWeakTQueue   ) +import Unsafe.Coerce+ -------------------------------------------------------------------------------- -- Basic messaging                                                            -- --------------------------------------------------------------------------------@@ -184,15 +227,32 @@ -- | Send a message send :: Serializable a => ProcessId -> a -> Process () -- This requires a lookup on every send. If we want to avoid that we need to--- modify serializable to allow for stateful (IO) deserialization+-- modify serializable to allow for stateful (IO) deserialization. send them msg = do   proc <- ask-  liftIO $ sendMessage (processNode proc)-                       (ProcessIdentifier (processId proc))-                       (ProcessIdentifier them)-                       NoImplicitReconnect-                       msg+  let us       = processId proc+      node     = processNode proc+      nodeId   = localNodeId node+      destNode = (processNodeId them) in do+  case destNode == nodeId of+    True  -> sendLocal them msg+    False -> liftIO $ sendMessage (processNode proc)+                                  (ProcessIdentifier (processId proc))+                                  (ProcessIdentifier them)+                                  NoImplicitReconnect+                                  msg+  -- We do not fire the trace event until after the sending is complete;+  -- In the remote case, 'sendMessage' can block in the networking stack.+  liftIO $ traceEvent (localEventBus node)+                      (MxSent them us (createUnencodedMessage msg)) +-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize+-- and (in the case when the destination process resides on the same local+-- node) therefore ensure that the payload is fully evaluated before it is+-- delivered.+unsafeSend :: Serializable a => ProcessId -> a -> Process ()+unsafeSend = Unsafe.send+ -- | Wait for a message of a specific type expect :: forall a. Serializable a => Process a expect = receiveWait [match return]@@ -229,12 +289,24 @@ sendChan :: Serializable a => SendPort a -> a -> Process () sendChan (SendPort cid) msg = do   proc <- ask-  liftIO $ sendBinary (processNode proc)-                      (ProcessIdentifier (processId proc))-                      (SendPortIdentifier cid)-                      NoImplicitReconnect-                      msg+  let node     = localNodeId (processNode proc)+      destNode = processNodeId (sendPortProcessId cid) in do+  case destNode == node of+    True  -> sendChanLocal cid msg+    False -> do+      liftIO $ sendBinary (processNode proc)+                          (ProcessIdentifier (processId proc))+                          (SendPortIdentifier cid)+                          NoImplicitReconnect+                          msg +-- | Send a message on a typed channel. This function makes /no/ attempt to+-- serialize and (in the case when the @ReceivePort@ resides on the same local+-- node) therefore ensure that the payload is fully evaluated before it is+-- delivered.+unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()+unsafeSendChan = Unsafe.sendChan+ -- | Wait for a message on a typed channel receiveChan :: Serializable a => ReceivePort a -> Process a receiveChan = liftIO . atomically . receiveSTM@@ -300,9 +372,26 @@     Nothing   -> return Nothing     Just proc -> Just <$> proc +-- | Match on a typed channel matchChan :: ReceivePort a -> (a -> Process b) -> Match b matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p)) +-- | Match on an arbitrary STM action.+--+-- This rather unusaul /match primitive/ allows us to compose arbitrary STM+-- actions with checks against our process' mailbox and/or any typed channel+-- @ReceivePort@s we may hold.+--+-- This allows us to process multiple input streams /along with our mailbox/,+-- in just the same way that 'matchChan' supports checking both the mailbox+-- /and/ an arbitrary set of typed channels in one atomic transaction.+--+-- Note there are no ordering guarnatees with respect to these disparate input+-- sources.+--+matchSTM :: STM a -> (a -> Process b) -> Match b+matchSTM stm fn = Match $ MatchChan (fmap fn stm)+ -- | Match against any message of the right type match :: forall a b. Serializable a => (a -> Process b) -> Match b match = matchIf (const True)@@ -310,83 +399,238 @@ -- | Match against any message of the right type that satisfies a predicate matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b matchIf c p = Match $ MatchMsg $ \msg ->-   case messageFingerprint msg == fingerprint (undefined :: a) of-     True | c decoded -> Just (p decoded)-       where-         decoded :: a-         -- Make sure the value is fully decoded so that we don't hang to-         -- bytestrings when the process calling 'matchIf' doesn't process-         -- the values immediately-         !decoded = decode (messageEncoding msg)-     _ -> Nothing+  case messageFingerprint msg == fingerprint (undefined :: a) of+    False -> Nothing+    True  -> case msg of+      (UnencodedMessage _ m) ->+        let m' = unsafeCoerce m :: a in+        case (c m') of+          True  -> Just (p m')+          False -> Nothing+      (EncodedMessage _ _) ->+        if (c decoded) then Just (p decoded) else Nothing+        where+          decoded :: a+            -- Make sure the value is fully decoded so that we don't hang to+            -- bytestrings when the process calling 'matchIf' doesn't process+            -- the values immediately+          !decoded = decode (messageEncoding msg) --- | Represents a received message and provides two basic operations on it.-data AbstractMessage = AbstractMessage {-    forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@-  , maybeHandleMessage :: forall a b. (Serializable a)-            => (a -> Process b) -> Process (Maybe b) {- ^ Handle the message.-        If the type of the message matches the type of the first argument to-        the supplied expression, then the expression will be evaluated against-        it. If this runtime type checking fails, then @Nothing@ will be returned-        to indicate the fact. If the check succeeds and evaluation proceeds-        however, the resulting value with be wrapped with @Just@.-    -}-  }+-- | Match against any message, regardless of the underlying (contained) type+matchMessage :: (Message -> Process Message) -> Match Message+matchMessage p = Match $ MatchMsg $ \msg -> Just (p msg) +-- | Match against any message (regardless of underlying type) that satisfies a predicate+matchMessageIf :: (Message -> Bool) -> (Message -> Process Message) -> Match Message+matchMessageIf c p = Match $ MatchMsg $ \msg ->+    case (c msg) of+      True  -> Just (p msg)+      False -> Nothing++-- | Forward a raw 'Message' to the given 'ProcessId'.+forward :: Message -> ProcessId -> Process ()+forward msg them = do+  proc <- ask+  let node     = processNode proc+      us       = processId proc+      nid      = localNodeId node+      destNode = (processNodeId them) in do+  case destNode == nid of+    True  -> sendCtrlMsg Nothing (LocalSend them msg)+    False -> liftIO $ sendPayload (processNode proc)+                                  (ProcessIdentifier (processId proc))+                                  (ProcessIdentifier them)+                                  NoImplicitReconnect+                                  (messageToPayload msg)+  -- We do not fire the trace event until after the sending is complete;+  -- In the remote case, 'sendMessage' can block in the networking stack.+  liftIO $ traceEvent (localEventBus node)+                      (MxSent them us msg)+++-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are+-- 'Serializable' - like the datum they contain - but also note, deserialising+-- such a 'Message' will yield a 'Message', not the type within it! To obtain the+-- wrapped datum, use 'unwrapMessage' or 'handleMessage' with a specific type.+--+-- @+-- do+--    self <- getSelfPid+--    send self (wrapMessage "blah")+--    Nothing  <- expectTimeout 1000000 :: Process (Maybe String)+--    (Just m) <- expectTimeout 1000000 :: Process (Maybe Message)+--    (Just "blah") <- unwrapMessage m :: Process (Maybe String)+-- @+--+wrapMessage :: Serializable a => a -> Message+wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]++-- | This is the /unsafe/ variant of 'wrapMessage'. See+-- "Control.Distributed.Process.UnsafePrimitives" for details.+unsafeWrapMessage :: Serializable a => a -> Message+unsafeWrapMessage = Unsafe.wrapMessage++-- [note Serializable UnencodedMessage]+-- if we attempt to serialise an UnencodedMessage, it will be converted to an+-- EncodedMessage first, so we're ok here. See 'messageToPayload' in+-- Primitives.hs for the gory details++-- | Attempt to unwrap a raw 'Message'.+-- If the type of the decoded message payload matches the expected type, the+-- value will be returned with @Just@, otherwise @Nothing@ indicates the types+-- do not match.+--+-- This expression, for example, will evaluate to @Nothing@+-- > unwrapMessage (wrapMessage "foobar") :: Process (Maybe Int)+--+-- Whereas this expression, will yield @Just "foo"@+-- > unwrapMessage (wrapMessage "foo") :: Process (Maybe String)+--+unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a)+unwrapMessage msg =+  case messageFingerprint msg == fingerprint (undefined :: a) of+    False -> return Nothing :: m (Maybe a)+    True  -> case msg of+      (UnencodedMessage _ ms) ->+        let ms' = unsafeCoerce ms :: a+        in return (Just ms')+      (EncodedMessage _ _) ->+        return (Just (decoded))+        where+          decoded :: a -- note [decoding]+          !decoded = decode (messageEncoding msg)++-- | Attempt to handle a raw 'Message'.+-- If the type of the message matches the type of the first argument to+-- the supplied expression, then the message will be decoded and the expression+-- evaluated against its value. If this runtime type checking fails however,+-- @Nothing@ will be returned to indicate the fact. If the check succeeds and+-- evaluation proceeds, the resulting value with be wrapped with @Just@.+--+-- Intended for use in `catchesExit` and `matchAny` primitives.+--+handleMessage :: forall m a b. (Monad m, Serializable a)+              => Message -> (a -> m b) -> m (Maybe b)+handleMessage msg proc = handleMessageIf msg (const True) proc++-- | Conditionally handle a raw 'Message'.+-- If the predicate @(a -> Bool)@ evaluates to @True@, invokes the supplied+-- handler, other returns @Nothing@ to indicate failure. See 'handleMessage'+-- for further information about runtime type checking.+--+handleMessageIf :: forall m a b . (Monad m, Serializable a)+                => Message+                -> (a -> Bool)+                -> (a -> m b)+                -> m (Maybe b)+handleMessageIf msg c proc = do+  case messageFingerprint msg == fingerprint (undefined :: a) of+    False -> return Nothing :: m (Maybe b)+    True  -> case msg of+      (UnencodedMessage _ ms) ->+        let ms' = unsafeCoerce ms :: a in+        case (c ms') of+          True  -> do { r <- proc ms'; return (Just r) }+          False -> return Nothing :: m (Maybe b)+      (EncodedMessage _ _) ->+        case (c decoded) of+          True  -> do { r <- proc (decoded :: a); return (Just r) }+          False -> return Nothing :: m (Maybe b)+        where+          decoded :: a -- note [decoding]+          !decoded = decode (messageEncoding msg)++-- | As 'handleMessage' but ignores result, which is useful if you don't+-- care whether or not the handler succeeded.+--+handleMessage_ :: forall m a . (Monad m, Serializable a)+               => Message -> (a -> m ()) -> m ()+handleMessage_ msg proc = handleMessageIf_ msg (const True) proc++-- | Conditional version of 'handleMessage_'.+--+handleMessageIf_ :: forall m a . (Monad m, Serializable a)+                => Message+                -> (a -> Bool)+                -> (a -> m ())+                -> m ()+handleMessageIf_ msg c proc = handleMessageIf msg c proc >> return ()+ -- | Match against an arbitrary message. 'matchAny' removes the first available--- message from the process mailbox, and via the 'AbstractMessage' type,--- supports forwarding /or/ handling the message /if/ it is of the correct--- type. If /not/ of the right type, then the 'AbstractMessage'--- @maybeHandleMessage@ function will not evaluate the supplied expression,--- /but/ the message will still have been removed from the process mailbox!+-- message from the process mailbox. To handle arbitrary /raw/ messages once+-- removed from the mailbox, see 'handleMessage' and 'unwrapMessage'. ---matchAny :: forall b. (AbstractMessage -> Process b) -> Match b-matchAny p = Match $ MatchMsg $ Just . p . abstract+matchAny :: forall b. (Message -> Process b) -> Match b+matchAny p = Match $ MatchMsg $ \msg -> Just (p msg) --- | Match against an arbitrary message. 'matchAnyIf' will /only/ remove the--- message from the process mailbox, /if/ the supplied condition matches. The--- success (or failure) of runtime type checks in @maybeHandleMessage@ does not--- count here, i.e., if the condition evaluates to @True@ then the message will+-- | Match against an arbitrary message. Intended for use with 'handleMessage'+-- and 'unwrapMessage', this function /only/ removes a message from the process+-- mailbox, /if/ the supplied condition matches. The success (or failure) of+-- runtime type checks deferred to @handleMessage@ and friends is irrelevant+-- here, i.e., if the condition evaluates to @True@ then the message will -- be removed from the process mailbox and decoded, but that does /not/--- guarantee that an expression passed to @maybeHandleMessage@ will pass the--- runtime type checks and therefore be evaluated. If the types do not match--- up, then @maybeHandleMessage@ returns 'Nothing'.+-- guarantee that an expression passed to @handleMessage@ will pass the+-- runtime type checks and therefore be evaluated.+-- matchAnyIf :: forall a b. (Serializable a)                        => (a -> Bool)-                       -> (AbstractMessage -> Process b)+                       -> (Message -> Process b)                        -> Match b matchAnyIf c p = Match $ MatchMsg $ \msg ->-   case messageFingerprint msg == fingerprint (undefined :: a) of-     True | c decoded -> Just (p (abstract msg))+  case messageFingerprint msg == fingerprint (undefined :: a) of+     True | check -> Just (p msg)        where-         decoded :: a-         -- Make sure the value is fully decoded so that we don't hang to-         -- bytestrings when the calling process doesn't evaluate immediately+         check :: Bool+         !check =+           case msg of+             (EncodedMessage _ _)    -> c decoded+             (UnencodedMessage _ m') -> c (unsafeCoerce m')++         decoded :: a -- note [decoding]          !decoded = decode (messageEncoding msg)      _ -> Nothing -abstract :: Message -> AbstractMessage-abstract msg = AbstractMessage {-    forward = \them -> do-      proc <- ask-      liftIO $ sendPayload (processNode proc)-                           (ProcessIdentifier (processId proc))-                           (ProcessIdentifier them)-                           NoImplicitReconnect-                           (messageToPayload msg)-  , maybeHandleMessage = \(proc :: (a -> Process b)) -> do-      case messageFingerprint msg == fingerprint (undefined :: a) of-        True -> do { r <- proc (decoded :: a); return (Just r) }-          where-            decoded :: a-            !decoded = decode (messageEncoding msg)-        _ -> return Nothing-  }+{- note [decoding]+For an EncodedMessage, we need to ensure the value is fully decoded so that+we don't hang to bytestrings if the calling process doesn't evaluate+immediately. For UnencodedMessage we know (because the fingerprint comparison+succeeds) that unsafeCoerce will not fail.+-}  -- | Remove any message from the queue matchUnknown :: Process b -> Match b matchUnknown p = Match $ MatchMsg (const (Just p)) +-- | Receives messages and forwards them to @pid@ if @p msg == True@.+delegate :: ProcessId -> (Message -> Bool) -> Process ()+delegate pid p = do+  receiveWait [+      matchAny (\m -> case (p m) of+                        True  -> forward m pid+                        False -> return ())+    ]+  delegate pid p++-- | A straight relay that forwards all messages to the supplied pid.+relay :: ProcessId -> Process ()+relay !pid = receiveWait [ matchAny (\m -> forward m pid) ] >> relay pid++-- | Proxies @pid@ and forwards messages whenever @proc@ evaluates to @True@.+-- Unlike 'delegate' the predicate @proc@ runs in the 'Process' monad, allowing+-- for richer proxy behaviour. If @proc@ returns @False@ or the /runtime type check/+-- fails, no action is taken and the proxy process will continue running.+proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()+proxy pid proc = do+  receiveWait [+      matchAny (\m -> do+                   next <- handleMessage m proc+                   case next of+                     Just True  -> forward m pid+                     Just False -> return ()  -- explicitly ignored+                     Nothing    -> return ()) -- un-routable+    ]+  proxy pid proc+ -------------------------------------------------------------------------------- -- Process management                                                         -- --------------------------------------------------------------------------------@@ -405,6 +649,9 @@ -- | Die immediately - throws a 'ProcessExitException' with the given @reason@. die :: Serializable a => a -> Process b die reason = do+-- NOTE: We do /not/ want to use UnencodedMessage here, as the exception+-- could be decoded by a handler passed to 'catchExit', re-thrown or even+-- passed to another process via the tracing mechanism.   pid <- getSelfPid   liftIO $ throwIO (ProcessExitException pid (createMessage reason)) @@ -454,23 +701,23 @@ -- | Lift 'Control.Exception.catches' (almost). -- -- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded--- message, a handler must accept an input of the expected type. In order to+-- message, a handler must accept inputs of the expected type. In order to -- handle a list of potentially different handlers (and therefore input types),--- a handler passed to 'catchesExit' must accept 'AbstractMessage' and return+-- a handler passed to 'catchesExit' must accept 'Message' and return -- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@). ----- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.+-- See 'maybeHandleMessage' and 'Message' for more details. catchesExit :: Process b-            -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))]+            -> [(ProcessId -> Message -> (Process (Maybe b)))]             -> Process b catchesExit act handlers = catch act ((flip handleExit) handlers)   where     handleExit :: ProcessExitException-               -> [(ProcessId -> AbstractMessage -> Process (Maybe b))]+               -> [(ProcessId -> Message -> Process (Maybe b))]                -> Process b     handleExit ex [] = liftIO $ throwIO ex     handleExit ex@(ProcessExitException from msg) (h:hs) = do-      r <- h from (abstract msg)+      r <- h from msg       case r of         Nothing -> handleExit ex hs         Just p  -> return p@@ -483,6 +730,31 @@ getSelfNode :: Process NodeId getSelfNode = localNodeId . processNode <$> ask ++-- | Get statistics about the specified node+getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)+getNodeStats nid = do+    selfNode <- getSelfNode+    if nid == selfNode+      then Right `fmap` getLocalNodeStats -- optimisation+      else getNodeStatsRemote nid+  where+    getNodeStatsRemote :: NodeId -> Process (Either DiedReason NodeStats)+    getNodeStatsRemote nid = do+        sendCtrlMsg (Just nid) $ GetNodeStats nid+        bracket (monitorNode nid) unmonitor $ \mRef ->+            receiveWait [ match (\(stats :: NodeStats) -> return $ Right stats)+                        , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)+                                  (\(NodeMonitorNotification _ _ dr) -> return $ Left dr)+                        ]++-- | Get statistics about our local node+getLocalNodeStats :: Process NodeStats+getLocalNodeStats = do+  self <- getSelfNode+  sendCtrlMsg Nothing $ GetNodeStats self+  receiveWait [ match (\(stats :: NodeStats) -> return stats) ]+ -- | Get information about the specified process getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo) getProcessInfo pid =@@ -623,10 +895,13 @@ mask p = do     lproc <- ask     liftIO $ Ex.mask $ \restore ->-      runLocalProcess lproc (p (liftRestore lproc restore))+      runLocalProcess lproc (p (liftRestore restore))   where-    liftRestore :: LocalProcess -> (forall a. IO a -> IO a) -> (forall a. Process a -> Process a)-    liftRestore lproc restoreIO = liftIO . restoreIO . runLocalProcess lproc+    liftRestore :: (forall a. IO a -> IO a)+                -> (forall a. Process a -> Process a)+    liftRestore restoreIO = \p2 -> do+      ourLocalProc <- ask+      liftIO $ restoreIO $ runLocalProcess ourLocalProc p2  -- | Lift 'Control.Exception.onException' onException :: Process a -> Process b -> Process a@@ -654,7 +929,7 @@ data Handler a = forall e . Exception e => Handler (e -> Process a)  instance Functor Handler where-     fmap f (Handler h) = Handler (fmap f . h)+    fmap f (Handler h) = Handler (fmap f . h)  -- | Lift 'Control.Exception.catches' catches :: Process a -> [Handler a] -> Process a@@ -831,8 +1106,15 @@ -- | Named send to a process in the local registry (asynchronous) nsend :: Serializable a => String -> a -> Process () nsend label msg =-  sendCtrlMsg Nothing (NamedSend label (createMessage msg))+  sendCtrlMsg Nothing (NamedSend label (createUnencodedMessage msg)) +-- | Named send to a process in the local registry (asynchronous).+-- This function makes /no/ attempt to serialize and (in the case when the+-- destination process resides on the same local node) therefore ensure that+-- the payload is fully evaluated before it is delivered.+unsafeNSend :: Serializable a => String -> a -> Process ()+unsafeNSend = Unsafe.nsend+ -- | Named send to a process in a remote registry (asynchronous) nsendRemote :: Serializable a => NodeId -> String -> a -> Process () nsendRemote nid label msg =@@ -896,51 +1178,19 @@   liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))  ----------------------------------------------------------------------------------- Debugging/Tracing                                                          --+-- Auxiliary functions                                                        -- -------------------------------------------------------------------------------- --- | Send a message to the internal (system) trace facility. If tracing is--- enabled, this will create a custom trace event. Note that several Cloud Haskell--- sub-systems also generate trace events for informational/debugging purposes,--- thus traces generated this way will not be the only output seen.------ Just as with the "Debug.Trace" module, this is a debugging/tracing facility--- for use in development, and should not be used in a production setting ---- which is why the default behaviour is to trace to the GHC eventlog. For a--- general purpose logging facility, you should consider 'say'.------ Trace events can be written to the GHC event log, a text file, or to the--- standard system logger process (see 'say'). The default behaviour for writing--- to the eventlog requires specific intervention to work, without which traces--- are silently dropped/ignored and no output will be generated.--- The GHC eventlog documentation provides information about enabling, viewing--- and working with event traces: <http://hackage.haskell.org/trac/ghc/wiki/EventLog>.------ When a new local node is started, the contents of the environment variable--- @DISTRIBUTED_PROCESS_TRACE_FILE@ are checked for a valid file path. If this--- exists and the file can be opened for writing, all trace output will be directed--- thence. If the environment variable is empty, the path invalid, or the file--- unavailable for writing - e.g., because another node has already started--- tracing to it - then the @DISTRIBUTED_PROCESS_TRACE_CONSOLE@ environment--- variable is checked for /any/ non-empty value. If this is set, then all trace--- output will be directed to the system logger process. If neither evironment--- variable provides a valid trace configuration, all internal traces are written--- to "Debug.Trace.traceEventIO", which writes to the GHC eventlog.------ Users of the /simplelocalnet/ Cloud Haskell backend should also note that--- because the trace file option only supports trace output from a single node--- (so as to avoid interleaving), a file trace configured for the master node will--- prevent slaves from tracing to the file and they will fall back to using the--- console/'say' or eventlog instead.----trace :: String -> Process ()-trace s = do-  node <- processNode <$> ask-  liftIO $ Trace.trace (localTracer node) s+sendLocal :: (Serializable a) => ProcessId -> a -> Process ()+sendLocal to msg =+  sendCtrlMsg Nothing $ LocalSend to (createUnencodedMessage msg) ------------------------------------------------------------------------------------ Auxiliary functions                                                        -----------------------------------------------------------------------------------+sendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()+sendChanLocal spId msg =+  -- we *must* fully serialize/encode the message here, because+  -- attempting to use `unsafeCoerce' in the node controller+  -- won't work since we know nothing about the required type+  sendCtrlMsg Nothing $ LocalPortSend spId (createUnencodedMessage msg)  getMonitorRefFor :: Identifier -> Process MonitorRef getMonitorRefFor ident = do@@ -970,22 +1220,3 @@ -- | Link to a process/node/channel link' :: Identifier -> Process () link' = sendCtrlMsg Nothing . Link---- Send a control message-sendCtrlMsg :: Maybe NodeId  -- ^ Nothing for the local node-            -> ProcessSignal -- ^ Message to send-            -> Process ()-sendCtrlMsg mNid signal = do-  proc <- ask-  let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)-                  , ctrlMsgSignal = signal-                  }-  case mNid of-    Nothing -> do-      liftIO $ writeChan (localCtrlChan (processNode proc)) msg-    Just nid ->-      liftIO $ sendBinary (processNode proc)-                          (ProcessIdentifier (processId proc))-                          (NodeIdentifier nid)-                          WithImplicitReconnect-                          msg
+ src/Control/Distributed/Process/Internal/Spawn.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE RankNTypes  #-}+module Control.Distributed.Process.Internal.Spawn+  ( spawn+  , spawnLink+  , spawnMonitor+  , call+  , spawnSupervised+  , spawnChannel+  ) where++import Control.Distributed.Static+  ( Static+  , Closure+  , closureCompose+  , staticClosure+  )+import Control.Distributed.Process.Internal.Types+  ( NodeId(..)+  , ProcessId(..)+  , Process(..)+  , MonitorRef(..)+  , ProcessMonitorNotification(..)+  , NodeMonitorNotification(..)+  , DidSpawn(..)+  , SendPort(..)+  , ReceivePort(..)+  , nullProcessId+  )+import Control.Distributed.Process.Serializable (Serializable, SerializableDict)+import Control.Distributed.Process.Internal.Closure.BuiltIn+  ( sdictSendPort+  , sndStatic+  , idCP+  , seqCP+  , bindCP+  , splitCP+  , cpLink+  , cpSend+  , cpNewChan+  , cpDelay+  )+import Control.Distributed.Process.Internal.Primitives+  ( -- Basic messaging+    send+  , expect+  , receiveWait+  , match+  , matchIf+  , link+  , getSelfPid+  , monitor+  , monitorNode+  , unmonitor+  , spawnAsync+  , reconnect+  )++-- | Spawn a process+--+-- For more information about 'Closure', see+-- "Control.Distributed.Process.Closure".+--+-- See also 'call'.+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId+spawn nid proc = do+  us   <- getSelfPid+  mRef <- monitorNode nid+  sRef <- spawnAsync nid (cpDelay us proc)+  receiveWait [+      matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do+        unmonitor mRef+        send pid ()+        return pid+    , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->+        return (nullProcessId nid)+    ]++-- | Spawn a process and link to it+--+-- Note that this is just the sequential composition of 'spawn' and 'link'.+-- (The "Unified" semantics that underlies Cloud Haskell does not even support+-- a synchronous link operation)+spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId+spawnLink nid proc = do+  pid <- spawn nid proc+  link pid+  return pid++-- | Like 'spawnLink', but monitor the spawned process+spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)+spawnMonitor nid proc = do+  pid <- spawn nid proc+  ref <- monitor pid+  return (pid, ref)++-- | Run a process remotely and wait for it to reply+--+-- We monitor the remote process: if it dies before it can send a reply, we die+-- too.+--+-- For more information about 'Static', 'SerializableDict', and 'Closure', see+-- "Control.Distributed.Process.Closure".+--+-- See also 'spawn'.+call :: Serializable a+        => Static (SerializableDict a)+        -> NodeId+        -> Closure (Process a)+        -> Process a+call dict nid proc = do+  us <- getSelfPid+  (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)+  -- We are guaranteed to receive the reply before the monitor notification+  -- (if a reply is sent at all)+  -- NOTE: This might not be true if we switch to unreliable delivery.+  mResult <- receiveWait+    [ match (return . Right)+    , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)+              (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))+    ]+  case mResult of+    Right a  -> do+      -- Wait for the monitor message so that we the mailbox doesn't grow+      receiveWait+        [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)+                  (\(ProcessMonitorNotification {}) -> return ())+        ]+      -- Clean up connection to pid+      reconnect pid+      return a+    Left err ->+      fail $ "call: remote process died: " ++ show err++-- | Spawn a child process, have the child link to the parent and the parent+-- monitor the child+spawnSupervised :: NodeId+                -> Closure (Process ())+                -> Process (ProcessId, MonitorRef)+spawnSupervised nid proc = do+  us   <- getSelfPid+  them <- spawn nid (cpLink us `seqCP` proc)+  ref  <- monitor them+  return (them, ref)++-- | Spawn a new process, supplying it with a new 'ReceivePort' and return+-- the corresponding 'SendPort'.+spawnChannel :: forall a. Serializable a => Static (SerializableDict a)+             -> NodeId+             -> Closure (ReceivePort a -> Process ())+             -> Process (SendPort a)+spawnChannel dict nid proc = do+    us <- getSelfPid+    _ <- spawn nid (go us)+    expect+  where+    go :: ProcessId -> Closure (Process ())+    go pid = cpNewChan dict+           `bindCP`+             (cpSend (sdictSendPort dict) pid `splitCP` proc)+           `bindCP`+             (idCP `closureCompose` staticClosure sndStatic)
src/Control/Distributed/Process/Internal/StrictMVar.hs view
@@ -6,6 +6,7 @@   , newMVar   , takeMVar   , putMVar+  , readMVar   , withMVar   , modifyMVar_   , modifyMVar@@ -21,6 +22,7 @@   , newMVar   , takeMVar   , putMVar+  , readMVar   , withMVar   , modifyMVar_   , modifyMVar@@ -43,6 +45,9 @@  putMVar :: StrictMVar a -> a -> IO () putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x++readMVar :: StrictMVar a -> IO a+readMVar (StrictMVar v) = MVar.readMVar v  withMVar :: StrictMVar a -> (a -> IO b) -> IO b withMVar (StrictMVar v) = MVar.withMVar v
− src/Control/Distributed/Process/Internal/Trace.hs
@@ -1,122 +0,0 @@--- | Simple (internal) system logging/tracing support.-module Control.Distributed.Process.Internal.Trace-  ( Tracer-  , TraceArg(..)-  , trace-  , traceFormat-  , startTracing-  , stopTracer-  ) where--import Control.Concurrent (forkIO)-import Control.Concurrent.Chan (writeChan)-import Control.Concurrent.STM-  ( TQueue-  , newTQueueIO-  , readTQueue-  , writeTQueue-  , atomically-  )-import Control.Distributed.Process.Internal.Types-  ( Tracer(..)-  , LocalNode(..)-  , NCMsg(..)-  , Identifier(ProcessIdentifier)-  , ProcessSignal(NamedSend)-  , forever'-  , nullProcessId-  , createMessage-  )-import Control.Exception-  ( catch-  , throwTo-  , SomeException-  , AsyncException(ThreadKilled)-  )-import Data.List (intersperse)-import Data.Time.Clock (getCurrentTime)-import Data.Time.Format (formatTime)-import Debug.Trace (traceEventIO)--import Prelude hiding (catch)--import System.Environment (getEnv)-import System.IO-  ( Handle-  , IOMode(AppendMode)-  , BufferMode(..)-  , openFile-  , hClose-  , hPutStrLn-  , hSetBuffering-  )-import System.Locale (defaultTimeLocale)--data TraceArg = -    TraceStr String-  | forall a. (Show a) => Trace a--startTracing :: LocalNode -> IO LocalNode-startTracing node = do-  tracer <- defaultTracer node-  return node { localTracer = tracer }--defaultTracer :: LocalNode -> IO Tracer-defaultTracer node = do-  catch (getEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)-        (\(_ :: IOError) -> defaultTracerAux node)--defaultTracerAux :: LocalNode -> IO Tracer-defaultTracerAux node = do-  catch (getEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> procTracer node)-        (\(_ :: IOError) -> return (EventLogTracer traceEventIO))-  where procTracer :: LocalNode -> IO Tracer-        procTracer n = return $ (LocalNodeTracer n)--logfileTracer :: FilePath -> IO Tracer-logfileTracer p = do-    q <- newTQueueIO-    h <- openFile p AppendMode-    hSetBuffering h LineBuffering-    tid <- forkIO $ logger h q `catch` (\(_ :: SomeException) ->-                                         hClose h >> return ())-    return $ LogFileTracer tid q h-  where logger :: Handle -> TQueue String -> IO ()-        logger h q' = forever' $ do-          msg <- atomically $ readTQueue q'-          now <- getCurrentTime-          hPutStrLn h $ msg ++ (formatTime defaultTimeLocale " - %c" now)---- TODO: compatibility layer for GHC/base versions (e.g., where's killThread?)--stopTracer :: Tracer -> IO ()  -- overzealous but harmless duplication of hClose-stopTracer (LogFileTracer tid _ h) = throwTo tid ThreadKilled >> hClose h-stopTracer _                       = return ()--trace :: Tracer -> String -> IO ()-trace (LogFileTracer _ q _) msg = atomically $ writeTQueue q msg-trace (LocalNodeTracer n)   msg = sendTraceMsg n msg-trace (EventLogTracer t)    msg = t msg-trace InactiveTracer        _   = return ()--traceFormat :: Tracer-            -> String-            -> [TraceArg]-            -> IO ()-traceFormat t d ls =-    trace t $ concat (intersperse d (map toS ls))-  where toS :: TraceArg -> String-        toS (TraceStr s) = s-        toS (Trace    a) = show a--sendTraceMsg :: LocalNode -> String -> IO ()-sendTraceMsg node string = do-  now <- getCurrentTime-  msg <- return $ (formatTime defaultTimeLocale "%c" now, string)-  emptyPid <- return $ (nullProcessId (localNodeId node))-  traceMsg <- return $ NCMsg {-                         ctrlMsgSender = ProcessIdentifier (emptyPid)-                       , ctrlMsgSignal = (NamedSend "logger" (createMessage msg))-                       }-  writeChan (localCtrlChan node) traceMsg-
src/Control/Distributed/Process/Internal/Types.hs view
@@ -1,3 +1,9 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE GeneralizedNewtypeDeriving  #-}+{-# LANGUAGE GADTs  #-}+{-# LANGUAGE DeriveGeneric #-}+ -- | Types used throughout the Cloud Haskell framework -- -- We collect all types used internally in a single module because@@ -15,6 +21,7 @@     -- * Local nodes and processes   , LocalNode(..)   , Tracer(..)+  , MxEventBus(..)   , LocalNodeState(..)   , LocalProcess(..)   , LocalProcessState(..)@@ -29,7 +36,10 @@   , ReceivePort(..)     -- * Messages   , Message(..)+  , isEncoded   , createMessage+  , createUnencodedMessage+  , unsafeCreateUnencodedMessage   , messageToPayload   , payloadToMessage     -- * Node controller user-visible data types@@ -53,6 +63,7 @@   , RegisterReply(..)   , ProcessInfo(..)   , ProcessInfoNone(..)+  , NodeStats(..)     -- * Node controller internal data types   , NCMsg(..)   , ProcessSignal(..)@@ -75,7 +86,7 @@ import System.Mem.Weak (Weak) import Data.Map (Map) import Data.Int (Int32)-import Data.Typeable (Typeable)+import Data.Typeable (Typeable, typeOf) import Data.Binary (Binary(put, get), putWord8, getWord8, encode) import qualified Data.ByteString as BSS (ByteString, concat, copy) import qualified Data.ByteString.Lazy as BSL@@ -83,15 +94,17 @@   , toChunks   , splitAt   , fromChunks+  , length   ) import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..)) import Data.Accessor (Accessor, accessor) import Control.Category ((>>>))+import Control.DeepSeq (NFData(..)) import Control.Exception (Exception) import Control.Concurrent (ThreadId) import Control.Concurrent.Chan (Chan) import Control.Concurrent.STM (STM)-import qualified Control.Concurrent.STM as STM (TQueue)+import Control.Concurrent.STM.TChan (TChan) import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection) import Control.Applicative (Applicative, Alternative, (<$>), (<*>)) import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)@@ -110,16 +123,20 @@ import Control.Distributed.Process.Internal.WeakTQueue (TQueue) import Control.Distributed.Static (RemoteTable, Closure) import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)-import System.IO (Handle) +import Data.Hashable+import GHC.Generics+ -------------------------------------------------------------------------------- -- Node and process identifiers                                               -- --------------------------------------------------------------------------------  -- | Node identifier newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }-  deriving (Eq, Ord, Binary, Typeable)-+  deriving (Eq, Ord, Typeable, Generic)+instance Binary NodeId where+instance NFData NodeId+instance Hashable NodeId where instance Show NodeId where   show (NodeId addr) = "nid://" ++ show addr @@ -129,8 +146,10 @@   { lpidUnique  :: {-# UNPACK #-} !Int32   , lpidCounter :: {-# UNPACK #-} !Int32   }-  deriving (Eq, Ord, Typeable, Show)+  deriving (Eq, Ord, Typeable, Generic, Show) +instance Hashable LocalProcessId where+ -- | Process identifier data ProcessId = ProcessId   { -- | The ID of the node the process is running on@@ -138,8 +157,12 @@     -- | Node-local identifier for the process   , processLocalId :: {-# UNPACK #-} !LocalProcessId   }-  deriving (Eq, Ord, Typeable)+  deriving (Eq, Ord, Typeable, Generic) +instance Binary ProcessId where+instance NFData ProcessId where+instance Hashable ProcessId where+ instance Show ProcessId where   show (ProcessId (NodeId addr) (LocalProcessId _ lid))     = "pid://" ++ show addr ++ ":" ++ show lid@@ -149,8 +172,10 @@     NodeIdentifier !NodeId   | ProcessIdentifier !ProcessId   | SendPortIdentifier !SendPortId-  deriving (Eq, Ord)+  deriving (Eq, Ord, Generic) +instance Hashable Identifier where+ instance Show Identifier where   show (NodeIdentifier nid)     = show nid   show (ProcessIdentifier pid)  = show pid@@ -180,13 +205,31 @@ -- Local nodes and processes                                                  -- -------------------------------------------------------------------------------- --- | Required for system tracing in the node controller-data Tracer =-    LogFileTracer   !ThreadId !(STM.TQueue String) !Handle-  | EventLogTracer  !(String -> IO ())-  | LocalNodeTracer !LocalNode-  | InactiveTracer  -- NB: never used, this is required to initialize LocalNode+-- | Provides access to the trace controller+data Tracer = Tracer+              {+                -- | Process id for the currently active trace handler+                tracerPid :: !ProcessId+                -- | Weak reference to the tracer controller's mailbox+              , weakQ     :: !(Weak (CQueue Message))+              } +-- | Local system management event bus state+data MxEventBus =+    MxEventBusInitialising+  | MxEventBus+    {+      -- | Process id of the management agent controller process+      agent  :: !ProcessId+      -- | Configuration for the local trace controller+    , tracer :: !Tracer+      -- | Weak reference to the management agent controller's mailbox+    , evbuss :: !(Weak (CQueue Message))+      -- | API for adding management agents to a running node+    , mxNew  :: !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)+--    , mxReg  :: !(StrictMVar (Map MxAgentId ))+    }+ -- | Local nodes data LocalNode = LocalNode   { -- | 'NodeId' of the node@@ -197,8 +240,8 @@   , localState      :: !(StrictMVar LocalNodeState)     -- | Channel for the node controller   , localCtrlChan   :: !(Chan NCMsg)-    -- | Current active system debug/trace log-  , localTracer     :: !Tracer+    -- | Internal management event bus+  , localEventBus   :: !MxEventBus     -- | Runtime lookup table for supporting closures     -- TODO: this should be part of the CH state, not the local endpoint state   , remoteTable     :: !RemoteTable@@ -265,8 +308,9 @@     -- | Process-local ID of the channel   , sendPortLocalId   :: {-# UNPACK #-} !LocalSendPortId   }-  deriving (Eq, Ord)+  deriving (Eq, Ord, Typeable, Generic) +instance Hashable SendPortId where instance Show SendPortId where   show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)     = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid@@ -278,8 +322,12 @@     -- | The (unique) ID of this send port     sendPortId :: SendPortId   }-  deriving (Typeable, Binary, Show, Eq, Ord)+  deriving (Typeable, Generic, Show, Eq, Ord) +instance (Serializable a) => Binary (SendPort a) where+instance (Hashable a) => Hashable (SendPort a) where+instance (NFData a) => NFData (SendPort a) where+ -- | The receive end of a typed channel (not serializable) -- -- Note that 'ReceivePort' implements 'Functor', 'Applicative', 'Alternative'@@ -303,25 +351,57 @@ --------------------------------------------------------------------------------  -- | Messages consist of their typeRep fingerprint and their encoding-data Message = Message+data Message =+  EncodedMessage   { messageFingerprint :: !Fingerprint   , messageEncoding    :: !BSL.ByteString+  } |+  forall a . Serializable a =>+  UnencodedMessage+  {+    messageFingerprint :: !Fingerprint+  , messagePayload     :: !a   }+  deriving (Typeable)  instance Show Message where-  show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp []+  show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []+  show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc) +-- | /internal use only/.+isEncoded :: Message -> Bool+isEncoded (EncodedMessage _ _) = True+isEncoded _                    = False+-- [note] isEncoded:+-- This is just as internal as it looks and yes, it does feel a bit odd that+-- we're exporting it for use, however DPP does a /lot/ of work with low+-- level APIs such as unwrapMessage and handleMessage, and in the process+-- tries very hard to avoid copying (and re-serialisation) where possible.+-- Being able to determine that a message is encoded (or otherwise) makes+-- that a lot more manageable.+ -- | Turn any serialiable term into a message createMessage :: Serializable a => a -> Message-createMessage a = Message (fingerprint a) (encode a)+createMessage a = EncodedMessage (fingerprint a) (encode a) +-- | Turn any serializable term into an unencoded/local message+createUnencodedMessage :: Serializable a => a -> Message+createUnencodedMessage a =+  let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a++-- | Turn any serializable term into an unencodede/local message, without+-- evalutaing it! This is a dangerous business.+unsafeCreateUnencodedMessage :: Serializable a => a -> Message+unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a+ -- | Serialize a message messageToPayload :: Message -> [BSS.ByteString]-messageToPayload (Message fp enc) = encodeFingerprint fp : BSL.toChunks enc+messageToPayload (EncodedMessage fp enc) = encodeFingerprint fp : BSL.toChunks enc+messageToPayload (UnencodedMessage fp m) = messageToPayload ((EncodedMessage fp (encode m)))  -- | Deserialize a message payloadToMessage :: [BSS.ByteString] -> Message-payloadToMessage payload = Message fp (copy msg)+payloadToMessage payload = EncodedMessage fp (copy msg)   where     encFp :: BSL.ByteString     msg   :: BSL.ByteString@@ -346,7 +426,8 @@     -- | Unique to distinguish multiple monitor requests by the same process   , monitorRefCounter :: !Int32   }-  deriving (Eq, Ord, Show)+  deriving (Eq, Ord, Show, Typeable, Generic)+instance Hashable MonitorRef where  -- | Message sent by process monitors data ProcessMonitorNotification =@@ -446,6 +527,15 @@ data RegisterReply = RegisterReply String Bool   deriving (Show, Typeable) +data NodeStats = NodeStats {+     nodeStatsNode            :: NodeId+   , nodeStatsRegisteredNames :: Int+   , nodeStatsMonitors        :: Int+   , nodeStatsLinks           :: Int+   , nodeStatsProcesses       :: Int+   }+   deriving (Show, Eq, Typeable)+ -- | Provide information about a running process data ProcessInfo = ProcessInfo {     infoNode               :: NodeId@@ -480,23 +570,27 @@   | WhereIs !String   | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister   | NamedSend !String !Message+  | LocalSend !ProcessId !Message+  | LocalPortSend !SendPortId !Message   | Kill !ProcessId !String   | Exit !ProcessId !Message   | GetInfo !ProcessId+  | SigShutdown+  | GetNodeStats !NodeId   deriving Show  -------------------------------------------------------------------------------- -- Binary instances                                                           -- -------------------------------------------------------------------------------- +instance Binary Message where+  put msg = put $ messageToPayload msg+  get = payloadToMessage <$> get+ instance Binary LocalProcessId where   put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)   get      = LocalProcessId <$> get <*> get -instance Binary ProcessId where-  put pid = put (processNodeId pid) >> put (processLocalId pid)-  get     = ProcessId <$> get <*> get- instance Binary ProcessMonitorNotification where   put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason   get = ProcessMonitorNotification <$> get <*> get <*> get@@ -518,18 +612,22 @@   get     = MonitorRef <$> get <*> get  instance Binary ProcessSignal where-  put (Link pid)            = putWord8 0 >> put pid-  put (Unlink pid)          = putWord8 1 >> put pid-  put (Monitor ref)         = putWord8 2 >> put ref-  put (Unmonitor ref)       = putWord8 3 >> put ref-  put (Died who reason)     = putWord8 4 >> put who >> put reason-  put (Spawn proc ref)      = putWord8 5 >> put proc >> put ref-  put (WhereIs label)       = putWord8 6 >> put label+  put (Link pid)              = putWord8 0 >> put pid+  put (Unlink pid)            = putWord8 1 >> put pid+  put (Monitor ref)           = putWord8 2 >> put ref+  put (Unmonitor ref)         = putWord8 3 >> put ref+  put (Died who reason)       = putWord8 4 >> put who >> put reason+  put (Spawn proc ref)        = putWord8 5 >> put proc >> put ref+  put (WhereIs label)         = putWord8 6 >> put label   put (Register label nid pid force) = putWord8 7 >> put label >> put nid >> put pid >> put force-  put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)-  put (Kill pid reason)     = putWord8 9 >> put pid >> put reason-  put (Exit pid reason)     = putWord8 10 >> put pid >> put (messageToPayload reason)-  put (GetInfo about)       = putWord8 30 >> put about+  put (NamedSend label msg)   = putWord8 8 >> put label >> put (messageToPayload msg)+  put (Kill pid reason)       = putWord8 9 >> put pid >> put reason+  put (Exit pid reason)       = putWord8 10 >> put pid >> put (messageToPayload reason)+  put (LocalSend to' msg)      = putWord8 11 >> put to' >> put (messageToPayload msg)+  put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)+  put (GetInfo about)         = putWord8 30 >> put about+  put (SigShutdown)         = putWord8 31+  put (GetNodeStats nid)         = putWord8 32 >> put nid   get = do     header <- getWord8     case header of@@ -544,7 +642,11 @@       8  -> NamedSend <$> get <*> (payloadToMessage <$> get)       9  -> Kill <$> get <*> get       10 -> Exit <$> get <*> (payloadToMessage <$> get)+      11 -> LocalSend <$> get <*> (payloadToMessage <$> get)+      12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)       30 -> GetInfo <$> get+      31 -> return SigShutdown+      32 -> GetNodeStats <$> get       _ -> fail "ProcessSignal.get: invalid"  instance Binary DiedReason where@@ -571,6 +673,9 @@   put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)   get = SendPortId <$> get <*> get +instance NFData SendPortId where+  rnf cid = (sendPortProcessId cid) `seq` (sendPortLocalId cid) `seq` ()+ instance Binary Identifier where   put (ProcessIdentifier pid)  = putWord8 0 >> put pid   put (NodeIdentifier nid)     = putWord8 1 >> put nid@@ -599,6 +704,14 @@            >> put (infoMonitors pInfo)            >> put (infoLinks pInfo) +instance Binary NodeStats where+  get = NodeStats <$> get <*> get <*> get <*> get <*> get+  put nStats =  put (nodeStatsNode nStats)+             >> put (nodeStatsRegisteredNames nStats)+             >> put (nodeStatsMonitors nStats)+             >> put (nodeStatsLinks nStats)+             >> put (nodeStatsProcesses nStats)+ instance Binary ProcessInfoNone where   get = ProcessInfoNone <$> get   put (ProcessInfoNone r) = put r@@ -623,7 +736,7 @@ localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid  localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))-localConnectionBetween from to = localConnections >>> DAC.mapMaybe (from, to)+localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')  monitorCounter :: Accessor LocalProcessState Int32 monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
src/Control/Distributed/Process/Internal/WeakTQueue.hs view
@@ -1,10 +1,11 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE MagicHash, UnboxedTuples #-} -- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue -- -- Not all functionality from the original module is available: unGetTQueue, -- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd -- need to be able to touch# the write end of the queue inside unGetTQueue, but -- that means we need a version of touch# that works within the STM monad.-{-# LANGUAGE MagicHash, UnboxedTuples #-} module Control.Distributed.Process.Internal.WeakTQueue (   -- * Original functionality   TQueue,
+ src/Control/Distributed/Process/Management.hs view
@@ -0,0 +1,557 @@+{-# LANGUAGE DeriveDataTypeable         #-}+{-# LANGUAGE DeriveGeneric              #-}+{-# LANGUAGE StandaloneDeriving         #-}+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE PatternGuards              #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE UndecidableInstances       #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Distributed.Process.Management+-- Copyright   :  (c) Well-Typed / Tim Watson+-- License     :  BSD3 (see the file LICENSE)+--+-- Maintainer  :  Tim Watson <watson.timothy@gmail.com>+-- Stability   :  experimental+-- Portability :  non-portable (requires concurrency)+--+-- [Management Extensions API]+--+-- This module presents an API for creating /Management Agents/:+-- special processes that are capable of receiving and responding to+-- a node's internal system events. These /system events/ are delivered by+-- the management event bus: An internal subsystem maintained for each+-- running node, to which all agents are automatically subscribed.+--+-- /Agents/ are defined in terms of /event sinks/, taking a particular+-- @Serializable@ type and evaluating to an action in the 'MxAgent' monad in+-- response. Each 'MxSink' evaluates to an 'MxAction' that specifies whether+-- the agent should continue processing it's inputs or stop. If the type of a+-- message cannot be matched to any of the agent's sinks, it will be discarded.+-- A sink can also deliberately skip processing a message, deferring to the+-- remaining handlers. This is the /only/ way that more than one event sink+-- can handle the same data type, since otherwise the first /type match/ will+-- /win/ every time a message arrives. See 'mxSkip' for details.+--+-- Various events are published to the management event bus automatically,+-- the full list of which can be found in the definition of the 'MxEvent' data+-- type. Additionally, clients of the /Management API/ can publish arbitrary+-- @Serializable@ data to the event bus using 'mxNotify'. All running agents+-- receive all events (from the primary event bus to which they're subscribed).+--+-- Agent processes are automatically registered on the local node, and can+-- receive messages via their mailbox just like ordinary processes. Unlike+-- ordinary @Process@ code however, it is unnecessary (though possible) for+-- agents to use the base @expect@ and @receiveX@ primitives to do this, since+-- the management infrastructure will continuously read from both the primary+-- event bus /and/ the process' own mailbox. Messages are transparently passed+-- to the agent's event sinks from both sources, so an agent need only concern+-- itself with how to respond to its inputs.+--+-- Some agents may wish to prioritise messages from their mailbox over traffic+-- on the management event bus, or vice versa. The 'mxReceive' and+-- 'mxReceiveChan' API calls do this for the mailbox and event bus,+-- respectively. The /prioritisation/ these APIs offer is simply that the chosen+-- data stream will be checked first. No blocking will occur if the chosen+-- (prioritised) source is devoid of input messages, instead the agent handling+-- code will revert to switching between the alternatives in /round-robin/ as+-- usual. If messages exist in one or more channels, they will be consumed as+-- soon as they're available, priority is effectively a hint about which+-- channel to consume from, should messages be available in both.+--+-- Prioritisation then, is a /hint/ about the preference of data source from+-- which the next input should be chosen. No guarantee can be made that the+-- chosen source will in fact be selected at runtime.+--+-- [Management API Semantics]+--+-- The management API provides /no guarantees whatsoever/, viz:+--+--  * The ordering of messages delivered to the event bus.+--+--  * The order in which agents will be executed.+--+--  * Whether messages will be taken from the mailbox first, or the event bus.+--+-- [Management Data API]+--+-- Both management agents and clients of the API have access to a variety of+-- data storage capabilities, to facilitate publishing and consuming useful+-- system information. Agents maintain their own internal state privately (via a+-- state transformer - see 'mxGetLocal' et al), however it is possible for+-- agents to share additional data with each other (and the outside world)+-- using /data tables/.+--+-- Each agent is assigned its own data table, which acts as a shared map, where+-- the keys are @String@s and the values are @Serializable@ datum of whatever+-- type the agent or its clients stores.+--+-- Because an agent's /data table/ stores its values in raw 'Message' format,+-- it works effectively as an /un-typed dictionary/, into which data of varying+-- types can be fed and later retrieved. The upside of this is that different+-- keys can be mapped to various types without any additional work on the part+-- of the developer. The downside is that the code reading these values must+-- know in advance what type(s) to expect, and the API provides no additional+-- support for handling that.+--+-- Publishing is accomplished using the 'mxPublish' and 'mxSet' APIs, whilst+-- querying and deletion are handled by 'mxGet', 'mxClear', 'mxPurgeTable' and+-- 'mxDropTable' respectively.+--+-- When a management agent terminates, their tables are left in memory despite+-- termination, such that an agent may resume its role (by restarting) or have+-- its 'MxAgentId' taken over by another subsequent agent, leaving the data+-- originally captured in place.+--+-- [Defining Agents]+--+-- New agents are defined with 'mxAgent' and require a unique 'MxAgentId', an+-- initial state - 'MxAgent' runs in a state transformer - and a list of the+-- agent's event sinks. Each 'MxSink' is defined in terms of a specific+-- @Serializable@ type, via the 'mxSink' function, binding the event handler+-- expression to inputs of only that type.+--+-- Apart from modifying its own local state, an agent can execute arbitrary+-- @Process a@ code via lifting (see 'liftMX') and even publish its own messages+-- back to the primary event bus (see 'mxBroadcast').+--+-- Since messages are delivered to agents from both the management event bus and+-- the agent processes mailbox, agents (i.e., event sinks) will generally have+-- no idea as to their origin. An agent can, however, choose to prioritise the+-- choice of input (source) each time one of its event sinks runs. The /standard/+-- way for an event sink to indicate that the agent is ready for its next input+-- is to evaluate 'mxReady'. When this happens, the management infrastructure+-- will obtain data from the event bus and process' mailbox in a round robbin+-- fashion, i.e., one after the other, changing each time.+--+-- [Example Code]+--+-- What follows is a grossly over-simplified example of a management agent that+-- provides a basic name monitoring facility. Whenever a process name is+-- registered or unregistered, clients are informed of the fact.+--+-- > -- simple notification data type+-- >+-- > data Registration = Reg { added  :: Bool+-- >                         , procId :: ProcessId+-- >                         , name   :: String+-- >                         }+-- >+-- > -- start a /name monitoring agent/+-- > nameMonitorAgent = do+-- >   mxAgent (MxAgentId "name-monitor") Set.empty [+-- >         (mxSink $ \(pid :: ProcessId) -> do+-- >            mxUpdateState $ Set.insert pid+-- >            mxReady)+-- >       , (mxSink $+-- >             let act =+-- >                   case ev of+-- >                     (MxRegistered   p n) -> notify True  n p+-- >                     (MxUnRegistered p n) -> notify False n p+-- >                     _                    -> return ()+-- >             act >> mxReady)+-- >     ]+-- >   where+-- >     notify a n p = do+-- >       Foldable.mapM_ (liftMX . deliver (Reg a n p)) =<< mxGetLocal+-- >+--+-- The client interface (for sending their pid) can take one of two forms:+--+-- > monitorNames = getSelfPid >>= nsend "name-monitor"+-- > monitorNames2 = getSelfPid >>= mxNotify+--+-- For some real-world examples, see the distributed-process-platform package.+--+-- [Performance, Stablity and Scalability]+--+-- /Management Agents/ offer numerous advantages over regular processes:+-- broadcast communication with them can have a lower latency, they offer+-- simplified messgage (i.e., input type) handling and they have access to+-- internal system information that would be otherwise unobtainable.+--+-- Do not be tempted to implement everything (e.g., the kitchen sink) using the+-- management API though. There are overheads associated with management agents+-- which is why they're presented as tools for consuming low level system+-- information, instead of as /application level/ development tools.+--+-- Agents that rely heavily on a busy mailbox can cause the management event+-- bus to backlog un-GC'ed data, leading to increased heap space. Producers that+-- do not take care to avoid passing unevaluated thunks to the API can crash+-- /all/ the agents in the system. Agents are not monitored or managed in any+-- way, and those that crash will not be restarted.+--+-- The management event bus can receive a great deal of traffic. Every time+-- a message is sent and/or received, an event is passed to the agent controller+-- and broadcast to all agents (plus the trace controller, if tracing is enabled+-- for the node). This is already a significant overhead - though profiling and+-- benchmarks have demonstrated that it does not adversely affect performance+-- if few agents are installed. Agents will typically use more cycles than plain+-- processes, since they perform additional work: selecting input data from both+-- the event bus /and/ their own mailboxes, plus searching through the set of+-- event sinks (for each agent) to determine the right handler for the event.+--+-- Each management agent requires not only its own @Process@ (in which the agent+-- code is run), but also a peer process that provides its /data table/. These+-- data tables also have to be coordinated and manaaged on each agent's behalf.+--+-- [Architecture Overview]+--+-- The architecture of the management event bus is internal and subject to+-- change without prior notice. The description that follows is provided for+-- informational purposes only.+--+-- When a node initially starts, two special, internal system processes are+-- started to support the management infrastructure. The first, known as the+-- /trace controller/, is responsible for consuming 'MxEvent's and forwarding+-- them to the configured tracer - see "Control.Distributed.Process.Debug" for+-- further details. The second is the /management agent controller/, and is the+-- primary worker process underpinning the management infrastructure. All+-- published management events are routed to this process, which places them+-- onto a system wide /event bus/ and additionally passes them directly to the+-- /trace controller/.+--+-- There are several reasons for segregating the tracing and management control+-- planes in this fashion. Tracing can be enabled or disabled by clients, whilst+-- the management event bus cannot, since in addition to providing+-- runtime instrumentation, its intended use-cases include node monitoring, peer+-- discovery (via topology providing backends) and other essential system+-- services that require knowledge of otherwise hidden system internals. Tracing+-- is also subject to /trace flags/ that limit the specific 'MxEvent's delivered+-- to trace clients - an overhead/complexity not shared by management agents.+-- Finally, tracing and management agents are implemented using completely+-- different signalling techniques - more on this later - which would introduce+-- considerable complexity if the shared the same /event loop/.+--+-- The management control plane is driven by a shared broadcast channel, which+-- is written to by the agent controller and subscribed to by all agent+-- processes. Agents are spawned as regular processes, whose primary+-- implementation (i.e., /server loop/) is responsible for consuming+-- messages from both the broadcast channel and their own mailbox. Once+-- consumed, messages are applied to the agent's /event sinks/ until one+-- matches the input, at which point it is applied and the loop continues.+-- The implementation chooses from the event bus and the mailbox in a+-- round-robin fashion, until a message is received. This polling activity would+-- lead to management agents consuming considerable system resources if left+-- unchecked, therefore the implementation will poll for a limitted number of+-- retries, after which it will perform a blocking read on the event bus.+--+-----------------------------------------------------------------------------+module Control.Distributed.Process.Management+  (+    MxEvent(..)+    -- * Firing Arbitrary /Mx Events/+  , mxNotify+    -- * Constructing Mx Agents+  , MxAction()+  , MxAgentId(..)+  , MxAgent()+  , mxAgent+  , mxAgentWithFinalize+  , MxSink()+  , mxSink+  , mxGetId+  , mxDeactivate+  , mxReady+  , mxSkip+  , mxReceive+  , mxReceiveChan+  , mxBroadcast+  , mxSetLocal+  , mxGetLocal+  , mxUpdateLocal+  , liftMX+    -- * Mx Data API+  , mxPublish+  , mxSet+  , mxGet+  , mxClear+  , mxPurgeTable+  , mxDropTable+  ) where++import Control.Applicative ((<$>))+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TChan+  ( tryReadTChan+  , readTChan+  , writeTChan+  , TChan+  )+import Control.Distributed.Process.Internal.Primitives+  ( newChan+  , nsend+  , receiveWait+  , receiveTimeout+  , matchChan+  , matchAny+  , matchSTM+  , unwrapMessage+  , onException+  , register+  , whereis+  , die+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , ProcessId+  , Message+  , LocalProcess(..)+  , LocalNode(..)+  , MxEventBus(..)+  , unsafeCreateUnencodedMessage+  )+import Control.Distributed.Process.Management.Internal.Bus (publishEvent)+import qualified Control.Distributed.Process.Management.Internal.Table as Table+import Control.Distributed.Process.Management.Internal.Types+  ( MxAgentId(..)+  , MxAgent(..)+  , MxAction(..)+  , ChannelSelector(..)+  , MxAgentState(..)+  , MxAgentStart(..)+  , MxSink+  , MxEvent(..)+  )+import Control.Distributed.Process.Serializable (Serializable)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask)+import qualified Control.Monad.State as ST+  ( MonadState+  , StateT+  , get+  , modify+  , lift+  , runStateT+  )++-- | Publishes an arbitrary @Serializable@ message to the management event bus.+-- Note that /no attempt is made to force the argument/, therefore it is very+-- important that you do not pass unevaluated thunks that might crash the+-- receiving process via this API, since /all/ registered agents will gain+-- access to the data structure once it is broadcast by the agent controller.+mxNotify :: (Serializable a) => a -> Process ()+mxNotify msg = do+  bus <- localEventBus . processNode <$> ask+  liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg++-- | Publish an arbitrary @Message@ as a property in the management database.+--+-- For publishing @Serializable@ data, use 'mxSet' instead.+--+mxPublish :: MxAgentId -> String -> Message -> Process ()+mxPublish a k v = Table.set k v (Table.MxForAgent a)++-- | Sets an arbitrary @Serializable@ datum against a key in the management+-- database. Note that /no attempt is made to force the argument/, therefore+-- it is very important that you do not pass unevaluated thunks that might+-- crash some other, arbitrary process (or management agent!) that obtains+-- and attempts to force the value later on.+--+mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()+mxSet mxId key msg = do+  Table.set key (unsafeCreateUnencodedMessage msg) (Table.MxForAgent mxId)++-- | Fetches a property from the management database for the given key.+-- If the property is not set, or does not match the expected type when+-- typechecked (at runtime), returns @Nothing@.+mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)+mxGet = Table.fetch . Table.MxForAgent++-- | Clears a property from the management database using the given key.+-- If the key does not exist in the database, this is a noop.+mxClear :: MxAgentId -> String -> Process ()+mxClear mxId key = Table.clear key (Table.MxForAgent mxId)++-- | Purges a table in the management database of all its stored properties.+mxPurgeTable :: MxAgentId -> Process ()+mxPurgeTable = Table.purge . Table.MxForAgent++-- | Deletes a table from the management database.+mxDropTable :: MxAgentId -> Process ()+mxDropTable = Table.delete . Table.MxForAgent++--------------------------------------------------------------------------------+-- API for writing user defined management extensions (i.e., agents)          --+--------------------------------------------------------------------------------++-- | Return the 'MxAgentId' for the currently executing agent.+--+mxGetId :: MxAgent s MxAgentId+mxGetId = ST.get >>= return . mxAgentId++-- | The 'MxAgent' version of 'mxNotify'.+--+mxBroadcast :: (Serializable m) => m -> MxAgent s ()+mxBroadcast msg = do+  state <- ST.get+  liftMX $ liftIO $ atomically $ do+    writeTChan (mxBus state) (unsafeCreateUnencodedMessage msg)++-- | Gracefully terminate an agent.+--+mxDeactivate :: forall s. String -> MxAgent s MxAction+mxDeactivate = return . MxAgentDeactivate++-- | Continue executing (i.e., receiving and processing messages).+--+mxReady :: forall s. MxAgent s MxAction+mxReady = return MxAgentReady++-- | Causes the currently executing /event sink/ to be skipped.+-- The remaining declared event sinks will be evaluated to find+-- a matching handler. Can be used to allow multiple event sinks+-- to process data of the same type.+--+mxSkip :: forall s. MxAgent s MxAction+mxSkip = return MxAgentSkip++-- | Continue exeucting, prioritising inputs from the process' own+-- /mailbox/ ahead of data from the management event bus.+--+mxReceive :: forall s. MxAgent s MxAction+mxReceive = return $ MxAgentPrioritise Mailbox++-- | Continue exeucting, prioritising inputs from the management event bus+-- over the process' own /mailbox/.+--+mxReceiveChan :: forall s. MxAgent s MxAction+mxReceiveChan = return $ MxAgentPrioritise InputChan++-- | Lift a @Process@ action.+--+liftMX :: Process a -> MxAgent s a+liftMX p = MxAgent $ ST.lift p++-- | Set the agent's local state.+--+mxSetLocal :: s -> MxAgent s ()+mxSetLocal s = ST.modify $ \st -> st { mxLocalState = s }++-- | Update the agent's local state.+--+mxUpdateLocal :: (s -> s) -> MxAgent s ()+mxUpdateLocal f = ST.modify $ \st -> st { mxLocalState = (f $ mxLocalState st) }++-- | Fetch the agent's local state.+--+mxGetLocal :: MxAgent s s+mxGetLocal = ST.get >>= return . mxLocalState++-- | Create an 'MxSink' from an expression taking a @Serializable@ type @m@,+-- that yields an 'MxAction' in the 'MxAgent' monad.+--+mxSink :: forall s m . (Serializable m)+       => (m -> MxAgent s MxAction)+       -> MxSink s+mxSink act msg = do+  msg' <- liftMX $ (unwrapMessage msg :: Process (Maybe m))+  case msg' of+    Nothing -> return Nothing+    Just m  -> do+      r <- act m+      case r of+        MxAgentSkip -> return Nothing+        _           -> return $ Just r++-- private ADT: a linked list of event sinks+data MxPipeline s =+  MxPipeline+  {+    current  :: !(MxSink s)+  , next     :: !(MxPipeline s)+  } | MxStop++-- | Activates a new agent.+--+mxAgent :: MxAgentId -> s -> [MxSink s] -> Process ProcessId+mxAgent mxId st hs = mxAgentWithFinalize mxId st hs $ return ()++-- | Activates a new agent. This variant takes a /finalizer/ expression,+-- that is run once the agent shuts down (even in case of failure/exceptions).+-- The /finalizer/ expression runs in the mx monad -  @MxAgent s ()@ - such+-- that the agent's internal state remains accessible to the shutdown/cleanup+-- code.+--+mxAgentWithFinalize :: MxAgentId+        -> s+        -> [MxSink s]+        -> MxAgent s ()+        -> Process ProcessId+mxAgentWithFinalize mxId initState handlers dtor = do+    let name = agentId mxId+    existing <- whereis name+    case existing of+      Just _  -> die "DuplicateAgentId"  -- TODO: better error handling policy+      Nothing -> do+        node <- processNode <$> ask+        pid <- liftIO $ mxNew (localEventBus node) $ start+        register name pid+        return pid+  where+    start (sendTChan, recvTChan) = do+      (sp, rp) <- newChan+      nsend Table.mxTableCoordinator (MxAgentStart sp mxId)+      tablePid <- receiveWait [ matchChan rp (\(p :: ProcessId) -> return p) ]+      let nState = MxAgentState mxId sendTChan tablePid initState+      runAgent dtor handlers InputChan recvTChan nState++    runAgent :: MxAgent s ()+             -> [MxSink s]+             -> ChannelSelector+             -> TChan Message+             -> MxAgentState s+             -> Process ()+    runAgent eh hs cs c s =+      runAgentWithFinalizer eh hs cs c s+        `onException` runAgentFinalizer eh s++    runAgentWithFinalizer :: MxAgent s ()+                          -> [MxSink s]+                          -> ChannelSelector+                          -> TChan Message+                          -> MxAgentState s+                          -> Process ()+    runAgentWithFinalizer eh' hs' cs' c' s' = do+      msg <- getNextInput cs' c'+      (action, state) <- runPipeline msg s' $ pipeline hs'+      case action of+        MxAgentReady               -> runAgent eh' hs' InputChan c' state+        MxAgentPrioritise priority -> runAgent eh' hs' priority  c' state+        MxAgentDeactivate _        -> runAgentFinalizer eh' state+        MxAgentSkip                -> error "IllegalState"+--      MxAgentBecome h'           -> runAgent h' c state++    getNextInput sel chan =+      let stmRead = atomically . readTChan+          matches =+            case sel of+              Mailbox   -> [ matchAny return+                           , matchSTM (readTChan chan) return]+              InputChan -> [ matchSTM (readTChan chan) return+                           , matchAny return]+      in receiveWait matches++    runAgentFinalizer :: MxAgent s () -> MxAgentState s -> Process ()+    runAgentFinalizer f s = ST.runStateT (unAgent f) s >>= return . fst++    pipeline :: forall s . [MxSink s] -> MxPipeline s+    pipeline []           = MxStop+    pipeline (sink:sinks) = MxPipeline sink (pipeline sinks)++    runPipeline :: forall s .+                   Message+                -> MxAgentState s+                -> MxPipeline s+                -> Process (MxAction, MxAgentState s)+    runPipeline _   state MxStop         = return (MxAgentReady, state)+    runPipeline msg state MxPipeline{..} = do+      let act = current msg+      (pass, state') <- ST.runStateT (unAgent act) state+      case pass of+        Nothing     -> runPipeline msg state next+        Just result -> return (result, state')+
+ src/Control/Distributed/Process/Management/Internal/Agent.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE RecordWildCards #-}++module Control.Distributed.Process.Management.Internal.Agent where++import Control.Applicative ((<$>))+import Control.Concurrent (forkIO)+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TChan+  ( TChan+  , newBroadcastTChanIO+  , readTChan+  , writeTChan+  , dupTChan+  )+import Control.Distributed.Process.Internal.Primitives+  ( receiveWait+  , matchAny+  , die+  , catches+  , Handler(..)+  )+import Control.Distributed.Process.Internal.CQueue+  ( enqueueSTM+  , CQueue+  )+import Control.Distributed.Process.Management.Internal.Types+  ( Fork+  )+import Control.Distributed.Process.Management.Internal.Trace.Tracer+  ( traceController+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , Message+  , Tracer(..)+  , LocalProcess(..)+  , ProcessId+  , forever'+  )+import Control.Exception (AsyncException(ThreadKilled), SomeException)+import Control.Monad (void)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask)+import GHC.Weak (Weak, deRefWeak)++--------------------------------------------------------------------------------+-- Agent Controller Implementation                                            --+--------------------------------------------------------------------------------++-- | A triple containing a configured tracer, weak pointer to the+-- agent controller's mailbox (CQueue) and an expression used to+-- instantiate new agents on the current node.+type AgentConfig =+  (Tracer, Weak (CQueue Message),+   (((TChan Message, TChan Message) -> Process ()) -> IO ProcessId))++-- | Starts a management agent for the current node. The agent process+-- must not crash or be killed, so we generally avoid publishing its+-- @ProcessId@ where possible.+--+-- Our process is also responsible for forwarding messages to the trace+-- controller, since having two /special processes/ handled via the+-- @LocalNode@ would be inelegant. We forward messages directly to the+-- trace controller's message queue, just as the @MxEventBus@ that's+-- set up on the @LocalNode@ forwards messages directly to us. This+-- optimises the code path for tracing and avoids overloading the node+-- node controller's internal control plane with additional routing, at the+-- cost of a little more complexity and two cases where we break+-- encapsulation.+--+mxAgentController :: Fork+                  -> MVar AgentConfig+                  -> Process ()+mxAgentController forkProcess mv = do+    trc <- liftIO $ startTracing forkProcess+    sigbus <- liftIO $ newBroadcastTChanIO+    liftIO $ startDeadLetterQueue sigbus+    weakQueue <- processWeakQ <$> ask+    liftIO $ putMVar mv (trc, weakQueue, mxStartAgent forkProcess sigbus)+    go sigbus trc+  where+    go bus tracer = forever' $ do+      void $ receiveWait [+          -- This is exactly what it appears to be: a "catch all" handler.+          -- Since mxNotify can potentially pass an unevaluated thunk to+          -- our mailbox, the dequeue (i.e., matchMessage) can fail and+          -- crash this process, which we DO NOT want. Alternatively,+          -- we handle IO exceptions here explicitly, since we don't want+          -- this process to ever crash, and the assumption we therefore+          -- make is thus:+          --+          -- 1. only ThreadKilled can tell this process to terminate+          -- 2. all other exceptions are invalid and should be ignored+          --+          -- The outcome of course, is that /bad/ calls to mxNotify+          -- (e.g., passing unevaluated thunks that will crash when+          -- they're eventually forced) are thus silently ignored.+          --+          matchAny (liftIO . broadcast bus tracer)+        ] `catches` [Handler (\ThreadKilled -> die "Killed"),+                     Handler (\(_ :: SomeException) -> return ())]++    broadcast :: TChan Message -> Tracer -> Message -> IO ()+    broadcast ch tr msg = do+      tmQueue <- tracerQueue tr+      atomicBroadcast ch tmQueue msg++    tracerQueue :: Tracer -> IO (Maybe (CQueue Message))+    tracerQueue (Tracer _ wQ) = deRefWeak wQ++    atomicBroadcast :: TChan Message+                    -> Maybe (CQueue Message)+                    -> Message -> IO ()+    atomicBroadcast ch Nothing  msg = liftIO $ atomically $ writeTChan ch msg+    atomicBroadcast ch (Just q) msg = do+      -- liftIO $ putStrLn $ "broadcasting " ++ (show msg)+      liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg++-- | Forks a new process in which an mxAgent is run.+--+mxStartAgent :: Fork+             -> TChan Message+             -> ((TChan Message, TChan Message) -> Process ())+             -> IO ProcessId+mxStartAgent fork chan handler = do+  chan' <- atomically (dupTChan chan)+  let proc = handler (chan, chan')+  fork proc++-- | Start the tracer controller.+--+startTracing :: Fork -> IO Tracer+startTracing forkProcess = do+  mv  <- newEmptyMVar+  pid <- forkProcess $ traceController mv+  wQ  <- liftIO $ takeMVar mv+  return $ Tracer pid wQ++-- | Start a dead letter (agent) queue.+--+-- If no agents are registered on the system, the management+-- event bus will fill up and its data won't be GC'ed until someone+-- comes along and reads from the broadcast channel (via dupTChan+-- of course). This is effectively a leak, so to mitigate it, we+-- start a /dead letter queue/ that drains the event bus continuously,+-- thus ensuring if there are no other consumers that we won't use+-- up heap space unnecessarily.+--+startDeadLetterQueue :: TChan Message+                     -> IO ()+startDeadLetterQueue sigbus = do+  chan' <- atomically (dupTChan sigbus)+  void $ forkIO $ forever' $ do+    void $ atomically $ readTChan chan'
+ src/Control/Distributed/Process/Management/Internal/Bus.hs view
@@ -0,0 +1,21 @@+-- | Interface to the management event bus.+module Control.Distributed.Process.Management.Internal.Bus+  ( publishEvent+  ) where++import Control.Distributed.Process.Internal.CQueue+  ( enqueue+  )+import Control.Distributed.Process.Internal.Types+  ( MxEventBus(..)+  , Message+  )+import Data.Foldable (forM_)+import System.Mem.Weak (deRefWeak)++publishEvent :: MxEventBus -> Message -> IO ()+publishEvent MxEventBusInitialising _     = return ()+publishEvent (MxEventBus _ _ wqRef _) msg =  do+  mQueue <- deRefWeak wqRef+  forM_ mQueue $ \queue -> enqueue queue msg+
+ src/Control/Distributed/Process/Management/Internal/Table.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE RankNTypes  #-}+{-# LANGUAGE DeriveGeneric   #-}+{-# LANGUAGE RecordWildCards #-}++module Control.Distributed.Process.Management.Internal.Table+  ( MxTableRequest(..)+  , MxTableId(..)+  , mxTableCoordinator+  , startTableCoordinator+  , delete+  , purge+  , clear+  , set+  , get+  , fetch+  ) where++import Control.Distributed.Process.Internal.Primitives+  ( receiveWait+  , receiveChan+  , match+  , matchAny+  , matchIf+  , matchChan+  , send+  , nsend+  , sendChan+  , getSelfPid+  , link+  , monitor+  , unwrapMessage+  , newChan+  , withMonitor+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , ProcessId+  , ProcessMonitorNotification(..)+  , SendPort+  , ReceivePort+  , Message+  , unsafeCreateUnencodedMessage+  )+import Control.Distributed.Process.Management.Internal.Types+  ( MxTableId(..)+  , MxAgentId(..)+  , MxAgentStart(..)+  , Fork)+import Control.Distributed.Process.Serializable (Serializable)+import Control.Monad.IO.Class (liftIO)+import Data.Accessor (Accessor, accessor, (^=), (^:))+import Data.Binary (Binary)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Typeable (Typeable)++import GHC.Generics++-- An extremely lightweight shared Map implementation, for use+-- by /management agents/ and their cohorts. Each agent is assigned+-- a table, into which any serializable @Message@ can be inserted.+-- Data are inserted, removed and searched for via their key, which+-- is a string. Tables can be purged, values can be set, fetched or+-- cleared/removed.+--++data MxTableRequest =+    Delete+  | Purge+  | Clear !String+  | Set !String !Message+  | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]+  deriving (Typeable, Generic)+instance Binary MxTableRequest where++data MxTableState = MxTableState { _name    :: !String+                                 , _entries :: !(Map String Message)+                                 }++type MxTables = Map MxAgentId ProcessId++mxTableCoordinator :: String+mxTableCoordinator = "mx.table.coordinator"++delete :: MxTableId -> Process ()+delete = sendReq Delete++purge :: MxTableId -> Process ()+purge = sendReq Purge++clear :: String -> MxTableId -> Process ()+clear k = sendReq (Clear k)++set :: String -> Message -> MxTableId -> Process ()+set k v = sendReq (Set k v)++fetch :: forall a. (Serializable a)+      => MxTableId+      -> String+      -> Process (Maybe a)+fetch (MxForPid pid)      key = get pid key+fetch mxId@(MxForAgent _) key = do+  (sp, rp) <- newChan :: Process (SendPort (Maybe Message),+                                  ReceivePort (Maybe Message))+  sendReq (Get key sp) mxId+  receiveChan rp >>= maybe (return Nothing)+                           (unwrapMessage :: Message -> Process (Maybe a))++-- [note: un-typed send port]+-- Here, fetch uses a typed channel over a raw Message to obtain+-- its result, so type checking is deferred until receipt and will+-- be handled in the caller's thread. This is necessary because+-- the server portion of the code knows nothing about the types+-- involved, nor should it, since these tables can be used to+-- store arbitrary serializable data.++get :: forall a. (Serializable a)+      => ProcessId+      -> String+      -> Process (Maybe a)+get pid key = do+  safeFetch pid key >>= maybe (return Nothing)+                              (unwrapMessage :: Message -> Process (Maybe a))++safeFetch :: ProcessId -> String -> Process (Maybe Message)+safeFetch pid key = do+  (sp, rp) <- newChan+  send pid $ Get key sp+  withMonitor pid $ do+    receiveWait [+        matchChan rp return+      , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)+                (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))+      ]++sendReq :: MxTableRequest -> MxTableId -> Process ()+sendReq req tid = (resolve tid) req++resolve :: Serializable a => MxTableId -> (a -> Process ())+resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)+resolve (MxForPid   pid)   = \msg -> send pid msg++startTableCoordinator :: Fork -> Process ()+startTableCoordinator fork = run Map.empty+  where+    run :: MxTables -> Process ()+    run tables =+      receiveWait [+          -- note that this state change can race with MxAgentStart requests+          match (\(ProcessMonitorNotification _ pid _) -> do+                    return $ Map.filter (/= pid) tables)+        , match (\(MxAgentStart ch agent) -> do+                    lookupAgent tables agent >>= \(p, t) -> do+                    sendChan ch p >> return t)+        , match (\req@(agent, tReq :: MxTableRequest) -> do+                    case tReq of+                      Get k sp -> do+                        lookupAgent tables agent >>= \(p, t) -> do+                            safeFetch p k >>= sendChan sp >> return t+                      _ -> do+                        handleRequest tables req)+        , matchAny (\_ -> return tables) -- unrecognised messages are dropped+        ] >>= run++    handleRequest :: MxTables+                  -> (MxAgentId, MxTableRequest)+                  -> Process MxTables+    handleRequest tables' (agent, req) = do+      lookupAgent tables' agent >>= \(p, t) -> send p req >> return t++    lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)+    lookupAgent tables' agentId' = do+      case Map.lookup agentId' tables' of+        Nothing -> launchNew agentId' tables'+        Just p  -> return (p, tables')++    launchNew :: MxAgentId+              -> MxTables+              -> Process (ProcessId, MxTables)+    launchNew mxId tblMap = do+      let initState = MxTableState { _name = (agentId mxId)+                                   , _entries = Map.empty+                                   }+      (pid, _) <- spawnSup $ tableHandler initState+      return $ (pid, mxId `seq` pid `seq` Map.insert mxId pid tblMap)++    spawnSup proc = do+      us   <- getSelfPid+      -- we need to use that passed in "fork", in order to+      -- break an import cycle with Node.hs courtesy of the+      -- management agent, API and tracing modules+      them <- liftIO $ fork $ link us >> proc+      ref  <- monitor them+      return (them, ref)++tableHandler :: MxTableState -> Process ()+tableHandler state = do+  ns <- receiveWait [+      match (handleTableRequest state)+    , matchAny (\_ -> return (Just state))+    ]+  case ns of+    Nothing -> return ()+    Just s' -> tableHandler s'+  where+    handleTableRequest _  Delete    = return Nothing+    handleTableRequest st Purge     = return $ Just $ (entries ^= Map.empty) $ st+    handleTableRequest st (Clear k) = return $ Just $ (entries ^: (k `seq` Map.delete k)) $ st+    handleTableRequest st (Set k v) = return $ Just $ (entries ^: (k `seq` v `seq` Map.insert k v)) st+    handleTableRequest st (Get k c) = getEntry k c st >> return (Just st)++getEntry :: String+         -> SendPort (Maybe Message)+         -> MxTableState+         -> Process ()+getEntry k m MxTableState{..} = do+  sendChan m =<< return (Map.lookup k _entries)++entries :: Accessor MxTableState (Map String Message)+entries = accessor _entries (\ls st -> st { _entries = ls })
+ src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE TypeSynonymInstances  #-}+-- | Keeps the tracing API calls separate from the Tracer implementation,+-- which allows us to avoid a nasty import cycle between tracing and+-- the messaging primitives that rely on it, and also between the node+-- controller (which requires access to the tracing related elements of+-- our RemoteTable) and the Debug module, which requires @forkProcess@.+-- This module is also used by the management agent, which relies on the+-- tracing infrastructure's messaging fabric.+module Control.Distributed.Process.Management.Internal.Trace.Primitives+  ( -- * Sending Trace Data+    traceLog+  , traceLogFmt+  , traceMessage+    -- * Configuring A Tracer+  , defaultTraceFlags+  , enableTrace+  , enableTraceAsync+  , disableTrace+  , disableTraceAsync+  , getTraceFlags+  , setTraceFlags+  , setTraceFlagsAsync+  , traceOnly+  , traceOn+  , traceOff+  , withLocalTracer+  , withRegisteredTracer+  ) where++import Control.Applicative ((<$>))+import Control.Distributed.Process.Internal.Primitives+  ( whereis+  , newChan+  , receiveChan+  )+import Control.Distributed.Process.Management.Internal.Trace.Types+  ( TraceArg(..)+  , TraceFlags(..)+  , TraceOk(..)+  , TraceSubject(..)+  , defaultTraceFlags+  )+import qualified Control.Distributed.Process.Management.Internal.Trace.Types as Tracer+  ( traceLog+  , traceLogFmt+  , traceMessage+  , enableTrace+  , enableTraceSync+  , disableTrace+  , disableTraceSync+  , setTraceFlags+  , setTraceFlagsSync+  , getTraceFlags+  , getCurrentTraceClient+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , ProcessId+  , LocalProcess(..)+  , LocalNode(localEventBus)+  , SendPort+  , MxEventBus(..)+  )+import Control.Distributed.Process.Serializable+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask)++import qualified Data.Set as Set (fromList)++--------------------------------------------------------------------------------+-- Main API                                                                   --+--------------------------------------------------------------------------------++-- | Converts a list of identifiers (that can be+-- mapped to process ids), to a 'TraceSubject'.+class Traceable a where+  uod :: [a] -> TraceSubject++instance Traceable ProcessId where+  uod = TraceProcs . Set.fromList++instance Traceable String where+  uod = TraceNames . Set.fromList++-- | Turn tracing for for a subset of trace targets.+traceOnly :: Traceable a => [a] -> Maybe TraceSubject+traceOnly = Just . uod++-- | Trace all targets.+traceOn :: Maybe TraceSubject+traceOn = Just TraceAll++-- | Trace no targets.+traceOff :: Maybe TraceSubject+traceOff = Nothing++-- | Enable tracing to the supplied process.+enableTraceAsync :: ProcessId -> Process ()+enableTraceAsync pid = withLocalTracer $ \t -> liftIO $ Tracer.enableTrace t pid++-- TODO: refactor _Sync versions of trace configuration functions...++-- | Enable tracing to the supplied process and wait for a @TraceOk@+-- response from the trace coordinator process.+enableTrace :: ProcessId -> Process ()+enableTrace pid =+  withLocalTracerSync $ \t sp -> Tracer.enableTraceSync t sp pid++-- | Disable the currently configured trace.+disableTraceAsync :: Process ()+disableTraceAsync = withLocalTracer $ \t -> liftIO $ Tracer.disableTrace t++-- | Disable the currently configured trace and wait for a @TraceOk@+-- response from the trace coordinator process.+disableTrace :: Process ()+disableTrace =+  withLocalTracerSync $ \t sp -> Tracer.disableTraceSync t sp++getTraceFlags :: Process TraceFlags+getTraceFlags = do+  (sp, rp) <- newChan+  withLocalTracer $ \t -> liftIO $ Tracer.getTraceFlags t sp+  receiveChan rp++-- | Set the given flags for the current tracer.+setTraceFlagsAsync :: TraceFlags -> Process ()+setTraceFlagsAsync f = withLocalTracer $ \t -> liftIO $ Tracer.setTraceFlags t f++-- | Set the given flags for the current tracer and wait for a @TraceOk@+-- response from the trace coordinator process.+setTraceFlags :: TraceFlags -> Process ()+setTraceFlags f =+  withLocalTracerSync $ \t sp -> Tracer.setTraceFlagsSync t sp f++-- | Send a log message to the internal tracing facility. If tracing is+-- enabled, this will create a custom trace log event.+--+traceLog :: String -> Process ()+traceLog s = withLocalTracer $ \t -> liftIO $ Tracer.traceLog t s++-- | Send a log message to the internal tracing facility, using the given+-- list of printable 'TraceArg's interspersed with the preceding delimiter.+--+traceLogFmt :: String -> [TraceArg] -> Process ()+traceLogFmt d ls = withLocalTracer $ \t -> liftIO $ Tracer.traceLogFmt t d ls++-- | Send an arbitrary 'Message' to the tracer process.+traceMessage :: Serializable m => m -> Process ()+traceMessage msg = withLocalTracer $ \t -> liftIO $ Tracer.traceMessage t msg++withLocalTracer :: (MxEventBus -> Process ()) -> Process ()+withLocalTracer act = do+  node <- processNode <$> ask+  act (localEventBus node)++withLocalTracerSync :: (MxEventBus -> SendPort TraceOk -> IO ()) -> Process ()+withLocalTracerSync act = do+  (sp, rp) <- newChan+  withLocalTracer $ \t -> liftIO $ (act t sp)+  TraceOk <- receiveChan rp+  return ()++withRegisteredTracer :: (ProcessId -> Process a) -> Process a+withRegisteredTracer act = do+  (sp, rp) <- newChan+  withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp+  currentTracer <- receiveChan rp+  case currentTracer of+    Nothing  -> do { (Just p') <- whereis "tracer.initial"; act p' }+    (Just p) -> act p+
+ src/Control/Distributed/Process/Management/Internal/Trace/Remote.hs view
@@ -0,0 +1,61 @@+module Control.Distributed.Process.Management.Internal.Trace.Remote+  ( -- * Configuring A Remote Tracer+    setTraceFlagsRemote+  , startTraceRelay+    -- * Remote Table+  , remoteTable+  ) where++import Control.Distributed.Process.Internal.Closure.BuiltIn+  ( cpEnableTraceRemote+  )+import Control.Distributed.Process.Internal.Primitives+  ( getSelfPid+  , relay+  , nsendRemote+  )+import Control.Distributed.Process.Management.Internal.Trace.Types+  ( TraceFlags(..)+  , TraceOk(..)+  )+import Control.Distributed.Process.Management.Internal.Trace.Primitives+  ( withRegisteredTracer+  , enableTrace+  )+import Control.Distributed.Process.Internal.Spawn+  ( spawn+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , ProcessId+  , SendPort+  , NodeId+  )+import Control.Distributed.Static+  ( RemoteTable+  , registerStatic+  )+import Data.Rank1Dynamic (toDynamic)++-- | Remote Table.+remoteTable :: RemoteTable -> RemoteTable+remoteTable = registerStatic "$enableTraceRemote" (toDynamic enableTraceRemote)++enableTraceRemote :: ProcessId -> Process ()+enableTraceRemote pid =+  getSelfPid >>= enableTrace >> relay pid++-- | Starts a /trace relay/ process on the remote node, which forwards all trace+-- events to the registered tracer on /this/ (the calling process') node.+startTraceRelay :: NodeId -> Process ProcessId+startTraceRelay nodeId = do+  withRegisteredTracer $ \pid ->+    spawn nodeId $ cpEnableTraceRemote pid++-- | Set the given flags for a remote node (asynchronous).+setTraceFlagsRemote :: TraceFlags -> NodeId -> Process ()+setTraceFlagsRemote flags node = do+  nsendRemote node+              "trace.controller"+              ((Nothing :: Maybe (SendPort TraceOk)), flags)+
+ src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hs view
@@ -0,0 +1,347 @@+{-# LANGUAGE CPP  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+-- | Tracing/Debugging support - Trace Implementation+module Control.Distributed.Process.Management.Internal.Trace.Tracer+  ( -- * API for the Management Agent+    traceController+    -- * Built in tracers+  , defaultTracer+  , systemLoggerTracer+  , logfileTracer+  , eventLogTracer+  ) where++import Control.Applicative ((<$>))+import Control.Concurrent.Chan (writeChan)+import Control.Concurrent.MVar+  ( MVar+  , putMVar+  )+import Control.Distributed.Process.Internal.CQueue+  ( CQueue+  )+import Control.Distributed.Process.Internal.Primitives+  ( catch+  , finally+  , die+  , receiveWait+  , forward+  , sendChan+  , match+  , matchAny+  , matchIf+  , handleMessage+  , matchUnknown+  )+import Control.Distributed.Process.Management.Internal.Types+  ( MxEvent(..)+  , Addressable(..)+  )+import Control.Distributed.Process.Management.Internal.Trace.Types+  ( SetTrace(..)+  , TraceSubject(..)+  , TraceFlags(..)+  , TraceOk(..)+  , defaultTraceFlags+  )+import Control.Distributed.Process.Management.Internal.Trace.Primitives+  ( traceOn )+import Control.Distributed.Process.Internal.Types+  ( LocalNode(..)+  , NCMsg(..)+  , ProcessId+  , Process+  , LocalProcess(..)+  , Identifier(..)+  , ProcessSignal(NamedSend)+  , Message+  , SendPort+  , forever'+  , nullProcessId+  , createUnencodedMessage+  )++import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask)++import Data.Set (Set)+import qualified Data.Set as Set+import Data.Map (Map)+import qualified Data.Map as Map++import Data.Maybe (fromMaybe)+import Data.Time.Clock (getCurrentTime)+import Data.Time.Format (formatTime)+import Debug.Trace (traceEventIO)++#if ! MIN_VERSION_base(4,6,0)+import Prelude hiding (catch)+#endif++import System.Environment (getEnv)+import System.IO+  ( Handle+  , IOMode(AppendMode)+  , BufferMode(..)+  , openFile+  , hClose+  , hPutStrLn+  , hSetBuffering+  )+import System.Locale (defaultTimeLocale)+import System.Mem.Weak+  ( Weak+  )++data TracerState =+  TracerST+  {+    client   :: !(Maybe ProcessId)+  , flags    :: !TraceFlags+  , regNames :: !(Map ProcessId (Set String))+  }++--------------------------------------------------------------------------------+-- Trace Handlers                                                             --+--------------------------------------------------------------------------------++defaultTracer :: Process ()+defaultTracer =+  catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)+        (\(_ :: IOError) -> defaultTracerAux)++defaultTracerAux :: Process ()+defaultTracerAux =+  catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> systemLoggerTracer)+        (\(_ :: IOError) -> defaultEventLogTracer)++-- TODO: it would be /nice/ if we had some way of checking the runtime+-- options to see if +RTS -v (or similar) has been given...+defaultEventLogTracer :: Process ()+defaultEventLogTracer =+  catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_EVENTLOG" >> eventLogTracer)+        (\(_ :: IOError) -> nullTracer)++checkEnv :: String -> Process String+checkEnv s = liftIO $ getEnv s++-- This trace client is (intentionally) a noop - it simply provides+-- an intial client for the trace controller to talk to, until some+-- other (hopefully more useful) client is installed over the top of+-- it. This is the default trace client.+nullTracer :: Process ()+nullTracer =+  forever' $ receiveWait [ matchUnknown (return ()) ]++systemLoggerTracer :: Process ()+systemLoggerTracer = do+  node <- processNode <$> ask+  let tr = sendTraceLog node+  forever' $ receiveWait [ matchAny (\m -> handleMessage m tr) ]+  where+    sendTraceLog :: LocalNode -> MxEvent -> Process ()+    sendTraceLog node ev = do+      now <- liftIO $ getCurrentTime+      msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)+      emptyPid <- return $ (nullProcessId (localNodeId node))+      traceMsg <- return $ NCMsg {+                             ctrlMsgSender = ProcessIdentifier (emptyPid)+                           , ctrlMsgSignal = (NamedSend "logger"+                                                 (createUnencodedMessage msg))+                           }+      liftIO $ writeChan (localCtrlChan node) traceMsg++    buildTxt :: MxEvent -> String+    buildTxt (MxLog msg) = msg+    buildTxt ev          = show ev++eventLogTracer :: Process ()+eventLogTracer =+  -- NB: when the GHC event log supports tracing arbitrary (ish) data, we will+  -- almost certainly use *that* facility independently of whether or not there+  -- is a tracer process installed. This is just a stop gap until then.+  forever' $ receiveWait [ matchAny (\m -> handleMessage m writeTrace) ]+  where+    writeTrace :: MxEvent -> Process ()+    writeTrace ev = liftIO $ traceEventIO (show ev)++logfileTracer :: FilePath -> Process ()+logfileTracer p = do+  -- TODO: error handling if the handle cannot be opened+  h <- liftIO $ openFile p AppendMode+  liftIO $ hSetBuffering h LineBuffering+  logger h `finally` (liftIO $ hClose h)+  where+    logger :: Handle -> Process ()+    logger h' = forever' $ do+      receiveWait [+          matchIf (\ev -> case ev of+                            MxTraceDisable      -> True+                            (MxTraceTakeover _) -> True+                            _                   -> False)+                  (\_ -> (liftIO $ hClose h') >> die "trace stopped")+        , matchAny (\ev -> handleMessage ev (writeTrace h'))+        ]++    writeTrace :: Handle -> MxEvent -> Process ()+    writeTrace h ev = do+      liftIO $ do+        now <- getCurrentTime+        hPutStrLn h $ (formatTime defaultTimeLocale "%c - " now) ++ (show ev)++--------------------------------------------------------------------------------+-- Tracer Implementation                                                      --+--------------------------------------------------------------------------------++traceController :: MVar ((Weak (CQueue Message))) -> Process ()+traceController mv = do+    -- See the documentation for mxAgentController for a+    -- commentary that explains this breach of encapsulation+    weakQueue <- processWeakQ <$> ask+    liftIO $ putMVar mv weakQueue+    initState <- initialState+    traceLoop initState { client = Nothing }+  where+    traceLoop :: TracerState -> Process ()+    traceLoop st = do+      let client' = client st+      -- Trace events are forwarded to the enabled trace target.+      -- At some point in the future, we're going to start writing these custom+      -- events to the ghc eventlog, at which point this design might change.+      st' <- receiveWait [+          match (\(setResp, set :: SetTrace) -> do+                  -- We consider at most one trace client, which is a process.+                  -- Tracking multiple clients represents too high an overhead,+                  -- so we leave that kind of thing to our consumers (e.g., the+                  -- high level Debug client module) to figure out.+                  case set of+                    (TraceEnable pid) -> do+                      -- notify the previous tracer it has been replaced+                      sendTraceMsg client' (createUnencodedMessage (MxTraceTakeover pid))+                      sendOk setResp+                      return st { client = (Just pid) }+                    TraceDisable -> do+                      sendTraceMsg client' (createUnencodedMessage MxTraceDisable)+                      sendOk setResp+                      return st { client = Nothing })+        , match (\(confResp, flags') ->+                  sendOk confResp >> applyTraceFlags flags' st)+        , match (\chGetFlags -> sendChan chGetFlags (flags st) >> return st)+        , match (\chGetCurrent -> sendChan chGetCurrent (client st) >> return st)+          -- we dequeue incoming events even if we don't process them+        , matchAny (\ev ->+            handleMessage ev (handleTrace st ev) >>= return . fromMaybe st)+        ]+      traceLoop st'++    sendOk :: Maybe (SendPort TraceOk) -> Process ()+    sendOk Nothing   = return ()+    sendOk (Just sp) = sendChan sp TraceOk++    initialState :: Process TracerState+    initialState = do+      flags' <- checkEnvFlags+      return $ TracerST { client   = Nothing+                        , flags    = flags'+                        , regNames = Map.empty+                        }++    checkEnvFlags :: Process TraceFlags+    checkEnvFlags =+      catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" >>= return . parseFlags)+            (\(_ :: IOError) -> return defaultTraceFlags)++    parseFlags :: String -> TraceFlags+    parseFlags s = parseFlags' s defaultTraceFlags+      where parseFlags' :: String -> TraceFlags -> TraceFlags+            parseFlags' [] parsedFlags = parsedFlags+            parseFlags' (x:xs) parsedFlags+              | x == 'p'  = parseFlags' xs parsedFlags { traceSpawned = traceOn }+              | x == 'n'  = parseFlags' xs parsedFlags { traceRegistered = traceOn }+              | x == 'u'  = parseFlags' xs parsedFlags { traceUnregistered = traceOn }+              | x == 'd'  = parseFlags' xs parsedFlags { traceDied = traceOn }+              | x == 's'  = parseFlags' xs parsedFlags { traceSend = traceOn }+              | x == 'r'  = parseFlags' xs parsedFlags { traceRecv = traceOn }+              | x == 'l'  = parseFlags' xs parsedFlags { traceNodes = True }+              | otherwise = parseFlags' xs parsedFlags++applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState+applyTraceFlags flags' state = return state { flags = flags' }++handleTrace :: TracerState -> Message -> MxEvent -> Process TracerState+handleTrace st msg ev@(MxRegistered p n) =+  let regNames' =+        Map.insertWith (\_ ns -> Set.insert n ns) p+                       (Set.singleton n)+                       (regNames st)+  in do+    traceEv ev msg (traceRegistered (flags st)) st+    return st { regNames = regNames' }+handleTrace st msg ev@(MxUnRegistered p n) =+  let f ns = case ns of+               Nothing  -> Nothing+               Just ns' -> Just (Set.delete n ns')+      regNames' = Map.alter f p (regNames st)+  in do+    traceEv ev msg (traceUnregistered (flags st)) st+    return st { regNames = regNames' }+handleTrace st msg ev@(MxSpawned  _)   = do+  traceEv ev msg (traceSpawned (flags st)) st >> return st+handleTrace st msg ev@(MxProcessDied _ _)     = do+  traceEv ev msg (traceDied (flags st)) st >> return st+handleTrace st msg ev@(MxSent _ _ _)   =+  traceEv ev msg (traceSend (flags st)) st >> return st+handleTrace st msg ev@(MxReceived _ _) =+  traceEv ev msg (traceRecv (flags st)) st >> return st+handleTrace st msg ev = do+  case ev of+    (MxNodeDied _ _) ->+      case (traceNodes (flags st)) of+        True  -> sendTrace st ev msg+        False -> return ()+    (MxUser _) -> sendTrace st ev msg+    (MxLog _)  -> sendTrace st ev msg+    _ ->+      case (traceConnections (flags st)) of+        True  -> sendTrace st ev msg+        False -> return ()+  return st++traceEv :: MxEvent+        -> Message+        -> Maybe TraceSubject+        -> TracerState+        -> Process ()+traceEv _  _   Nothing                  _  = return ()+traceEv ev msg (Just TraceAll)          st = sendTrace st ev msg+traceEv ev msg (Just (TraceProcs pids)) st = do+  node <- processNode <$> ask+  let p = case resolveToPid ev of+            Nothing  -> (nullProcessId (localNodeId node))+            Just pid -> pid+  case (Set.member p pids) of+    True  -> sendTrace st ev msg+    False -> return ()+traceEv ev msg (Just (TraceNames names)) st = do+  -- if we have recorded regnames for p, then we forward the trace iif+  -- there are overlapping trace targets+  node <- processNode <$> ask+  let p = case resolveToPid ev of+            Nothing  -> (nullProcessId (localNodeId node))+            Just pid -> pid+  case (Map.lookup p (regNames st)) of+    Nothing -> return ()+    Just ns -> if (Set.null (Set.intersection ns names))+                 then return ()+                 else sendTrace st ev msg++sendTrace :: TracerState -> MxEvent -> Message -> Process ()+sendTrace st ev msg = do+  let c = client st+  if c == (resolveToPid ev)  -- we do not send the tracer events about itself...+     then return ()+     else sendTraceMsg c msg++sendTraceMsg :: Maybe ProcessId -> Message -> Process ()+sendTraceMsg Nothing  _   = return ()+sendTraceMsg (Just p) msg = (flip forward) p msg
+ src/Control/Distributed/Process/Management/Internal/Trace/Types.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE TypeSynonymInstances  #-}+{-# LANGUAGE GADTs  #-}+{-# LANGUAGE DeriveGeneric  #-}++-- | Tracing/Debugging support - Types+module Control.Distributed.Process.Management.Internal.Trace.Types+  ( SetTrace(..)+  , TraceSubject(..)+  , TraceFlags(..)+  , TraceArg(..)+  , TraceOk(..)+  , traceLog+  , traceLogFmt+  , traceEvent+  , traceMessage+  , defaultTraceFlags+  , enableTrace+  , enableTraceSync+  , disableTrace+  , disableTraceSync+  , getTraceFlags+  , setTraceFlags+  , setTraceFlagsSync+  , getCurrentTraceClient+  ) where++import Control.Distributed.Process.Internal.Types+  ( MxEventBus(..)+  , ProcessId+  , SendPort+  , unsafeCreateUnencodedMessage+  )+import Control.Distributed.Process.Management.Internal.Bus+  ( publishEvent+  )+import Control.Distributed.Process.Management.Internal.Types+  ( MxEvent(..)+  )+import Control.Distributed.Process.Serializable+import Data.Binary+import Data.List (intersperse)+import Data.Set (Set)+import qualified Data.Set as Set (fromList)+import Data.Typeable+import GHC.Generics++--------------------------------------------------------------------------------+-- Types                                                                      --+--------------------------------------------------------------------------------++data SetTrace = TraceEnable !ProcessId | TraceDisable+  deriving (Typeable, Generic, Eq, Show)+instance Binary SetTrace where++-- | Defines which processes will be traced by a given 'TraceFlag',+-- either by name, or @ProcessId@. Choosing @TraceAll@ is /by far/+-- the most efficient approach, as the tracer process therefore+-- avoids deciding whether or not a trace event is viable.+--+data TraceSubject =+    TraceAll                     -- enable tracing for all running processes+  | TraceProcs !(Set ProcessId)  -- enable tracing for a set of processes+  | TraceNames !(Set String)     -- enable tracing for a set of named/registered processes+  deriving (Typeable, Generic, Show)+instance Binary TraceSubject where++-- | Defines /what/ will be traced. Flags that control tracing of+-- @Process@ events, take a 'TraceSubject' controlling which processes+-- should generate trace events in the target process.+data TraceFlags = TraceFlags {+    traceSpawned      :: !(Maybe TraceSubject) -- filter process spawned tracing+  , traceDied         :: !(Maybe TraceSubject) -- filter process died tracing+  , traceRegistered   :: !(Maybe TraceSubject) -- filter process registration tracing+  , traceUnregistered :: !(Maybe TraceSubject) -- filter process un-registration+  , traceSend         :: !(Maybe TraceSubject) -- filter process/message tracing by sender+  , traceRecv         :: !(Maybe TraceSubject) -- filter process/message tracing by receiver+  , traceNodes        :: !Bool                 -- enable node status trace events+  , traceConnections  :: !Bool                 -- enable connection status trace events+  } deriving (Typeable, Generic, Show)+instance Binary TraceFlags where++defaultTraceFlags :: TraceFlags+defaultTraceFlags =+  TraceFlags {+    traceSpawned      = Nothing+  , traceDied         = Nothing+  , traceRegistered   = Nothing+  , traceUnregistered = Nothing+  , traceSend         = Nothing+  , traceRecv         = Nothing+  , traceNodes        = False+  , traceConnections  = False+  }++data TraceArg =+    TraceStr String+  | forall a. (Show a) => Trace a++-- | A generic 'ok' response from the trace coordinator.+data TraceOk = TraceOk+  deriving (Typeable, Generic)+instance Binary TraceOk where++--------------------------------------------------------------------------------+-- Internal/Common API                                                        --+--------------------------------------------------------------------------------++traceLog :: MxEventBus -> String -> IO ()+traceLog tr s = publishEvent tr (unsafeCreateUnencodedMessage $ MxLog s)++traceLogFmt :: MxEventBus+            -> String+            -> [TraceArg]+            -> IO ()+traceLogFmt t d ls =+  traceLog t $ concat (intersperse d (map toS ls))+  where toS :: TraceArg -> String+        toS (TraceStr s) = s+        toS (Trace    a) = show a++traceEvent :: MxEventBus -> MxEvent -> IO ()+traceEvent tr ev = publishEvent tr (unsafeCreateUnencodedMessage ev)++traceMessage :: Serializable m => MxEventBus -> m -> IO ()+traceMessage tr msg = traceEvent tr (MxUser (unsafeCreateUnencodedMessage msg))++enableTrace :: MxEventBus -> ProcessId -> IO ()+enableTrace t p =+  publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),+                                                (TraceEnable p)))++enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()+enableTraceSync t s p =+  publishEvent t (unsafeCreateUnencodedMessage (Just s, TraceEnable p))++disableTrace :: MxEventBus -> IO ()+disableTrace t =+  publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),+                                     TraceDisable))++disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()+disableTraceSync t s =+  publishEvent t (unsafeCreateUnencodedMessage ((Just s), TraceDisable))++setTraceFlags :: MxEventBus -> TraceFlags -> IO ()+setTraceFlags t f =+  publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))++setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()+setTraceFlagsSync t s f =+  publishEvent t (unsafeCreateUnencodedMessage ((Just s), f))++getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()+getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)++getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()+getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)++class Traceable a where+  uod :: [a] -> TraceSubject++instance Traceable ProcessId where+  uod = TraceProcs . Set.fromList++instance Traceable String where+  uod = TraceNames . Set.fromList
+ src/Control/Distributed/Process/Management/Internal/Types.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE StandaloneDeriving  #-}+{-# LANGUAGE ExistentialQuantification  #-}+{-# LANGUAGE GeneralizedNewtypeDeriving  #-}+{-# LANGUAGE DeriveGeneric   #-}+module Control.Distributed.Process.Management.Internal.Types+  ( MxAgentId(..)+  , MxTableId(..)+  , MxAgentState(..)+  , MxAgent(..)+  , MxAction(..)+  , ChannelSelector(..)+  , MxAgentStart(..)+  , Fork+  , MxSink+  , MxEvent(..)+  , Addressable(..)+  ) where++import Control.Applicative (Applicative)+import Control.Concurrent.STM+  ( TChan+  )+import Control.Distributed.Process.Internal.Types+  ( Process+  , ProcessId+  , Message+  , SendPort+  , DiedReason+  , NodeId+  )+import Control.Monad.IO.Class (MonadIO)+import qualified Control.Monad.State as ST+  ( MonadState+  , StateT+  )+import Data.Binary+import Data.Typeable (Typeable)+import GHC.Generics+import Network.Transport+  ( ConnectionId+  , EndPointAddress+  )++-- | This is the /default/ management event, fired for various internal+-- events around the NT connection and Process lifecycle. All published+-- events that conform to this type, are eligible for tracing - i.e.,+-- they will be delivered to the trace controller.+--+data MxEvent =+    MxSpawned          ProcessId+    -- ^ fired whenever a local process is spawned+  | MxRegistered       ProcessId    String+    -- ^ fired whenever a process/name is registered (locally)+  | MxUnRegistered     ProcessId    String+    -- ^ fired whenever a process/name is unregistered (locally)+  | MxProcessDied      ProcessId    DiedReason+    -- ^ fired whenever a process dies+  | MxNodeDied         NodeId       DiedReason+    -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)+  | MxSent             ProcessId    ProcessId Message+    -- ^ fired whenever a message is sent from a local process+  | MxReceived         ProcessId    Message+    -- ^ fired whenever a message is received by a local process+  | MxConnected        ConnectionId EndPointAddress+    -- ^ fired when a network-transport connection is first established+  | MxDisconnected     ConnectionId EndPointAddress+    -- ^ fired when a network-transport connection is broken/disconnected+  | MxUser             Message+    -- ^ a user defined trace event+  | MxLog              String+    -- ^ a /logging/ event - used for debugging purposes only+  | MxTraceTakeover    ProcessId+    -- ^ notifies a trace listener that all subsequent traces will be sent to /pid/+  | MxTraceDisable+    -- ^ notifies a trace listener that it has been disabled/removed+    deriving (Typeable, Generic, Show)++instance Binary MxEvent where++-- | The class of things that we might be able to resolve to+-- a @ProcessId@ (or not).+class Addressable a where+  resolveToPid :: a -> Maybe ProcessId++instance Addressable MxEvent where+  resolveToPid (MxSpawned     p)     = Just p+  resolveToPid (MxProcessDied p _)   = Just p+  resolveToPid (MxSent        _ p _) = Just p+  resolveToPid (MxReceived    p _)   = Just p+  resolveToPid _                     = Nothing++-- | Gross though it is, this synonym represents a function+-- used to forking new processes, which has to be passed as a HOF+-- when calling mxAgentController, since there's no other way to+-- avoid a circular dependency with Node.hs+type Fork = (Process () -> IO ProcessId)++-- | A newtype wrapper for an agent id (which is a string).+newtype MxAgentId = MxAgentId { agentId :: String }+  deriving (Typeable, Binary, Eq, Ord)++data MxTableId =+    MxForAgent !MxAgentId+  | MxForPid   !ProcessId+  deriving (Typeable, Generic)+instance Binary MxTableId where++data MxAgentState s = MxAgentState+                      {+                        mxAgentId     :: !MxAgentId+                      , mxBus         :: !(TChan Message)+                      , mxSharedTable :: !ProcessId+                      , mxLocalState  :: !s+                      }++-- | Monad for management agents.+--+newtype MxAgent s a =+  MxAgent+  {+    unAgent :: ST.StateT (MxAgentState s) Process a+  } deriving ( Functor+             , Monad+             , MonadIO+             , ST.MonadState (MxAgentState s)+             , Typeable+             , Applicative+             )++data MxAgentStart = MxAgentStart+                    {+                      mxAgentTableChan :: SendPort ProcessId+                    , mxAgentIdStart   :: MxAgentId+                    }+  deriving (Typeable, Generic)+instance Binary MxAgentStart where++data ChannelSelector = InputChan | Mailbox++-- | Represents the actions a management agent can take+-- when evaluating an /event sink/.+--+data MxAction =+    MxAgentDeactivate !String+  | MxAgentPrioritise !ChannelSelector+  | MxAgentReady+  | MxAgentSkip++-- | Type of a management agent's event sink.+type MxSink s = Message -> MxAgent s (Maybe MxAction)
src/Control/Distributed/Process/Node.hs view
@@ -1,3 +1,11 @@+{-# LANGUAGE CPP  #-}+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE RankNTypes  #-}+{-# LANGUAGE BangPatterns  #-}+{-# LANGUAGE GeneralizedNewtypeDeriving  #-}+{-# LANGUAGE RecordWildCards #-}+ -- | Local nodes -- module Control.Distributed.Process.Node@@ -28,6 +36,7 @@   , filter   , partitionWithKey   , elems+  , size   , filterWithKey   , foldlWithKey   )@@ -40,7 +49,7 @@   , toList   ) import Data.Foldable (forM_)-import Data.Maybe (isJust, isNothing, catMaybes)+import Data.Maybe (isJust, fromJust, isNothing, catMaybes) import Data.Typeable (Typeable) import Control.Category ((>>>)) import Control.Applicative ((<$>))@@ -49,9 +58,15 @@ import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets) import qualified Control.Monad.State.Strict as StateT (get, put) import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)-import Control.Exception (throwIO, SomeException, Exception, throwTo)-import qualified Control.Exception as Exception (catch)-import Control.Concurrent (forkIO)+import Control.Exception+  ( throwIO+  , AsyncException(ThreadKilled)+  , SomeException+  , Exception+  , throwTo+  )+import qualified Control.Exception as Exception (Handler(..), catches, finally)+import Control.Concurrent (forkIO, myThreadId) import Control.Distributed.Process.Internal.StrictMVar   ( newMVar   , withMVar@@ -60,8 +75,10 @@   , newEmptyMVar   , putMVar   , takeMVar+  , readMVar   ) import Control.Concurrent.Chan (newChan, writeChan, readChan)+import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar) import Control.Concurrent.STM   ( atomically   )@@ -99,7 +116,7 @@   , LocalProcessId(..)   , ProcessId(..)   , LocalNode(..)-  , Tracer(InactiveTracer)+  , MxEventBus(..)   , LocalNodeState(..)   , LocalProcess(..)   , LocalProcessState(..)@@ -110,6 +127,7 @@   , localPidCounter   , localPidUnique   , localProcessWithId+  , localProcesses   , localConnections   , forever'   , MonitorRef(..)@@ -126,34 +144,49 @@   , DidUnlinkPort(..)   , SpawnRef   , DidSpawn(..)-  , Message+  , Message(..)   , TypedChannel(..)   , Identifier(..)   , nodeOf   , ProcessInfo(..)   , ProcessInfoNone(..)+  , NodeStats(..)   , SendPortId(..)   , typedChannelWithId   , RegisterReply(..)   , WhereIsReply(..)-  , messageToPayload   , payloadToMessage-  , createMessage+  , createUnencodedMessage   , runLocalProcess   , firstNonReservedProcessId-  , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)+  , ImplicitReconnect(WithImplicitReconnect)   )-import Control.Distributed.Process.Internal.Trace+import Control.Distributed.Process.Management.Internal.Agent+  ( mxAgentController+  )+import qualified Control.Distributed.Process.Management.Internal.Table as Table+  ( mxTableCoordinator+  , startTableCoordinator+  )+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace+  ( remoteTable+  )+import Control.Distributed.Process.Management.Internal.Trace.Tracer+  ( defaultTracer+  )+import Control.Distributed.Process.Management.Internal.Trace.Types   ( TraceArg(..)-  , traceFormat-  , startTracing-  , stopTracer+  , traceEvent+  , traceLogFmt+  , enableTrace   )+import Control.Distributed.Process.Management.Internal.Types+  ( MxEvent(..)+  ) import Control.Distributed.Process.Serializable (Serializable) import Control.Distributed.Process.Internal.Messaging   ( sendBinary   , sendMessage-  , sendPayload   , closeImplicitReconnections   , impliesDeathOf   )@@ -163,21 +196,24 @@   , receiveWait   , match   , sendChan+  , catch+  , unwrapMessage   )-import Control.Distributed.Process.Internal.Types (SendPort)+import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..)) import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)-import Control.Distributed.Process.Internal.WeakTQueue (writeTQueue)+import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue) import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC   ( mapMaybe   , mapDefault   )+import Unsafe.Coerce  -------------------------------------------------------------------------------- -- Initialization                                                             -- --------------------------------------------------------------------------------  initRemoteTable :: RemoteTable-initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable+initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable  -- | Initialize a new local node. newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode@@ -193,58 +229,105 @@ -- | Create a new local node (without any service processes running) createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode createBareLocalNode endPoint rtable = do-  unq <- randomIO-  state <- newMVar LocalNodeState-    { _localProcesses   = Map.empty-    , _localPidCounter  = firstNonReservedProcessId-    , _localPidUnique   = unq-    , _localConnections = Map.empty-    }-  ctrlChan <- newChan-  let node = LocalNode { localNodeId   = NodeId $ NT.address endPoint-                       , localEndPoint = endPoint-                       , localState    = state-                       , localCtrlChan = ctrlChan-                       , localTracer   = InactiveTracer-                       , remoteTable   = rtable-                       }-  tracedNode <- startTracing node-  void . forkIO $ runNodeController tracedNode-  void . forkIO $ handleIncomingMessages tracedNode-  return tracedNode+    unq <- randomIO+    state <- newMVar LocalNodeState+      { _localProcesses   = Map.empty+      , _localPidCounter  = firstNonReservedProcessId+      , _localPidUnique   = unq+      , _localConnections = Map.empty+      }+    ctrlChan <- newChan+    let node = LocalNode { localNodeId   = NodeId $ NT.address endPoint+                         , localEndPoint = endPoint+                         , localState    = state+                         , localCtrlChan = ctrlChan+                         , localEventBus = MxEventBusInitialising+                         , remoteTable   = rtable+                         }+    tracedNode <- startMxAgent node +    -- Once the NC terminates, the endpoint isn't much use,+    void $ forkIO $ Exception.finally (runNodeController tracedNode)+                                      (NT.closeEndPoint (localEndPoint node))++    -- whilst a closed/failing endpoint will terminate the NC+    void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)+                                      (stopNC node)++    return tracedNode+  where+    stopNC node =+       writeChan (localCtrlChan node) NCMsg+            { ctrlMsgSender = NodeIdentifier (localNodeId node)+            , ctrlMsgSignal = SigShutdown+            }++startMxAgent :: LocalNode -> IO LocalNode+startMxAgent node = do+  -- see note [tracer/forkProcess races]+  let fork = forkProcess node+  mv <- MVar.newEmptyMVar+  pid <- fork $ mxAgentController fork mv+  (tracer', wqRef, mxNew') <- MVar.takeMVar mv+  return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }++startDefaultTracer :: LocalNode -> IO ()+startDefaultTracer node' = do+  let t = localEventBus node'+  case t of+    MxEventBus _ (Tracer pid _) _ _ -> do+      runProcess node' $ register "trace.controller" pid+      pid' <- forkProcess node' defaultTracer+      enableTrace (localEventBus node') pid'+      runProcess node' $ register "tracer.initial" pid'+    _ -> return ()++-- TODO: we need a better mechanism for defining and registering services+ -- | Start and register the service processes on a node--- (for now, this is only the logger) startServiceProcesses :: LocalNode -> IO () startServiceProcesses node = do+  -- tracing /spawns/ relies on the tracer being enabled, but we start+  -- the default tracer first, even though it might @nsend@ to the logger+  -- before /that/ process has started - this is a totally harmless race+  -- however, so we deliberably ignore it+  startDefaultTracer node+  tableCoordinatorPid <- fork $ Table.startTableCoordinator fork+  runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid   logger <- forkProcess node loop   runProcess node $ register "logger" logger  where-  loop = do-    receiveWait-      [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do-          liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string-          loop-      , match $ \((time, string) :: (String, String)) -> do-          -- this is a 'trace' message from the local node tracer-          liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string-          loop-      , match $ \(ch :: SendPort ()) -> -- a shutdown request-          sendChan ch ()-      ]+   fork = forkProcess node +   loop = do+     receiveWait+       [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do+           liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string+           loop+       , match $ \((time, string) :: (String, String)) -> do+           -- this is a 'trace' message from the local node tracer+           liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string+           loop+       , match $ \(ch :: SendPort ()) -> -- a shutdown request+           sendChan ch ()+       ]+ -- | Force-close a local node -- -- TODO: for now we just close the associated endpoint closeLocalNode :: LocalNode -> IO () closeLocalNode node =+  -- TODO: close all our processes, surely!?   NT.closeEndPoint (localEndPoint node)  -- | Run a process on a local node and wait for it to finish runProcess :: LocalNode -> Process () -> IO () runProcess node proc = do   done <- newEmptyMVar-  void $ forkProcess node (proc `finally` liftIO (putMVar done ()))+  tid <- myThreadId+  void $ forkProcess node $ do+    catch (proc `finally` liftIO (putMVar done ()))+          (\(ex :: SomeException) -> liftIO $ throwTo tid ex)   takeMVar done  -- | Spawn a new process on a local node@@ -275,9 +358,15 @@                                  , processNode   = node                                  }         tid' <- forkIO $ do-          reason <- Exception.catch+          reason <- Exception.catches             (runLocalProcess lproc proc >> return DiedNormal)-            (return . DiedException . (show :: SomeException -> String))+            [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do+                 mMsg <- unwrapMessage msg :: IO (Maybe String)+                 case mMsg of+                   Nothing -> return $ DiedException $ show ex+                   Just m  -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))+            , (Exception.Handler+                (return . DiedException . (show :: SomeException -> String)))]            -- [Unified: Table 4, rules termination and exiting]           modifyMVar_ (localState node) (cleanupProcess pid)@@ -287,8 +376,13 @@             }         return (tid', lproc) +      -- see note [tracer/forkProcess races]+      trace node (MxSpawned pid)+       if lpidCounter lpid == maxBound         then do+          -- TODO: this doesn't look right at all - how do we know+          -- that newUnique represents a process id that is available!?           newUnique <- randomIO           return ( (localProcessWithId lpid ^= Just lproc)                  . (localPidCounter ^= firstNonReservedProcessId)@@ -312,6 +406,15 @@              . (localConnections ^= unaffected)              $ st +-- note [tracer/forkProcess races]+--+-- Our startTracing function uses forkProcess to start the trace controller+-- process, and of course forkProcess attempts to call traceEvent once the+-- process has started. This is harmless, as the localEventBus is not updated+-- until /after/ the initial forkProcess completes, so the first call to+-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).+--+ -------------------------------------------------------------------------------- -- Handle incoming messages                                                   -- --------------------------------------------------------------------------------@@ -320,7 +423,7 @@  data IncomingTarget =     Uninit-  | ToProc (Weak (CQueue Message))+  | ToProc ProcessId (Weak (CQueue Message))   | ToChan TypedChannel   | ToNode @@ -355,20 +458,24 @@       case event of         NT.ConnectionOpened cid rel theirAddr ->           if rel == NT.ReliableOrdered-            then go ( (incomingAt cid ^= Just (theirAddr, Uninit))+            then+              trace node (MxConnected cid theirAddr)+              >> go (+                      (incomingAt cid ^= Just (theirAddr, Uninit))                     . (incomingFrom theirAddr ^: Set.insert cid)                     $ st                     )             else invalidRequest cid st         NT.Received cid payload ->           case st ^. incomingAt cid of-            Just (_, ToProc weakQueue) -> do+            Just (_, ToProc pid weakQueue) -> do               mQueue <- deRefWeak weakQueue               forM_ mQueue $ \queue -> do                 -- TODO: if we find that the queue is Nothing, should we remove                 -- it from the NC state? (and same for channels, below)                 let msg = payloadToMessage payload                 enqueue queue msg -- 'enqueue' is strict+                trace node (MxReceived pid msg)               go st             Just (_, ToChan (TypedChannel chan')) -> do               mChan <- deRefWeak chan'@@ -389,7 +496,7 @@                   mProc <- withMVar state $ return . (^. localProcessWithId lpid)                   case mProc of                     Just proc ->-                      go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)+                      go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)                     Nothing ->                       invalidRequest cid st                 SendPortIdentifier chId -> do@@ -416,7 +523,8 @@           case st ^. incomingAt cid of             Nothing ->               invalidRequest cid st-            Just (src, _) ->+            Just (src, _) -> do+              trace node (MxDisconnected cid src)               go ( (incomingAt cid ^= Nothing)                  . (incomingFrom src ^: Set.delete cid)                  $ st@@ -435,18 +543,15 @@              $ st              )         NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->-          fatal $ "Cloud Haskell fatal error: end point failed: " ++ str+          fail $ "Cloud Haskell fatal error: end point failed: " ++ str         NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->-          fatal $ "Cloud Haskell fatal error: transport failed: " ++ str+          fail $ "Cloud Haskell fatal error: transport failed: " ++ str         NT.EndPointClosed ->-          stopTracer (localTracer node) >> return ()+          return ()         NT.ReceivedMulticast _ _ ->           -- If we received a multicast message, something went horribly wrong           -- and we just give up-          fatal "Cloud Haskell fatal error: received unexpected multicast"--    fatal :: String -> IO ()-    fatal msg = stopTracer (localTracer node) >> fail msg+          fail "Cloud Haskell fatal error: received unexpected multicast"      invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()     invalidRequest cid st = do@@ -454,7 +559,7 @@       -- node. That is, we should report the remote node as having died, and we       -- should close incoming connections (this requires a Transport layer       -- extension).-      traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),+      traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),                                (Trace cid)]       go ( incomingAt cid ^= Nothing          $ st@@ -511,31 +616,30 @@ -- Tracing/Debugging                                                          -- -------------------------------------------------------------------------------- --- [Issue #104]+-- [Issue #104 / DP-13]  traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC () traceNotifyDied node ident reason =-  case reason of-    DiedNormal -> return ()-    _ -> traceNcEventFmt node " "-                         [(TraceStr "[node-controller]"),-                          (Trace ident),-                          (Trace reason)]--traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()-traceNcEventFmt node fmt args =-  liftIO $ traceEventFmtIO node fmt args+  -- TODO: sendPortDied notifications+  liftIO $ withLocalTracer node $ \t ->+    case ident of+      (NodeIdentifier nid)    -> traceEvent t (MxNodeDied nid reason)+      (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)+      _                       -> return ()  traceEventFmtIO :: LocalNode                 -> String                 -> [TraceArg]                 -> IO () traceEventFmtIO node fmt args =-  withLocalTracer node $ \t -> traceFormat t fmt args+  withLocalTracer node $ \t -> traceLogFmt t fmt args -withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()-withLocalTracer node act = act (localTracer node)+trace :: LocalNode -> MxEvent -> IO ()+trace node ev = withLocalTracer node $ \t -> traceEvent t ev +withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()+withLocalTracer node act = act (localEventBus node)+ -------------------------------------------------------------------------------- -- Core functionality                                                         -- --------------------------------------------------------------------------------@@ -575,14 +679,25 @@         ncEffectRegister from label atnode pid force       NCMsg (ProcessIdentifier from) (WhereIs label) ->         ncEffectWhereIs from label-      NCMsg from (NamedSend label msg') ->-        ncEffectNamedSend from label msg'+      NCMsg _ (NamedSend label msg') ->+        ncEffectNamedSend label msg'+      NCMsg _ (LocalSend to msg') ->+        ncEffectLocalSend node to msg'+      NCMsg _ (LocalPortSend to msg') ->+        ncEffectLocalPortSend to msg'       NCMsg (ProcessIdentifier from) (Kill to reason) ->         ncEffectKill from to reason       NCMsg (ProcessIdentifier from) (Exit to reason) ->         ncEffectExit from to reason       NCMsg (ProcessIdentifier from) (GetInfo pid) ->         ncEffectGetInfo from pid+      NCMsg _ SigShutdown ->+        liftIO $ do+          NT.closeEndPoint (localEndPoint node)+            `Exception.finally` throwIO ThreadKilled+        -- ThreadKilled seems to make more sense than fail/error here+      NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->+        ncEffectGetNodeStats from nid       unexpected ->         error $ "nodeController: unexpected message " ++ show unexpected @@ -692,6 +807,7 @@   mProc <- unClosure cProc   -- If the closure does not exist, we spawn a process that throws an exception   -- This allows the remote node to find out what's happening+  -- TODO:   let proc = case mProc of                Left err -> fail $ "Error: Could not resolve closure: " ++ err                Right p  -> p@@ -722,6 +838,9 @@      then do when (isOk) $                do modify' $ registeredHereFor label ^= mPid                   updateRemote node currentVal mPid+                  case mPid of+                    (Just p) -> liftIO $ trace node (MxRegistered p label)+                    Nothing  -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)              liftIO $ sendMessage node                        (NodeIdentifier (localNodeId node))                        (ProcessIdentifier from)@@ -744,6 +863,7 @@             updateRemote _ _ _ = return ()             maybeify f Nothing = unmaybeify $ f []             maybeify f (Just x) = unmaybeify $ f x+             unmaybeify [] = Nothing             unmaybeify x = Just x             incList [] tag = [(tag,1)]@@ -777,18 +897,43 @@                        (WhereIsReply label mPid)  -- [Unified: Table 14]-ncEffectNamedSend :: Identifier -> String -> Message -> NC ()-ncEffectNamedSend from label msg = do-  node <- ask+ncEffectNamedSend :: String -> Message -> NC ()+ncEffectNamedSend label msg = do   mPid <- gets (^. registeredHereFor label)   -- If mPid is Nothing, we just ignore the named send (as per Table 14)-  forM_ mPid $ \pid ->-    liftIO $ sendPayload node-                         from-                         (ProcessIdentifier pid)-                         NoImplicitReconnect-                         (messageToPayload msg)+  forM_ mPid $ \pid -> postMessage pid msg +-- [Issue #DP-20]+ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()+ncEffectLocalSend node to msg =+  liftIO $ withLocalProc node to $ \p -> do+    enqueue (processQueue p) msg+    trace node (MxReceived to msg)++-- [Issue #DP-20]+ncEffectLocalPortSend :: SendPortId -> Message -> NC ()+ncEffectLocalPortSend from msg = do+  node <- ask+  let pid = sendPortProcessId from+      cid = sendPortLocalId   from+  liftIO $ withLocalProc node pid $ \proc -> do+    mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)+    case mChan of+      -- in the unlikely event we know nothing about this channel id,+      -- there's little to be done - perhaps some logging/tracing though...+      Nothing -> return ()+      Just (TypedChannel chan') -> do+        -- If ch is Nothing, the process has given up the read end of+        -- the channel and we simply ignore the incoming message - this+        ch <- deRefWeak chan'+        forM_ ch $ \chan -> deliverChan msg chan+  where deliverChan :: forall a . Message -> TQueue a -> IO ()+        deliverChan (UnencodedMessage _ raw) chan' =+            atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)+        deliverChan (EncodedMessage   _ _) _ =+            -- this will not happen unless someone screws with Primitives.hs+            error "invalid local channel delivery"+ -- [Issue #69] ncEffectKill :: ProcessId -> ProcessId -> String -> NC () ncEffectKill from to reason = do@@ -850,6 +995,21 @@                                                  then (k:ks)                                                  else ks) [] +ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()+ncEffectGetNodeStats from _nid = do+  node <- ask+  ncState <- StateT.get+  nodeState <- liftIO $ readMVar (localState node)+  let localProcesses' = nodeState ^. localProcesses+      stats =+        NodeStats {+            nodeStatsNode = localNodeId node+          , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere+          , nodeStatsMonitors = Map.size $ ncState ^. monitors+          , nodeStatsLinks = Map.size $ ncState ^. links+          , nodeStatsProcesses = Map.size localProcesses'+          }+  postAsMessage from stats -------------------------------------------------------------------------------- -- Auxiliary                                                                  -- --------------------------------------------------------------------------------@@ -887,21 +1047,25 @@  -- | [Unified: Table 8] destNid :: ProcessSignal -> Maybe NodeId-destNid (Link ident)        = Just $ nodeOf ident-destNid (Unlink ident)      = Just $ nodeOf ident-destNid (Monitor ref)       = Just $ nodeOf (monitorRefIdent ref)-destNid (Unmonitor ref)     = Just $ nodeOf (monitorRefIdent ref)-destNid (Spawn _ _)         = Nothing-destNid (Register _ _ _ _)  = Nothing-destNid (WhereIs _)         = Nothing-destNid (NamedSend _ _)     = Nothing+destNid (Link ident)          = Just $ nodeOf ident+destNid (Unlink ident)        = Just $ nodeOf ident+destNid (Monitor ref)         = Just $ nodeOf (monitorRefIdent ref)+destNid (Unmonitor ref)       = Just $ nodeOf (monitorRefIdent ref)+destNid (Spawn _ _)           = Nothing+destNid (Register _ _ _ _)    = Nothing+destNid (WhereIs _)           = Nothing+destNid (NamedSend _ _)       = Nothing -- We don't need to forward 'Died' signals; if monitoring/linking is setup, -- then when a local process dies the monitoring/linking machinery will take -- care of notifying remote nodes-destNid (Died _ _)          = Nothing-destNid (Kill pid _)        = Just $ processNodeId pid-destNid (Exit pid _)        = Just $ processNodeId pid-destNid (GetInfo pid)       = Just $ processNodeId pid+destNid (Died _ _)            = Nothing+destNid (Kill pid _)          = Just $ processNodeId pid+destNid (Exit pid _)          = Just $ processNodeId pid+destNid (GetInfo pid)         = Just $ processNodeId pid+destNid (GetNodeStats nid)    = Just nid+destNid (LocalSend pid _)     = Just $ processNodeId pid+destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)+destNid (SigShutdown)       = Nothing  -- | Check if a process is local to our own node isLocal :: LocalNode -> Identifier -> Bool@@ -938,25 +1102,25 @@ --------------------------------------------------------------------------------  postAsMessage :: Serializable a => ProcessId -> a -> NC ()-postAsMessage pid = postMessage pid . createMessage+postAsMessage pid = postMessage pid . createUnencodedMessage  postMessage :: ProcessId -> Message -> NC () postMessage pid msg = do-  withLocalProc pid $ \p -> enqueue (processQueue p) msg+  node <- ask+  liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg  throwException :: Exception e => ProcessId -> e -> NC ()-throwException pid e = withLocalProc pid $ \p ->-  throwTo (processThread p) e--withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()-withLocalProc pid p = do+throwException pid e = do   node <- ask-  liftIO $ do-    -- By [Unified: table 6, rule missing_process] messages to dead processes-    -- can silently be dropped-    let lpid = processLocalId pid-    mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)-    forM_ mProc p+  liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e++withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()+withLocalProc node pid p =+  -- By [Unified: table 6, rule missing_process] messages to dead processes+  -- can silently be dropped+  let lpid = processLocalId pid in do+  mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)+  forM_ mProc p  -------------------------------------------------------------------------------- -- Accessors                                                                  --
src/Control/Distributed/Process/Serializable.hs view
@@ -1,3 +1,8 @@+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE UndecidableInstances  #-}+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE GADTs  #-}+{-# LANGUAGE CPP    #-} module Control.Distributed.Process.Serializable   ( Serializable   , encodeFingerprint@@ -10,8 +15,15 @@   ) where  import Data.Binary (Binary)++#if MIN_VERSION_base(4,7,0)+import Data.Typeable (Typeable)+import Data.Typeable.Internal (TypeRep(TypeRep), typeOf)+#else import Data.Typeable (Typeable(..)) import Data.Typeable.Internal (TypeRep(TypeRep))+#endif+ import Numeric (showHex) import Control.Exception (throw) import GHC.Fingerprint.Type (Fingerprint(..))
+ src/Control/Distributed/Process/UnsafePrimitives.hs view
@@ -0,0 +1,121 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Distributed.Process.UnsafePrimitives+-- Copyright   :  (c) Well-Typed / Tim Watson+-- License     :  BSD3 (see the file LICENSE)+--+-- Maintainer  :  Tim Watson <watson.timothy@gmail.com>+-- Stability   :  experimental+-- Portability :  non-portable (requires concurrency)+--+-- [Unsafe Variants of Cloud Haskell's Messaging Primitives]+--+-- Cloud Haskell's semantics do not mention evaluation/strictness properties+-- at all; Only the promise (or lack) of signal/message delivery between+-- processes is considered. For practical purposes, Cloud Haskell optimises+-- communication between (intra-node) local processes by skipping the+-- network-transport layer. In order to ensure the same strictness semantics+-- however, messages /still/ undergo binary serialisation before (internal)+-- transmission takes place. Thus we ensure that in both (the local and remote)+-- cases, message payloads are fully evaluated. Whilst this provides the user+-- with /unsurprising/ behaviour, the resulting performance overhead is quite+-- severe. Communicating data between Haskell threads /without/ forcing binary+-- serialisation reduces (intra-node, inter-process) communication overheads+-- by several orders of magnitude.+--+-- This module provides variants of Cloud Haskell's messaging primitives+-- ('send', 'sendChan', 'nsend' and 'wrapMessage') which do /not/ force binary+-- serialisation in the local case, thereby offering superior intra-node+-- messaging performance. The /catch/ is that any evaluation problems lurking+-- within the passed data structure (e.g., fields set to @undefined@ and so on)+-- will show up in the receiver rather than in the caller (as they would with+-- the /normal/ strategy).+--+-- Use of the functions in this module can potentially change the runtime+-- behaviour of your application. You have been warned!+--+-- This module is exported so that you can replace the use of Cloud Haskell's+-- /safe/ messaging primitives. If you want to use both variants, then you can+-- take advantage of qualified imports, however "Control.Distributed.Process"+-- also re-exports these functions under different names, using the @unsafe@+-- prefix.+--+module Control.Distributed.Process.UnsafePrimitives+  ( -- * Unsafe Basic Messaging+    send+  , sendChan+  , nsend+  , wrapMessage+  ) where++import Control.Distributed.Process.Internal.Messaging+  ( sendMessage+  , sendBinary+  , sendCtrlMsg+  )++import Control.Distributed.Process.Internal.Types+  ( ProcessId(..)+  , LocalNode(..)+  , LocalProcess(..)+  , Process(..)+  , SendPort(..)+  , ProcessSignal(..)+  , Identifier(..)+  , ImplicitReconnect(..)+  , SendPortId(..)+  , Message+  , sendPortProcessId+  , unsafeCreateUnencodedMessage+  )+import Control.Distributed.Process.Serializable (Serializable)++import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Reader (ask)++-- | Named send to a process in the local registry (asynchronous)+nsend :: Serializable a => String -> a -> Process ()+nsend label msg =+  sendCtrlMsg Nothing (NamedSend label (unsafeCreateUnencodedMessage msg))++-- | Send a message+send :: Serializable a => ProcessId -> a -> Process ()+send them msg = do+  proc <- ask+  let node     = localNodeId (processNode proc)+      destNode = (processNodeId them) in do+  case destNode == node of+    True  -> unsafeSendLocal them msg+    False -> liftIO $ sendMessage (processNode proc)+                                  (ProcessIdentifier (processId proc))+                                  (ProcessIdentifier them)+                                  NoImplicitReconnect+                                  msg+  where+    unsafeSendLocal :: (Serializable a) => ProcessId -> a -> Process ()+    unsafeSendLocal pid msg' =+      sendCtrlMsg Nothing $ LocalSend pid (unsafeCreateUnencodedMessage msg')++-- | Send a message on a typed channel+sendChan :: Serializable a => SendPort a -> a -> Process ()+sendChan (SendPort cid) msg = do+  proc <- ask+  let node     = localNodeId (processNode proc)+      destNode = processNodeId (sendPortProcessId cid) in do+  case destNode == node of+    True  -> unsafeSendChanLocal cid msg+    False -> do+      liftIO $ sendBinary (processNode proc)+                          (ProcessIdentifier (processId proc))+                          (SendPortIdentifier cid)+                          NoImplicitReconnect+                          msg+  where+    unsafeSendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()+    unsafeSendChanLocal spId msg' =+      sendCtrlMsg Nothing $ LocalPortSend spId (unsafeCreateUnencodedMessage msg')++-- | Create an unencoded @Message@ for any @Serializable@ type.+wrapMessage :: Serializable a => a -> Message+wrapMessage = unsafeCreateUnencodedMessage+
− tests/TestCH.hs
@@ -1,1184 +0,0 @@-module Main where--#if ! MIN_VERSION_base(4,6,0)-import Prelude hiding (catch)-#endif--import Data.Binary (Binary(..))-import Data.Typeable (Typeable)-import Data.Foldable (forM_)-import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)-import Control.Concurrent.MVar-  ( MVar-  , newEmptyMVar-  , putMVar-  , takeMVar-  , readMVar-  )-import Control.Monad (replicateM_, replicateM, forever)-import Control.Exception (SomeException, throwIO)-import qualified Control.Exception as Ex (catch)-import Control.Applicative ((<$>), (<*>), pure, (<|>))-import qualified Network.Transport as NT (Transport, closeEndPoint)-import Network.Socket (sClose)-import Network.Transport.TCP-  ( createTransportExposeInternals-  , TransportInternals(socketBetween)-  , defaultTCPParameters-  )-import Control.Distributed.Process-import Control.Distributed.Process.Internal.Types-  ( NodeId(nodeAddress)-  , LocalNode(localEndPoint)-  , ProcessExitException(..)-  , nullProcessId-  )-import Control.Distributed.Process.Node-import Control.Distributed.Process.Serializable (Serializable)--import Test.HUnit (Assertion)-import Test.Framework (Test, defaultMain, testGroup)-import Test.Framework.Providers.HUnit (testCase)--newtype Ping = Ping ProcessId-  deriving (Typeable, Binary, Show)--newtype Pong = Pong ProcessId-  deriving (Typeable, Binary, Show)------------------------------------------------------------------------------------- Supporting definitions                                                     --------------------------------------------------------------------------------------- | Like fork, but throw exceptions in the child thread to the parent-forkTry :: IO () -> IO ThreadId-forkTry p = do-  tid <- myThreadId-  forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))---- | The ping server from the paper-ping :: Process ()-ping = do-  Pong partner <- expect-  self <- getSelfPid-  send partner (Ping self)-  ping---- | Quick and dirty synchronous version of whereisRemoteAsync-whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)-whereisRemote nid string = do-  whereisRemoteAsync nid string-  WhereIsReply _ mPid <- expect-  return mPid--data Add       = Add    ProcessId Double Double deriving (Typeable)-data Divide    = Divide ProcessId Double Double deriving (Typeable)-data DivByZero = DivByZero deriving (Typeable)--instance Binary Add where-  put (Add pid x y) = put pid >> put x >> put y-  get = Add <$> get <*> get <*> get--instance Binary Divide where-  put (Divide pid x y) = put pid >> put x >> put y-  get = Divide <$> get <*> get <*> get--instance Binary DivByZero where-  put DivByZero = return ()-  get = return DivByZero---- The math server from the paper-math :: Process ()-math = do-  receiveWait-    [ match (\(Add pid x y) -> send pid (x + y))-    , matchIf (\(Divide _   _ y) -> y /= 0)-              (\(Divide pid x y) -> send pid (x / y))-    , match (\(Divide pid _ _) -> send pid DivByZero)-    ]-  math---- | Monitor or link to a remote node-monitorOrLink :: Bool            -- ^ 'True' for monitor, 'False' for link-              -> ProcessId       -- Process to monitor/link to-              -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up-              -> Process (Maybe MonitorRef)-monitorOrLink mOrL pid mSignal = do-  result <- if mOrL then Just <$> monitor pid-                    else link pid >> return Nothing-  -- Monitor is asynchronous, which usually does not matter but if we want a-  -- *specific* signal then it does. Therefore we wait an arbitrary delay and-  -- hope that this means the monitor has been set up-  forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()-  return result--monitorTestProcess :: ProcessId       -- Process to monitor/link to-                   -> Bool            -- 'True' for monitor, 'False' for link-                   -> Bool            -- Should we unmonitor?-                   -> DiedReason      -- Expected cause of death-                   -> Maybe (MVar ()) -- Signal for 'monitor set up'-                   -> MVar ()         -- Signal for successful termination-                   -> Process ()-monitorTestProcess theirAddr mOrL un reason monitorSetup done =-  catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup-            case (un, mRef) of-              (True, Nothing) -> do-                unlink theirAddr-                liftIO $ putMVar done ()-              (True, Just ref) -> do-                unmonitor ref-                liftIO $ putMVar done ()-              (False, ref) -> do-                ProcessMonitorNotification ref' pid reason' <- expect-                True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'-                liftIO $ putMVar done ()-        )-        (\(ProcessLinkException pid reason') -> do-            True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'-            liftIO $ putMVar done ()-        )------------------------------------------------------------------------------------- The tests proper                                                           --------------------------------------------------------------------------------------- | Basic ping test-testPing :: NT.Transport -> Assertion-testPing transport = do-  serverAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- Server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode ping-    putMVar serverAddr addr--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    pingServer <- readMVar serverAddr--    let numPings = 10000--    runProcess localNode $ do-      pid <- getSelfPid-      replicateM_ numPings $ do-        send pingServer (Pong pid)-        Ping _ <- expect-        return ()--    putMVar clientDone ()--  takeMVar clientDone---- | Monitor a process on an unreachable node-testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorUnreachable transport mOrL un = do-  deadProcess <- newEmptyMVar-  done <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ threadDelay 1000000-    closeLocalNode localNode-    putMVar deadProcess addr--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    theirAddr <- readMVar deadProcess-    runProcess localNode $-      monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done--  takeMVar done---- | Monitor a process which terminates normally-testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorNormalTermination transport mOrL un = do-  monitorSetup <- newEmptyMVar-  monitoredProcess <- newEmptyMVar-  done <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode $-      liftIO $ readMVar monitorSetup-    putMVar monitoredProcess addr--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    theirAddr <- readMVar monitoredProcess-    runProcess localNode $-      monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done--  takeMVar done---- | Monitor a process which terminates abnormally-testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorAbnormalTermination transport mOrL un = do-  monitorSetup <- newEmptyMVar-  monitoredProcess <- newEmptyMVar-  done <- newEmptyMVar--  let err = userError "Abnormal termination"--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ do-      readMVar monitorSetup-      throwIO err-    putMVar monitoredProcess addr--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    theirAddr <- readMVar monitoredProcess-    runProcess localNode $-      monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done--  takeMVar done---- | Monitor a local process that is already dead-testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorLocalDeadProcess transport mOrL un = do-  processDead <- newEmptyMVar-  processAddr <- newEmptyMVar-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  forkIO $ do-    addr <- forkProcess localNode . liftIO $ putMVar processDead ()-    putMVar processAddr addr--  forkIO $ do-    theirAddr <- readMVar processAddr-    readMVar processDead-    runProcess localNode $ do-      monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done--  takeMVar done---- | Monitor a remote process that is already dead-testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorRemoteDeadProcess transport mOrL un = do-  processDead <- newEmptyMVar-  processAddr <- newEmptyMVar-  done <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ putMVar processDead ()-    putMVar processAddr addr--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    theirAddr <- readMVar processAddr-    readMVar processDead-    runProcess localNode $ do-      monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done--  takeMVar done---- | Monitor a process that becomes disconnected-testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion-testMonitorDisconnect transport mOrL un = do-  processAddr <- newEmptyMVar-  monitorSetup <- newEmptyMVar-  done <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode . liftIO $ threadDelay 1000000-    putMVar processAddr addr-    readMVar monitorSetup-    NT.closeEndPoint (localEndPoint localNode)--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    theirAddr <- readMVar processAddr-    runProcess localNode $ do-      monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done--  takeMVar done---- | Test the math server (i.e., receiveWait)-testMath :: NT.Transport -> Assertion-testMath transport = do-  serverAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- Server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode math-    putMVar serverAddr addr--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    mathServer <- readMVar serverAddr--    runProcess localNode $ do-      pid <- getSelfPid-      send mathServer (Add pid 1 2)-      3 <- expect :: Process Double-      send mathServer (Divide pid 8 2)-      4 <- expect :: Process Double-      send mathServer (Divide pid 8 0)-      DivByZero <- expect-      liftIO $ putMVar clientDone ()--  takeMVar clientDone---- | Send first message (i.e. connect) to an already terminated process--- (without monitoring); then send another message to a second process on--- the same remote node (we're checking that the remote node did not die)-testSendToTerminated :: NT.Transport -> Assertion-testSendToTerminated transport = do-  serverAddr1 <- newEmptyMVar-  serverAddr2 <- newEmptyMVar-  clientDone <- newEmptyMVar--  forkIO $ do-    terminated <- newEmptyMVar-    localNode <- newLocalNode transport initRemoteTable-    addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()-    addr2 <- forkProcess localNode $ ping-    readMVar terminated-    putMVar serverAddr1 addr1-    putMVar serverAddr2 addr2--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    server1 <- readMVar serverAddr1-    server2 <- readMVar serverAddr2-    runProcess localNode $ do-      pid <- getSelfPid-      send server1 "Hi"-      send server2 (Pong pid)-      Ping pid' <- expect-      True <- return $ pid' == server2-      liftIO $ putMVar clientDone ()--  takeMVar clientDone---- | Test (non-zero) timeout-testTimeout :: NT.Transport -> Assertion-testTimeout transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  runProcess localNode $ do-    Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]-    liftIO $ putMVar done ()--  takeMVar done---- | Test zero timeout-testTimeout0 :: NT.Transport -> Assertion-testTimeout0 transport = do-  serverAddr <- newEmptyMVar-  clientDone <- newEmptyMVar-  messagesSent <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    addr <- forkProcess localNode $ do-      liftIO $ readMVar messagesSent >> threadDelay 1000000-      -- Variation on the venerable ping server which uses a zero timeout-      -- Since we wait for all messages to be sent before doing this receive,-      -- we should nevertheless find the right message immediately-      Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]-      self <- getSelfPid-      send partner (Ping self)-    putMVar serverAddr addr--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    server <- readMVar serverAddr-    runProcess localNode $ do-      pid <- getSelfPid-      -- Send a bunch of messages. A large number of messages that the server-      -- is not interested in, and then a single message that it wants-      replicateM_ 10000 $ send server "Irrelevant message"-      send server (Pong pid)-      liftIO $ putMVar messagesSent ()-      Ping _ <- expect-      liftIO $ putMVar clientDone ()--  takeMVar clientDone---- | Test typed channels-testTypedChannels :: NT.Transport -> Assertion-testTypedChannels transport = do-  serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))-  clientDone <- newEmptyMVar--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    forkProcess localNode $ do-      (serverSendPort, rport) <- newChan-      liftIO $ putMVar serverChannel serverSendPort-      (clientSendPort, i) <- receiveChan rport-      sendChan clientSendPort (even i)-    return ()--  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    serverSendPort <- readMVar serverChannel-    runProcess localNode $ do-      (clientSendPort, rport) <- newChan-      sendChan serverSendPort (clientSendPort, 5)-      False <- receiveChan rport-      liftIO $ putMVar clientDone ()--  takeMVar clientDone---- | Test merging receive ports-testMergeChannels :: NT.Transport -> Assertion-testMergeChannels transport = do-    localNode <- newLocalNode transport initRemoteTable-    testFlat localNode True          "aaabbbccc"-    testFlat localNode False         "abcabcabc"-    testNested localNode True True   "aaabbbcccdddeeefffggghhhiii"-    testNested localNode True False  "adgadgadgbehbehbehcficficfi"-    testNested localNode False True  "abcabcabcdefdefdefghighighi"-    testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"-    testBlocked localNode True-    testBlocked localNode False-  where-    -- Single layer of merging-    testFlat :: LocalNode -> Bool -> String -> IO ()-    testFlat localNode biased expected = do-      done <- newEmptyMVar-      forkProcess localNode $ do-        rs  <- mapM charChannel "abc"-        m   <- mergePorts biased rs-        xs  <- replicateM 9 $ receiveChan m-        True <- return $ xs == expected-        liftIO $ putMVar done ()-      takeMVar done--    -- Two layers of merging-    testNested :: LocalNode -> Bool -> Bool -> String -> IO ()-    testNested localNode biasedInner biasedOuter expected = do-      done <- newEmptyMVar-      forkProcess localNode $ do-        rss  <- mapM (mapM charChannel) ["abc", "def", "ghi"]-        ms   <- mapM (mergePorts biasedInner) rss-        m    <- mergePorts biasedOuter ms-        xs   <- replicateM (9 * 3) $ receiveChan m-        True <- return $ xs == expected-        liftIO $ putMVar done ()-      takeMVar done--    -- Test that if no messages are (immediately) available, the scheduler makes no difference-    testBlocked :: LocalNode -> Bool -> IO ()-    testBlocked localNode biased = do-      vs <- replicateM 3 newEmptyMVar-      done <- newEmptyMVar--      forkProcess localNode $ do-        [sa, sb, sc] <- liftIO $ mapM readMVar vs-        mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)-          [ -- a, b, c-            (sa, 'a')-          , (sb, 'b')-          , (sc, 'c')-            -- a, c, b-          , (sa, 'a')-          , (sc, 'c')-          , (sb, 'b')-            -- b, a, c-          , (sb, 'b')-          , (sa, 'a')-          , (sc, 'c')-            -- b, c, a-          , (sb, 'b')-          , (sc, 'c')-          , (sa, 'a')-            -- c, a, b-          , (sc, 'c')-          , (sa, 'a')-          , (sb, 'b')-            -- c, b, a-          , (sc, 'c')-          , (sb, 'b')-          , (sa, 'a')-          ]--      forkProcess localNode $ do-        (ss, rs) <- unzip <$> replicateM 3 newChan-        liftIO $ mapM_ (uncurry putMVar) $ zip vs ss-        m  <- mergePorts biased rs-        xs <- replicateM (6 * 3) $ receiveChan m-        True <- return $ xs == "abcacbbacbcacabcba"-        liftIO $ putMVar done ()--      takeMVar done--    mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)-    mergePorts True  = mergePortsBiased-    mergePorts False = mergePortsRR--    charChannel :: Char -> Process (ReceivePort Char)-    charChannel c = do-      (sport, rport) <- newChan-      replicateM_ 3 $ sendChan sport c-      liftIO $ threadDelay 10000 -- Make sure messages have been sent-      return rport--testTerminate :: NT.Transport -> Assertion-testTerminate transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  pid <- forkProcess localNode $ do-    liftIO $ threadDelay 100000-    terminate--  runProcess localNode $ do-    ref <- monitor pid-    ProcessMonitorNotification ref' pid' (DiedException ex) <- expect-    True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException-    liftIO $ putMVar done ()--  takeMVar done--testMonitorNode :: NT.Transport -> Assertion-testMonitorNode transport = do-  [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  closeLocalNode node1--  runProcess node2 $ do-    ref <- monitorNode (localNodeId node1)-    NodeMonitorNotification ref' nid DiedDisconnect <- expect-    True <- return $ ref == ref' && nid == localNodeId node1-    liftIO $ putMVar done ()--  takeMVar done--testMonitorChannel :: NT.Transport -> Assertion-testMonitorChannel transport = do-    [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable-    gotNotification <- newEmptyMVar--    pid <- forkProcess node1 $ do-      sport <- expect :: Process (SendPort ())-      ref <- monitorPort sport-      PortMonitorNotification ref' port' reason <- expect-      -- reason might be DiedUnknownId if the receive port is GCed before the-      -- monitor is established (TODO: not sure that this is reasonable)-      return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)-      liftIO $ putMVar gotNotification ()--    runProcess node2 $ do-      (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())-      send pid sport-      liftIO $ threadDelay 100000--    takeMVar gotNotification--testRegistry :: NT.Transport -> Assertion-testRegistry transport = do-  node <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  pingServer <- forkProcess node ping--  runProcess node $ do-    register "ping" pingServer-    Just pid <- whereis "ping"-    True <- return $ pingServer == pid-    us <- getSelfPid-    nsend "ping" (Pong us)-    Ping pid' <- expect-    True <- return $ pingServer == pid'-    liftIO $ putMVar done ()--  takeMVar done--testRemoteRegistry :: NT.Transport -> Assertion-testRemoteRegistry transport = do-  node1 <- newLocalNode transport initRemoteTable-  node2 <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  pingServer <- forkProcess node1 ping--  runProcess node2 $ do-    let nid1 = localNodeId node1-    registerRemoteAsync nid1 "ping" pingServer-    receiveWait [-       matchIf (\(RegisterReply label' _) -> "ping" == label')-               (\(RegisterReply _ _) -> return ()) ]--    Just pid <- whereisRemote nid1 "ping"-    True <- return $ pingServer == pid-    us <- getSelfPid-    nsendRemote nid1 "ping" (Pong us)-    Ping pid' <- expect-    True <- return $ pingServer == pid'-    liftIO $ putMVar done ()--  takeMVar done--testSpawnLocal :: NT.Transport -> Assertion-testSpawnLocal transport = do-  node <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  runProcess node $ do-    us <- getSelfPid--    pid <- spawnLocal $ do-      sport <- expect-      sendChan sport (1234 :: Int)--    sport <- spawnChannelLocal $ \rport -> do-      (1234 :: Int) <- receiveChan rport-      send us ()--    send pid sport-    () <- expect-    liftIO $ putMVar done ()--  takeMVar done--testReconnect :: NT.Transport -> TransportInternals -> Assertion-testReconnect transport transportInternals = do-  [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable-  let nid1 = localNodeId node1-      nid2 = localNodeId node2-  processA <- newEmptyMVar-  [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar--  forkProcess node1 $ do-    us <- getSelfPid-    liftIO $ putMVar processA us-    msg1 <- expect-    msg2 <- expect-    True <- return $ msg1 == "message 1" && msg2 == "message 3"-    liftIO $ putMVar sendTestOk ()--  forkProcess node2 $ do-    {--     - Make sure there is no implicit reconnect on normal message sending-     -}--    them <- liftIO $ readMVar processA-    send them "message 1" >> liftIO (threadDelay 100000)--    -- Simulate network failure-    liftIO $ do-      sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)-      sClose sock-      threadDelay 10000--    -- Should not arrive-    send them "message 2"--    -- Should arrive-    reconnect them-    send them "message 3"--    liftIO $ takeMVar sendTestOk--    {--     - Test that there *is* implicit reconnect on node controller messages-     -}--    us <- getSelfPid-    registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous-    receiveWait [-        matchIf (\(RegisterReply label' _) -> "a" == label')-                (\(RegisterReply _ _) -> return ()) ]--    Just _  <- whereisRemote nid1 "a"---    -- Simulate network failure-    liftIO $ do-      sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)-      sClose sock-      threadDelay 10000--    -- This will happen due to implicit reconnect-    registerRemoteAsync nid1 "b" us-    receiveWait [-        matchIf (\(RegisterReply label' _) -> "b" == label')-                (\(RegisterReply _ _) -> return ()) ]--    -- Should happen-    registerRemoteAsync nid1 "c" us-    receiveWait [-        matchIf (\(RegisterReply label' _) -> "c" == label')-                (\(RegisterReply _ _) -> return ()) ]--    -- Check-    Nothing  <- whereisRemote nid1 "a"  -- this will fail because the name is removed when the node is disconnected-    Just _  <- whereisRemote nid1 "b"  -- this will suceed because the value is set after thereconnect-    Just _  <- whereisRemote nid1 "c"--    liftIO $ putMVar registerTestOk ()--  takeMVar registerTestOk---- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server--- in between-testMatchAny :: NT.Transport -> Assertion-testMatchAny transport = do-  proxyAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- Math server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    mathServer <- forkProcess localNode math-    proxyServer <- forkProcess localNode $ forever $ do-      msg <- receiveWait [ matchAny return ]-      forward msg mathServer-    putMVar proxyAddr proxyServer--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    mathServer <- readMVar proxyAddr--    runProcess localNode $ do-      pid <- getSelfPid-      send mathServer (Add pid 1 2)-      3 <- expect :: Process Double-      send mathServer (Divide pid 8 2)-      4 <- expect :: Process Double-      send mathServer (Divide pid 8 0)-      DivByZero <- expect-      liftIO $ putMVar clientDone ()--  takeMVar clientDone---- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server--- in between, however we block 'Divide' requests ....-testMatchAnyHandle :: NT.Transport -> Assertion-testMatchAnyHandle transport = do-  proxyAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- Math server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    mathServer <- forkProcess localNode math-    proxyServer <- forkProcess localNode $ forever $ do-        receiveWait [-            matchAny (maybeForward mathServer)-          ]-    putMVar proxyAddr proxyServer--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    mathServer <- readMVar proxyAddr--    runProcess localNode $ do-      pid <- getSelfPid-      send mathServer (Add pid 1 2)-      3 <- expect :: Process Double-      send mathServer (Divide pid 8 2)-      Nothing <- (expectTimeout 100000) :: Process (Maybe Double)-      liftIO $ putMVar clientDone ()--  takeMVar clientDone-  where maybeForward :: ProcessId -> AbstractMessage -> Process (Maybe ())-        maybeForward s msg =-            maybeHandleMessage msg (\m@(Add _ _ _) -> send s m)--testMatchAnyNoHandle :: NT.Transport -> Assertion-testMatchAnyNoHandle transport = do-  addr <- newEmptyMVar-  clientDone <- newEmptyMVar-  serverDone <- newEmptyMVar--  -- Math server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    server <- forkProcess localNode $ forever $ do-        receiveWait [-          matchAnyIf-            -- the condition has type `Add -> Bool`-            (\(Add _ _ _) -> True)-            -- the match `AbstractMessage -> Process ()` will succeed!-            (\m -> do-              -- `String -> Process ()` does *not* match the input types however-              r <- (maybeHandleMessage m (\(_ :: String) -> die "NONSENSE" ))-              case r of-                Nothing -> return ()-                Just _  -> die "NONSENSE")-          ]-        -- we *must* have removed the message from our mailbox though!!!-        Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]-        liftIO $ putMVar serverDone ()-    putMVar addr server--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    server <- readMVar addr--    runProcess localNode $ do-      pid <- getSelfPid-      send server (Add pid 1 2)-      -- we only care about the client having sent a message, so we're done-      liftIO $ putMVar clientDone ()--  takeMVar clientDone-  takeMVar serverDone---- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests--- unless the text body @/= "bar"@ - this case should time out rather than--- removing the message from the process mailbox.-testMatchAnyIf :: NT.Transport -> Assertion-testMatchAnyIf transport = do-  echoAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  -- echo server-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    echoServer <- forkProcess localNode $ forever $ do-        receiveWait [-            matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")-                       handleMessage-          ]-    putMVar echoAddr echoServer--  -- Client-  forkIO $ do-    localNode <- newLocalNode transport initRemoteTable-    server <- readMVar echoAddr--    runProcess localNode $ do-      pid <- getSelfPid-      send server (pid, "foo")-      "foo" <- expect-      send server (pid, "baz")-      "baz" <- expect-      send server (pid, "bar")-      Nothing <- (expectTimeout 100000) :: Process (Maybe Double)-      liftIO $ putMVar clientDone ()--  takeMVar clientDone-  where handleMessage :: AbstractMessage -> Process (Maybe ())-        handleMessage msg =-          maybeHandleMessage msg (\(pid :: ProcessId, (m :: String))-                                  -> do { send pid m; return () })---- Test 'receiveChanTimeout'-testReceiveChanTimeout :: NT.Transport -> Assertion-testReceiveChanTimeout transport = do-  done <- newEmptyMVar-  sendPort <- newEmptyMVar--  forkTry $ do-    localNode <- newLocalNode transport initRemoteTable-    runProcess localNode $ do-      -- Create a typed channel-      (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)-      liftIO $ putMVar sendPort sp--      -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second-      Nothing <- receiveChanTimeout 1000000 rp--      -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds-      Just True <- receiveChanTimeout 1000000 rp--      -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing-      Nothing <- receiveChanTimeout 0 rp--      -- Again, but now there is a message available-      liftIO $ threadDelay 1000000-      Just False <- receiveChanTimeout 0 rp--      liftIO $ putMVar done ()--  forkTry $ do-    localNode <- newLocalNode transport initRemoteTable-    runProcess localNode $ do-      sp <- liftIO $ readMVar sendPort--      liftIO $ threadDelay 1500000-      sendChan sp True--      liftIO $ threadDelay 500000-      sendChan sp False--  takeMVar done---- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan-testReceiveChanFeatures :: NT.Transport -> Assertion-testReceiveChanFeatures transport = do-  done <- newEmptyMVar--  forkTry $ do-    localNode <- newLocalNode transport initRemoteTable-    runProcess localNode $ do-      (spInt,  rpInt)  <- newChan :: Process (SendPort Int, ReceivePort Int)-      (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)--      -- Test Functor instance--      sendChan spInt 2-      sendChan spBool False--      rp1 <- mergePortsBiased [even <$> rpInt, rpBool]--      True <- receiveChan rp1-      False <- receiveChan rp1--      -- Test Applicative instance--      sendChan spInt 3-      sendChan spInt 4--      let rp2 = pure (+) <*> rpInt <*> rpInt--      7 <- receiveChan rp2--      -- Test Alternative instance--      sendChan spInt 3-      sendChan spBool True--      let rp3 = (even <$> rpInt) <|> rpBool--      False <- receiveChan rp3-      True <- receiveChan rp3--      -- Test Monad instance--      sendChan spBool True-      sendChan spBool False-      sendChan spInt 5--      let rp4 :: ReceivePort Int-          rp4 = do b <- rpBool-                   if b-                     then rpInt-                     else return 7--      5 <- receiveChan rp4-      7 <- receiveChan rp4--      liftIO $ putMVar done ()--  takeMVar done--testKillLocal :: NT.Transport -> Assertion-testKillLocal transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  pid <- forkProcess localNode $ do-    liftIO $ threadDelay 1000000--  runProcess localNode $ do-    ref <- monitor pid-    us <- getSelfPid-    kill pid "TestKill"-    ProcessMonitorNotification ref' pid' (DiedException ex) <- expect-    True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"-    liftIO $ putMVar done ()--  takeMVar done--testKillRemote :: NT.Transport -> Assertion-testKillRemote transport = do-  node1 <- newLocalNode transport initRemoteTable-  node2 <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  pid <- forkProcess node1 $ do-    liftIO $ threadDelay 1000000--  runProcess node2 $ do-    ref <- monitor pid-    us <- getSelfPid-    kill pid "TestKill"-    ProcessMonitorNotification ref' pid' (DiedException reason) <- expect-    True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"-    liftIO $ putMVar done ()--  takeMVar done--testCatchesExit :: NT.Transport -> Assertion-testCatchesExit transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  _ <- forkProcess localNode $ do-      (die ("foobar", 123 :: Int))-      `catchesExit` [-           (\_ m -> maybeHandleMessage m (\(_ :: String) -> return ()))-         , (\_ m -> maybeHandleMessage m (\(_ :: Maybe Int) -> return ()))-         , (\_ m -> maybeHandleMessage m (\(_ :: String, _ :: Int)-                    -> (liftIO $ putMVar done ()) >> return ()))-         ]--  takeMVar done--testCatches :: NT.Transport -> Assertion-testCatches transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  _ <- forkProcess localNode $ do-    node <- getSelfNode-    (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))-    `catches` [-        Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())-      ]--  takeMVar done--testDie :: NT.Transport -> Assertion-testDie transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  _ <- forkProcess localNode $ do-      (die ("foobar", 123 :: Int))-      `catchExit` \_from reason -> do-        -- TODO: should verify that 'from' has the right value-        True <- return $ reason == ("foobar", 123 :: Int)-        liftIO $ putMVar done ()--  takeMVar done--testPrettyExit :: NT.Transport -> Assertion-testPrettyExit transport = do-  localNode <- newLocalNode transport initRemoteTable-  done <- newEmptyMVar--  _ <- forkProcess localNode $ do-      (die "timeout")-      `catch` \ex@(ProcessExitException from _) ->-        let expected = "exit-from=" ++ (show from)-        in do-          True <- return $ (show ex) == expected-          liftIO $ putMVar done ()--  takeMVar done--testExitLocal :: NT.Transport -> Assertion-testExitLocal transport = do-  localNode <- newLocalNode transport initRemoteTable-  supervisedDone <- newEmptyMVar-  supervisorDone <- newEmptyMVar--  pid <- forkProcess localNode $ do-    (liftIO $ threadDelay 100000)-      `catchExit` \_from reason -> do-        -- TODO: should verify that 'from' has the right value-        True <- return $ reason == "TestExit"-        liftIO $ putMVar supervisedDone ()--  runProcess localNode $ do-    ref <- monitor pid-    exit pid "TestExit"-    -- This time the client catches the exception, so it dies normally-    ProcessMonitorNotification ref' pid' DiedNormal <- expect-    True <- return $ ref == ref' && pid == pid'-    liftIO $ putMVar supervisorDone ()--  takeMVar supervisedDone-  takeMVar supervisorDone--testExitRemote :: NT.Transport -> Assertion-testExitRemote transport = do-  node1 <- newLocalNode transport initRemoteTable-  node2 <- newLocalNode transport initRemoteTable-  supervisedDone <- newEmptyMVar-  supervisorDone <- newEmptyMVar--  pid <- forkProcess node1 $ do-    (liftIO $ threadDelay 100000)-      `catchExit` \_from reason -> do-        -- TODO: should verify that 'from' has the right value-        True <- return $ reason == "TestExit"-        liftIO $ putMVar supervisedDone ()--  runProcess node2 $ do-    ref <- monitor pid-    exit pid "TestExit"-    ProcessMonitorNotification ref' pid' DiedNormal <- expect-    True <- return $ ref == ref' && pid == pid'-    liftIO $ putMVar supervisorDone ()--  takeMVar supervisedDone-  takeMVar supervisorDone--tests :: (NT.Transport, TransportInternals)  -> [Test]-tests (transport, transportInternals) = [-    testGroup "Basic features" [-        testCase "Ping"                (testPing                transport)-      , testCase "Math"                (testMath                transport)-      , testCase "Timeout"             (testTimeout             transport)-      , testCase "Timeout0"            (testTimeout0            transport)-      , testCase "SendToTerminated"    (testSendToTerminated    transport)-      , testCase "TypedChannnels"      (testTypedChannels       transport)-      , testCase "MergeChannels"       (testMergeChannels       transport)-      , testCase "Terminate"           (testTerminate           transport)-      , testCase "Registry"            (testRegistry            transport)-      , testCase "RemoteRegistry"      (testRemoteRegistry      transport)-      , testCase "SpawnLocal"          (testSpawnLocal          transport)-      , testCase "MatchAny"            (testMatchAny            transport)-      , testCase "MatchAnyHandle"      (testMatchAnyHandle      transport)-      , testCase "MatchAnyNoHandle"    (testMatchAnyNoHandle    transport)-      , testCase "MatchAnyIf"          (testMatchAnyIf          transport)-      , testCase "ReceiveChanTimeout"  (testReceiveChanTimeout  transport)-      , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)-      , testCase "KillLocal"           (testKillLocal           transport)-      , testCase "KillRemote"          (testKillRemote          transport)-      , testCase "Die"                 (testDie                 transport)-      , testCase "PrettyExit"          (testPrettyExit          transport)-      , testCase "CatchesExit"         (testCatchesExit         transport)-      , testCase "Catches"             (testCatches             transport)-      , testCase "ExitLocal"           (testExitLocal           transport)-      , testCase "ExitRemote"          (testExitRemote          transport)-      ]-  , testGroup "Monitoring and Linking" [-      -- Monitoring processes-      ---      -- The "missing" combinations in the list below don't make much sense, as-      -- we cannot guarantee that the monitor reply or link exception will not-      -- happen before the unmonitor or unlink-      testCase "MonitorUnreachable"           (testMonitorUnreachable         transport True  False)-    , testCase "MonitorNormalTermination"     (testMonitorNormalTermination   transport True  False)-    , testCase "MonitorAbnormalTermination"   (testMonitorAbnormalTermination transport True  False)-    , testCase "MonitorLocalDeadProcess"      (testMonitorLocalDeadProcess    transport True  False)-    , testCase "MonitorRemoteDeadProcess"     (testMonitorRemoteDeadProcess   transport True  False)-    , testCase "MonitorDisconnect"            (testMonitorDisconnect          transport True  False)-    , testCase "LinkUnreachable"              (testMonitorUnreachable         transport False False)-    , testCase "LinkNormalTermination"        (testMonitorNormalTermination   transport False False)-    , testCase "LinkAbnormalTermination"      (testMonitorAbnormalTermination transport False False)-    , testCase "LinkLocalDeadProcess"         (testMonitorLocalDeadProcess    transport False False)-    , testCase "LinkRemoteDeadProcess"        (testMonitorRemoteDeadProcess   transport False False)-    , testCase "LinkDisconnect"               (testMonitorDisconnect          transport False False)-    , testCase "UnmonitorNormalTermination"   (testMonitorNormalTermination   transport True  True)-    , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True  True)-    , testCase "UnmonitorDisconnect"          (testMonitorDisconnect          transport True  True)-    , testCase "UnlinkNormalTermination"      (testMonitorNormalTermination   transport False True)-    , testCase "UnlinkAbnormalTermination"    (testMonitorAbnormalTermination transport False True)-    , testCase "UnlinkDisconnect"             (testMonitorDisconnect          transport False True)-      -- Monitoring nodes and channels-    , testCase "MonitorNode"                  (testMonitorNode                transport)-    , testCase "MonitorChannel"               (testMonitorChannel             transport)-      -- Reconnect-    , testCase "Reconnect"                    (testReconnect                  transport transportInternals)-    ]-  ]--main :: IO ()-main = do-  Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters-  defaultMain (tests transport)
− tests/TestClosure.hs
@@ -1,480 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Main where--import Data.ByteString.Lazy (empty)-import Data.Typeable (Typeable)-import Control.Monad (join, replicateM, forever, replicateM_, void)-import Control.Exception (IOException, throw)-import Control.Concurrent (forkIO, threadDelay)-import Control.Concurrent.MVar-  ( MVar-  , newEmptyMVar-  , readMVar-  , takeMVar-  , putMVar-  , modifyMVar_-  , newMVar-  )-import Control.Applicative ((<$>))-import System.Random (randomIO)-import Network.Transport (Transport)-import Network.Transport.TCP-  ( createTransportExposeInternals-  , defaultTCPParameters-  , TransportInternals(socketBetween)-  )-import Network.Socket (sClose)-import Control.Distributed.Process-import Control.Distributed.Process.Closure-import Control.Distributed.Process.Node-import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress))-import Control.Distributed.Static (staticLabel, staticClosure)--import Test.HUnit (Assertion)-import Test.Framework (Test, defaultMain)-import Test.Framework.Providers.HUnit (testCase)------------------------------------------------------------------------------------- Supporting definitions                                                     -------------------------------------------------------------------------------------quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)-quintuple a b c d e = (a, b, c, d, e)--sdictInt :: SerializableDict Int-sdictInt = SerializableDict--factorial :: Int -> Process Int-factorial 0 = return 1-factorial n = (n *) <$> factorial (n - 1)--addInt :: Int -> Int -> Int-addInt x y = x + y--putInt :: Int -> MVar Int -> IO ()-putInt = flip putMVar--sendPid :: ProcessId -> Process ()-sendPid toPid = do-  fromPid <- getSelfPid-  send toPid fromPid--wait :: Int -> Process ()-wait = liftIO . threadDelay--isPrime :: Integer -> Process Bool-isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]-  where-    sieve :: [Integer] -> [Integer]-    sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]-    sieve [] = error "Uh oh -- we've run out of primes"---- | First argument indicates empty closure environment-typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()-typedPingServer () rport = forever $ do-  sport <- receiveChan rport-  sendChan sport ()--signal :: ProcessId -> Process ()-signal pid = send pid ()--remotable [ 'factorial-          , 'addInt-          , 'putInt-          , 'sendPid-          , 'sdictInt-          , 'wait-          , 'typedPingServer-          , 'isPrime-          , 'quintuple-          , 'signal-          ]--randomElement :: [a] -> IO a-randomElement xs = do-  ix <- randomIO-  return (xs !! (ix `mod` length xs))--remotableDecl [-    [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;-        dfib (_, reply, 0) = sendChan reply 0-        dfib (_, reply, 1) = sendChan reply 1-        dfib (nids, reply, n) = do-          nid1 <- liftIO $ randomElement nids-          nid2 <- liftIO $ randomElement nids-          (sport, rport) <- newChan-          spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)-          spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)-          n1 <- receiveChan rport-          n2 <- receiveChan rport-          sendChan reply $ n1 + n2-      |]-  ]---- Just try creating a static polymorphic value-staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)-                => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))-staticQuintuple = $(mkStatic 'quintuple)--factorialClosure :: Int -> Closure (Process Int)-factorialClosure = $(mkClosure 'factorial)--addIntClosure :: Int -> Closure (Int -> Int)-addIntClosure = $(mkClosure 'addInt)--putIntClosure :: Int -> Closure (MVar Int -> IO ())-putIntClosure = $(mkClosure 'putInt)--sendPidClosure :: ProcessId -> Closure (Process ())-sendPidClosure = $(mkClosure 'sendPid)--sendFac :: Int -> ProcessId -> Closure (Process ())-sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid--factorialOf :: Closure (Int -> Process Int)-factorialOf = staticClosure $(mkStatic 'factorial)--factorial' :: Int -> Closure (Process Int)-factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf--waitClosure :: Int -> Closure (Process ())-waitClosure = $(mkClosure 'wait)--simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()-simulateNetworkFailure transportInternals fr to = liftIO $ do-  threadDelay 10000-  sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)-  sClose sock-  threadDelay 10000------------------------------------------------------------------------------------- The tests proper                                                           -------------------------------------------------------------------------------------testUnclosure :: Transport -> RemoteTable -> Assertion-testUnclosure transport rtable = do-  node <- newLocalNode transport rtable-  done <- newEmptyMVar-  forkProcess node $ do-    120 <- join . unClosure $ factorialClosure 5-    liftIO $ putMVar done ()-  takeMVar done--testBind :: Transport -> RemoteTable -> Assertion-testBind transport rtable = do-  node <- newLocalNode transport rtable-  done <- newEmptyMVar-  runProcess node $ do-    us <- getSelfPid-    join . unClosure $ sendFac 6 us-    (720 :: Int) <- expect-    liftIO $ putMVar done ()-  takeMVar done--testSendPureClosure :: Transport -> RemoteTable -> Assertion-testSendPureClosure transport rtable = do-  serverAddr <- newEmptyMVar-  serverDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    addr <- forkProcess node $ do-      cl <- expect-      fn <- unClosure cl :: Process (Int -> Int)-      13 <- return $ fn 6-      liftIO $ putMVar serverDone ()-    putMVar serverAddr addr--  forkIO $ do-    node <- newLocalNode transport rtable-    theirAddr <- readMVar serverAddr-    runProcess node $ send theirAddr (addIntClosure 7)--  takeMVar serverDone--testSendIOClosure :: Transport -> RemoteTable -> Assertion-testSendIOClosure transport rtable = do-  serverAddr <- newEmptyMVar-  serverDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    addr <- forkProcess node $ do-      cl <- expect-      io <- unClosure cl :: Process (MVar Int -> IO ())-      liftIO $ do-        someMVar <- newEmptyMVar-        io someMVar-        5 <- readMVar someMVar-        putMVar serverDone ()-    putMVar serverAddr addr--  forkIO $ do-    node <- newLocalNode transport rtable-    theirAddr <- readMVar serverAddr-    runProcess node $ send theirAddr (putIntClosure 5)--  takeMVar serverDone--testSendProcClosure :: Transport -> RemoteTable -> Assertion-testSendProcClosure transport rtable = do-  serverAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    addr <- forkProcess node $ do-      cl <- expect-      pr <- unClosure cl :: Process (Int -> Process ())-      pr 5-    putMVar serverAddr addr--  forkIO $ do-    node <- newLocalNode transport rtable-    theirAddr <- readMVar serverAddr-    runProcess node $ do-      pid <- getSelfPid-      send theirAddr (cpSend $(mkStatic 'sdictInt) pid)-      5 <- expect :: Process Int-      liftIO $ putMVar clientDone ()--  takeMVar clientDone--testSpawn :: Transport -> RemoteTable -> Assertion-testSpawn transport rtable = do-  serverNodeAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    putMVar serverNodeAddr (localNodeId node)--  forkIO $ do-    node <- newLocalNode transport rtable-    nid <- readMVar serverNodeAddr-    runProcess node $ do-      pid   <- getSelfPid-      pid'  <- spawn nid (sendPidClosure pid)-      pid'' <- expect-      True <- return $ pid' == pid''-      liftIO $ putMVar clientDone ()--  takeMVar clientDone--testCall :: Transport -> RemoteTable -> Assertion-testCall transport rtable = do-  serverNodeAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    putMVar serverNodeAddr (localNodeId node)--  forkIO $ do-    node <- newLocalNode transport rtable-    nid <- readMVar serverNodeAddr-    runProcess node $ do-      (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)-      liftIO $ putMVar clientDone ()--  takeMVar clientDone--testCallBind :: Transport -> RemoteTable -> Assertion-testCallBind transport rtable = do-  serverNodeAddr <- newEmptyMVar-  clientDone <- newEmptyMVar--  forkIO $ do-    node <- newLocalNode transport rtable-    putMVar serverNodeAddr (localNodeId node)--  forkIO $ do-    node <- newLocalNode transport rtable-    nid <- readMVar serverNodeAddr-    runProcess node $ do-      (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)-      liftIO $ putMVar clientDone ()--  takeMVar clientDone--testSeq :: Transport -> RemoteTable -> Assertion-testSeq transport rtable = do-  node <- newLocalNode transport rtable-  done <- newEmptyMVar-  runProcess node $ do-    us <- getSelfPid-    join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us-    120 :: Int <- expect-    720 :: Int <- expect-    liftIO $ putMVar done ()-  takeMVar done---- Test 'spawnSupervised'------ Set up a supervisor, spawn a child, then have a third process monitor the--- child. The supervisor then throws an exception, the child dies because it--- was linked to the supervisor, and the third process notices that the child--- dies.-testSpawnSupervised :: Transport -> RemoteTable -> Assertion-testSpawnSupervised transport rtable = do-    [node1, node2]       <- replicateM 2 $ newLocalNode transport rtable-    [superPid, childPid] <- replicateM 2 $ newEmptyMVar-    thirdProcessDone     <- newEmptyMVar--    forkProcess node1 $ do-      us <- getSelfPid-      liftIO $ putMVar superPid us-      (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)-      liftIO $ do-        putMVar childPid child-        threadDelay 500000 -- Give the child a chance to link to us-        throw supervisorDeath--    forkProcess node2 $ do-      [super, child] <- liftIO $ mapM readMVar [superPid, childPid]-      ref <- monitor child-      ProcessMonitorNotification ref' pid' (DiedException e) <- expect-      True <- return $ ref' == ref-                    && pid' == child-                    && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))-      liftIO $ putMVar thirdProcessDone ()--    takeMVar thirdProcessDone-  where-    supervisorDeath :: IOException-    supervisorDeath = userError "Supervisor died"--testSpawnInvalid :: Transport -> RemoteTable -> Assertion-testSpawnInvalid transport rtable = do-  node <- newLocalNode transport rtable-  done <- newEmptyMVar-  forkProcess node $ do-    (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)-    ProcessMonitorNotification ref' pid' _reason <- expect-    -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure-    True <- return $ ref' == ref && pid == pid'-    liftIO $ putMVar done ()-  takeMVar done--testClosureExpect :: Transport -> RemoteTable -> Assertion-testClosureExpect transport rtable = do-  node <- newLocalNode transport rtable-  done <- newEmptyMVar-  runProcess node $ do-    nodeId <- getSelfNode-    us     <- getSelfPid-    them   <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us-    send them (1234 :: Int)-    (1234 :: Int) <- expect-    liftIO $ putMVar done ()-  takeMVar done--testSpawnChannel :: Transport -> RemoteTable -> Assertion-testSpawnChannel transport rtable = do-  done <- newEmptyMVar-  [node1, node2] <- replicateM 2 $ newLocalNode transport rtable--  forkProcess node1 $ do-    pingServer <- spawnChannel-                    (sdictSendPort sdictUnit)-                    (localNodeId node2)-                    ($(mkClosure 'typedPingServer) ())-    (sendReply, receiveReply) <- newChan-    sendChan pingServer sendReply-    receiveChan receiveReply-    liftIO $ putMVar done ()--  takeMVar done--testTDict :: Transport -> RemoteTable -> Assertion-testTDict transport rtable = do-  done <- newEmptyMVar-  [node1, node2] <- replicateM 2 $ newLocalNode transport rtable-  forkProcess node1 $ do-    True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))-    liftIO $ putMVar done ()-  takeMVar done--testFib :: Transport -> RemoteTable -> Assertion-testFib transport rtable = do-  nodes <- replicateM 4 $ newLocalNode transport rtable-  done <- newEmptyMVar--  forkProcess (head nodes) $ do-    (sport, rport) <- newChan-    spawnLocal $ dfib (map localNodeId nodes, sport, 10)-    55 <- receiveChan rport :: Process Integer-    liftIO $ putMVar done ()--  takeMVar done--testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion-testSpawnReconnect transport rtable transportInternals = do-  [node1, node2] <- replicateM 2 $ newLocalNode transport rtable-  let nid1 = localNodeId node1-      nid2 = localNodeId node2-  done <- newEmptyMVar-  iv <- newMVar (0 :: Int)--  incr <- forkProcess node1 $ forever $ do-    () <- expect-    liftIO $ modifyMVar_ iv (return . (+ 1))--  forkProcess node2 $ do-    _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)-    simulateNetworkFailure transportInternals nid2 nid1-    _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)-    _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)--    liftIO $ threadDelay 100000--    count <- liftIO $ takeMVar iv-    True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'--    liftIO $ putMVar done ()--  takeMVar done---- | 'spawn' used to ave a race condition which would be triggered if the--- spawning process terminates immediately after spawning-testSpawnTerminate :: Transport -> RemoteTable -> Assertion-testSpawnTerminate transport rtable = do-  slave  <- newLocalNode transport rtable-  master <- newLocalNode transport rtable-  masterDone <- newEmptyMVar--  runProcess master $ do-    us <- getSelfPid-    replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us-    replicateM_ 1000 $ (expect :: Process ())-    liftIO $ putMVar masterDone ()--  takeMVar masterDone--tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]-tests (transport, transportInternals) rtable = [-    testCase "Unclosure"       (testUnclosure       transport rtable)-  , testCase "Bind"            (testBind            transport rtable)-  , testCase "SendPureClosure" (testSendPureClosure transport rtable)-  , testCase "SendIOClosure"   (testSendIOClosure   transport rtable)-  , testCase "SendProcClosure" (testSendProcClosure transport rtable)-  , testCase "Spawn"           (testSpawn           transport rtable)-  , testCase "Call"            (testCall            transport rtable)-  , testCase "CallBind"        (testCallBind        transport rtable)-  , testCase "Seq"             (testSeq             transport rtable)-  , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)-  , testCase "SpawnInvalid"    (testSpawnInvalid    transport rtable)-  , testCase "ClosureExpect"   (testClosureExpect   transport rtable)-  , testCase "SpawnChannel"    (testSpawnChannel    transport rtable)-  , testCase "TDict"           (testTDict           transport rtable)-  , testCase "Fib"             (testFib             transport rtable)-  , testCase "SpawnTerminate"  (testSpawnTerminate  transport rtable)-  , testCase "SpawnReconnect"  (testSpawnReconnect  transport rtable transportInternals)-  ]---main :: IO ()-main = do-  Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters-  let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable-  defaultMain (tests transport rtable)
− tests/TestStats.hs
@@ -1,162 +0,0 @@-{-# LANGUAGE DeriveDataTypeable        #-}-{-# OPTIONS_GHC -fno-warn-orphans      #-}-module Main where--import Control.Concurrent.MVar-  ( MVar-  , newEmptyMVar-  , putMVar-  , takeMVar-  )-import Control.Distributed.Process-import Control.Distributed.Process.Node-  ( forkProcess-  , newLocalNode-  , initRemoteTable-  , closeLocalNode-  , LocalNode)-import Data.Binary()-import Data.Typeable()-import Network.Transport.TCP-import Prelude hiding (catch, log)-import Test.Framework-  ( Test-  , defaultMain-  , testGroup-  )-import Test.HUnit (Assertion)-import Test.HUnit.Base (assertBool)-import Test.Framework.Providers.HUnit (testCase)---- these utilities have been cribbed from distributed-process-platform--- we should really find a way to share them...---- | A mutable cell containing a test result.-type TestResult a = MVar a--delayedAssertion :: (Eq a) => String -> LocalNode -> a ->-                    (TestResult a -> Process ()) -> Assertion-delayedAssertion note localNode expected testProc = do-  result <- newEmptyMVar-  _ <- forkProcess localNode $ testProc result-  assertComplete note result expected--assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()-assertComplete msg mv a = do-  b <- takeMVar mv-  assertBool msg (a == b)--stash :: TestResult a -> a -> Process ()-stash mvar x = liftIO $ putMVar mvar x----------testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()-testLocalDeadProcessInfo result = do-  pid <- spawnLocal $ do "finish" <- expect; return ()-  mref <- monitor pid-  send pid "finish"-  _ <- receiveWait [-      matchIf (\(ProcessMonitorNotification ref' pid' r) ->-                    ref' == mref && pid' == pid && r == DiedNormal)-              (\p -> return p)-    ]-  getProcessInfo pid >>= stash result--testLocalLiveProcessInfo :: TestResult Bool -> Process ()-testLocalLiveProcessInfo result = do-  self <- getSelfPid-  node <- getSelfNode-  register "foobar" self--  mon <- liftIO $ newEmptyMVar-  -- TODO: we can't get the mailbox's length-  -- mapM (send self) ["hello", "there", "mr", "process"]-  pid <- spawnLocal $ do-       link self-       mRef <- monitor self-       stash mon mRef-       "die" <- expect-       return ()--  monRef <- liftIO $ takeMVar mon--  mpInfo <- getProcessInfo self-  case mpInfo of-    Nothing -> stash result False-    Just p  -> verifyPInfo p pid monRef node-  where verifyPInfo :: ProcessInfo-                    -> ProcessId-                    -> MonitorRef-                    -> NodeId-                    -> Process ()-        verifyPInfo pInfo pid mref node =-          stash result $ infoNode pInfo     == node           &&-                         infoLinks pInfo    == [pid]          &&-                         infoMonitors pInfo == [(pid, mref)]  &&---                         infoMessageQueueLength pInfo == Just 4 &&-                         infoRegisteredNames pInfo == ["foobar"]--testRemoteLiveProcessInfo :: LocalNode -> Assertion-testRemoteLiveProcessInfo node1 = do-  serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)-  liftIO $ launchRemote serverAddr-  serverPid <- liftIO $ takeMVar serverAddr-  withActiveRemote node1 $ \result -> do-    self <- getSelfPid-    link serverPid-    -- our send op shouldn't overtake link or monitor requests AFAICT-    -- so a little table tennis should get us synchronised properly-    send serverPid (self, "ping")-    "pong" <- expect-    pInfo <- getProcessInfo serverPid-    stash result $ pInfo /= Nothing-  where -    launchRemote :: MVar ProcessId -> IO ()-    launchRemote locMV = do-        node2 <- liftIO $ mkNode "8082"-        _ <- liftIO $ forkProcess node2 $ do-            self <- getSelfPid-            liftIO $ putMVar locMV self-            _ <- receiveWait [-                  match (\(pid, "ping") -> send pid "pong") -                ]-            "stop" <- expect-            return ()-        return ()--    withActiveRemote :: LocalNode-                     -> ((TestResult Bool -> Process ()) -> Assertion)-    withActiveRemote n = do-      a <- delayedAssertion "getProcessInfo remotePid failed" n True-      return a--tests :: LocalNode -> IO [Test]-tests node1 = do-  return [-    testGroup "Process Info" [-        testCase "testLocalDeadProcessInfo"-            (delayedAssertion-             "expected dead process-info to be ProcessInfoNone"-             node1 (Nothing) testLocalDeadProcessInfo)-      , testCase "testLocalLiveProcessInfo"-            (delayedAssertion-             "expected process-info to be correctly populated"-             node1 True testLocalLiveProcessInfo)-      , testCase "testRemoveLiveProcessInfo"-                 (testRemoteLiveProcessInfo node1)-    ] ]--mkNode :: String -> IO LocalNode-mkNode port = do-  Right (transport1, _) <- createTransportExposeInternals-                                    "127.0.0.1" port defaultTCPParameters-  newLocalNode transport1 initRemoteTable--main :: IO ()-main = do-  node1 <- mkNode "8081"-  testData <- tests node1-  defaultMain testData-  closeLocalNode node1-  return ()