1{-# LANGUAGE RecordWildCards #-}
5module Control.Distributed.Process.Node
15-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
17#if ! MIN_VERSION_base(4,6,0)
18import Prelude hiding (catch)
21import System.IO (fixIO, hPutStrLn, stderr)
22import System.Mem.Weak (Weak, deRefWeak)
23import qualified Data.ByteString.Lazy as BSL (fromChunks)
24import Data.Binary (decode)
26import qualified Data.Map as Map
38import qualified Data.Set as Set
45import Data.Foldable (forM_)
46import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
47import Data.Typeable (Typeable)
48import Control.Category ((>>>))
49import Control.Applicative ((<$>))
50import Control.Monad (void, when)
51import Control.Monad.IO.Class (MonadIO, liftIO)
52import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
53import qualified Control.Monad.State.Strict as StateT (get, put)
54import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
55import Control.Exception
57 , AsyncException(ThreadKilled)
62import qualified Control.Exception as Exception (Handler(..), catches, finally)
63import Control.Concurrent (forkIO, myThreadId)
64import Control.Distributed.Process.Internal.StrictMVar
74import Control.Concurrent.Chan (newChan, writeChan, readChan)
75import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
76import Control.Concurrent.STM
79import Control.Distributed.Process.Internal.CQueue
85import qualified Network.Transport as NT
99 , Reliability(ReliableOrdered)
101import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
102import System.Random (randomIO)
103import Control.Distributed.Static (RemoteTable, Closure)
104import qualified Control.Distributed.Static as Static
108import Control.Distributed.Process.Internal.Types
116 , LocalProcessState(..)
128 , ProcessMonitorNotification(..)
129 , NodeMonitorNotification(..)
130 , PortMonitorNotification(..)
131 , ProcessExitException(..)
132 , ProcessLinkException(..)
133 , NodeLinkException(..)
134 , PortLinkException(..)
136 , DidUnlinkProcess(..)
146 , ProcessInfoNone(..)
153 , createUnencodedMessage
155 , firstNonReservedProcessId
156 , ImplicitReconnect(WithImplicitReconnect)
158import Control.Distributed.Process.Management.Agent
161import qualified Control.Distributed.Process.Management.Table as Table
163 , startTableCoordinator
165import qualified Control.Distributed.Process.Management.Trace.Remote as Trace
168import Control.Distributed.Process.Management.Trace.Tracer
171import Control.Distributed.Process.Management.Trace.Types
177import Control.Distributed.Process.Management.Types
180import Control.Distributed.Process.Serializable (Serializable)
181import Control.Distributed.Process.Internal.Messaging
184 , closeImplicitReconnections
187import Control.Distributed.Process.Internal.Primitives
196import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
197import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
198import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
199import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
205--------------------------------------------------------------------------------
207--------------------------------------------------------------------------------
209initRemoteTable :: RemoteTable
210initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
212-- | Initialize a new local node.
213newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
214newLocalNode transport rtable = do
215 mEndPoint <- NT.newEndPoint transport
217 Left ex -> throwIO ex
219 localNode <- createBareLocalNode endPoint rtable
220 startServiceProcesses localNode
223-- | Create a new local node (without any service processes running)
224createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
225createBareLocalNode endPoint rtable = do
227 state <- newMVar LocalNodeState
228 { _localProcesses = Map.empty
229 , _localPidCounter = firstNonReservedProcessId
230 , _localPidUnique = unq
231 , _localConnections = Map.empty
234 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
235 , localEndPoint = endPoint
237 , localCtrlChan = ctrlChan
238 , localEventBus = MxEventBusInitialising
239 , remoteTable = rtable
241 tracedNode <- startMxAgent node
243 -- Once the NC terminates, the endpoint isn't much use,
244 void $ forkIO $ Exception.finally (runNodeController tracedNode)
245 (NT.closeEndPoint (localEndPoint node))
247 -- whilst a closed/failing endpoint will terminate the NC
248 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
254 writeChan (localCtrlChan node) NCMsg
255 { ctrlMsgSender = NodeIdentifier (localNodeId node)
256 , ctrlMsgSignal = SigShutdown
259startMxAgent :: LocalNode -> IO LocalNode
260startMxAgent node = do
261 -- see note [tracer/forkProcess races]
262 let fork = forkProcess node
263 mv <- MVar.newEmptyMVar
264 pid <- fork $ mxAgentController fork mv
265 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
266 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
268startDefaultTracer :: LocalNode -> IO ()
269startDefaultTracer node' = do
270 let t = localEventBus node'
272 MxEventBus _ (Tracer pid _) _ _ -> do
273 runProcess node' $ register "trace.controller" pid
274 pid' <- forkProcess node' defaultTracer
275 enableTrace (localEventBus node') pid'
276 runProcess node' $ register "tracer.initial" pid'
279-- TODO: we need a better mechanism for defining and registering services
281-- | Start and register the service processes on a node
282startServiceProcesses :: LocalNode -> IO ()
283startServiceProcesses node = do
284 -- tracing /spawns/ relies on the tracer being enabled, but we start
285 -- the default tracer first, even though it might @nsend@ to the logger
286 -- before /that/ process has started - this is a totally harmless race
287 -- however, so we deliberably ignore it
288 startDefaultTracer node
289 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
290 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
291 logger <- forkProcess node loop
292 runProcess node $ register "logger" logger
294 fork = forkProcess node
298 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
299 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
301 , match $ \((time, string) :: (String, String)) -> do
302 -- this is a 'trace' message from the local node tracer
303 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
305 , match $ \(ch :: SendPort ()) -> -- a shutdown request
309-- | Force-close a local node
311-- TODO: for now we just close the associated endpoint
312closeLocalNode :: LocalNode -> IO ()
314 -- TODO: close all our processes, surely!?
315 NT.closeEndPoint (localEndPoint node)
317-- | Run a process on a local node and wait for it to finish
318runProcess :: LocalNode -> Process () -> IO ()
319runProcess node proc = do
322 void $ forkProcess node $ do
323 catch (proc `finally` liftIO (putMVar done ()))
324 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
327-- | Spawn a new process on a local node
328forkProcess :: LocalNode -> Process () -> IO ProcessId
329forkProcess node proc = modifyMVar (localState node) startProcess
331 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
333 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
334 , lpidUnique = st ^. localPidUnique
336 let pid = ProcessId { processNodeId = localNodeId node
337 , processLocalId = lpid
339 pst <- newMVar LocalProcessState { _monitorCounter = 0
341 , _channelCounter = 0
342 , _typedChannels = Map.empty
345 weakQueue <- mkWeakCQueue queue (return ())
346 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
347 let lproc = LocalProcess { processQueue = queue
348 , processWeakQ = weakQueue
351 , processThread = tid
355 reason <- Exception.catches
356 (runLocalProcess lproc proc >> return DiedNormal)
357 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
358 mMsg <- unwrapMessage msg :: IO (Maybe String)
360 Nothing -> return $ DiedException $ show ex
361 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
363 (return . DiedException . (show :: SomeException -> String)))]
365 -- [Unified: Table 4, rules termination and exiting]
366 modifyMVar_ (localState node) (cleanupProcess pid)
367 writeChan (localCtrlChan node) NCMsg
368 { ctrlMsgSender = ProcessIdentifier pid
369 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
373 -- see note [tracer/forkProcess races]
374 trace node (MxSpawned pid)
376 if lpidCounter lpid == maxBound
378 -- TODO: this doesn't look right at all - how do we know
379 -- that newUnique represents a process id that is available!?
380 newUnique <- randomIO
381 return ( (localProcessWithId lpid ^= Just lproc)
382 . (localPidCounter ^= firstNonReservedProcessId)
383 . (localPidUnique ^= newUnique)
388 return ( (localProcessWithId lpid ^= Just lproc)
389 . (localPidCounter ^: (+ 1))
394 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
395 cleanupProcess pid st = do
396 let pid' = ProcessIdentifier pid
397 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
398 mapM_ (NT.close . fst) (Map.elems affected)
399 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
400 . (localConnections ^= unaffected)
403-- note [tracer/forkProcess races]
405-- Our startTracing function uses forkProcess to start the trace controller
406-- process, and of course forkProcess attempts to call traceEvent once the
407-- process has started. This is harmless, as the localEventBus is not updated
408-- until /after/ the initial forkProcess completes, so the first call to
409-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
412--------------------------------------------------------------------------------
413-- Handle incoming messages --
414--------------------------------------------------------------------------------
416type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
420 | ToProc ProcessId (Weak (CQueue Message))
421 | ToChan TypedChannel
424data ConnectionState = ConnectionState {
425 _incoming :: !(Map NT.ConnectionId IncomingConnection)
426 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
429initConnectionState :: ConnectionState
430initConnectionState = ConnectionState {
431 _incoming = Map.empty
432 , _incomingFrom = Map.empty
435incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
436incoming = accessor _incoming (\conns st -> st { _incoming = conns })
438incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
439incomingAt cid = incoming >>> DAC.mapMaybe cid
441incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
442incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
444 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
446handleIncomingMessages :: LocalNode -> IO ()
447handleIncomingMessages node = go initConnectionState
449 go :: ConnectionState -> IO ()
451 event <- NT.receive endpoint
453 NT.ConnectionOpened cid rel theirAddr ->
454 if rel == NT.ReliableOrdered
456 trace node (MxConnected cid theirAddr)
458 (incomingAt cid ^= Just (theirAddr, Uninit))
459 . (incomingFrom theirAddr ^: Set.insert cid)
462 else invalidRequest cid st
463 NT.Received cid payload ->
464 case st ^. incomingAt cid of
465 Just (_, ToProc pid weakQueue) -> do
466 mQueue <- deRefWeak weakQueue
467 forM_ mQueue $ \queue -> do
468 -- TODO: if we find that the queue is Nothing, should we remove
469 -- it from the NC state? (and same for channels, below)
470 let msg = payloadToMessage payload
471 enqueue queue msg -- 'enqueue' is strict
472 trace node (MxReceived pid msg)
474 Just (_, ToChan (TypedChannel chan')) -> do
475 mChan <- deRefWeak chan'
476 -- If mChan is Nothing, the process has given up the read end of
477 -- the channel and we simply ignore the incoming message
478 forM_ mChan $ \chan -> atomically $
479 -- We make sure the message is fully decoded when it is enqueued
480 writeTQueue chan $! decode (BSL.fromChunks payload)
482 Just (_, ToNode) -> do
483 let ctrlMsg = decode . BSL.fromChunks $ payload
484 writeChan ctrlChan $! ctrlMsg
486 Just (src, Uninit) ->
487 case decode (BSL.fromChunks payload) of
488 ProcessIdentifier pid -> do
489 let lpid = processLocalId pid
490 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
493 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
495 invalidRequest cid st
496 SendPortIdentifier chId -> do
497 let lcid = sendPortLocalId chId
498 lpid = processLocalId (sendPortProcessId chId)
499 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
502 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
505 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
507 invalidRequest cid st
509 invalidRequest cid st
510 NodeIdentifier nid ->
511 if nid == localNodeId node
512 then go (incomingAt cid ^= Just (src, ToNode) $ st)
513 else invalidRequest cid st
515 invalidRequest cid st
516 NT.ConnectionClosed cid ->
517 case st ^. incomingAt cid of
519 invalidRequest cid st
521 trace node (MxDisconnected cid src)
522 go ( (incomingAt cid ^= Nothing)
523 . (incomingFrom src ^: Set.delete cid)
526 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
527 -- [Unified table 9, rule node_disconnect]
528 let nid = NodeIdentifier $ NodeId theirAddr
529 writeChan ctrlChan NCMsg
530 { ctrlMsgSender = nid
531 , ctrlMsgSignal = Died nid DiedDisconnect
533 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
534 closeImplicitReconnections node nid
535 go ( (incomingFrom theirAddr ^= Set.empty)
536 . (incoming ^: Map.filterWithKey (const . notLost))
539 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
540 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
541 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
542 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
545 NT.ReceivedMulticast _ _ ->
546 -- If we received a multicast message, something went horribly wrong
547 -- and we just give up
548 fail "Cloud Haskell fatal error: received unexpected multicast"
550 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
551 invalidRequest cid st = do
552 -- TODO: We should treat this as a fatal error on the part of the remote
553 -- node. That is, we should report the remote node as having died, and we
554 -- should close incoming connections (this requires a Transport layer
556 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
558 go ( incomingAt cid ^= Nothing
562 state = localState node
563 endpoint = localEndPoint node
564 ctrlChan = localCtrlChan node
566--------------------------------------------------------------------------------
567-- Top-level access to the node controller --
568--------------------------------------------------------------------------------
570runNodeController :: LocalNode -> IO ()
572 runReaderT (evalStateT (unNC nodeController) initNCState)
574--------------------------------------------------------------------------------
575-- Internal data types --
576--------------------------------------------------------------------------------
578data NCState = NCState
579 { -- Mapping from remote processes to linked local processes
580 _links :: !(Map Identifier (Set ProcessId))
581 -- Mapping from remote processes to monitoring local processes
582 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
583 -- Process registry: names and where they live, mapped to the PIDs
584 , _registeredHere :: !(Map String ProcessId)
585 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
588newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
589 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
591initNCState :: NCState
592initNCState = NCState { _links = Map.empty
593 , _monitors = Map.empty
594 , _registeredHere = Map.empty
595 , _registeredOnNodes = Map.empty
598-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
599-- type is deliberately not exported so it cannot be caught explicitly.
600data ProcessKillException =
601 ProcessKillException !ProcessId !String
604instance Exception ProcessKillException
605instance Show ProcessKillException where
606 show (ProcessKillException pid reason) =
607 "killed-by=" ++ show pid ++ ",reason=" ++ reason
609--------------------------------------------------------------------------------
610-- Tracing/Debugging --
611--------------------------------------------------------------------------------
613-- [Issue #104 / DP-13]
615traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
616traceNotifyDied node ident reason =
617 -- TODO: sendPortDied notifications
618 liftIO $ withLocalTracer node $ \t ->
620 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
621 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
624traceEventFmtIO :: LocalNode
628traceEventFmtIO node fmt args =
629 withLocalTracer node $ \t -> traceLogFmt t fmt args
631trace :: LocalNode -> MxEvent -> IO ()
632trace node ev = withLocalTracer node $ \t -> traceEvent t ev
634withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
635withLocalTracer node act = act (localEventBus node)
637--------------------------------------------------------------------------------
638-- Core functionality --
639--------------------------------------------------------------------------------
642nodeController :: NC ()
646 msg <- liftIO $ readChan (localCtrlChan node)
648 -- [Unified: Table 7, rule nc_forward]
649 case destNid (ctrlMsgSignal msg) of
650 Just nid' | nid' /= localNodeId node ->
651 liftIO $ sendBinary node
653 (NodeIdentifier nid')
654 WithImplicitReconnect
660 NCMsg (ProcessIdentifier from) (Link them) ->
661 ncEffectMonitor from them Nothing
662 NCMsg (ProcessIdentifier from) (Monitor ref) ->
663 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
664 NCMsg (ProcessIdentifier from) (Unlink them) ->
665 ncEffectUnlink from them
666 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
667 ncEffectUnmonitor from ref
668 NCMsg _from (Died ident reason) ->
669 ncEffectDied ident reason
670 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
671 ncEffectSpawn from proc ref
672 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
673 ncEffectRegister from label atnode pid force
674 NCMsg (ProcessIdentifier from) (WhereIs label) ->
675 ncEffectWhereIs from label
676 NCMsg _ (NamedSend label msg') ->
677 ncEffectNamedSend label msg'
678 NCMsg _ (LocalSend to msg') ->
679 ncEffectLocalSend node to msg'
680 NCMsg _ (LocalPortSend to msg') ->
681 ncEffectLocalPortSend to msg'
682 NCMsg (ProcessIdentifier from) (Kill to reason) ->
683 ncEffectKill from to reason
684 NCMsg (ProcessIdentifier from) (Exit to reason) ->
685 ncEffectExit from to reason
686 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
687 ncEffectGetInfo from pid
688 NCMsg _ SigShutdown ->
690 NT.closeEndPoint (localEndPoint node)
691 `Exception.finally` throwIO ThreadKilled
692 -- ThreadKilled seems to make more sense than fail/error here
693 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
694 ncEffectGetNodeStats from nid
696 error $ "nodeController: unexpected message " ++ show unexpected
698-- [Unified: Table 10]
699ncEffectMonitor :: ProcessId -- ^ Who's watching?
700 -> Identifier -- ^ Who's being watched?
701 -> Maybe MonitorRef -- ^ 'Nothing' to link
703ncEffectMonitor from them mRef = do
706 if not (isLocal node them)
708 else isValidLocalIdentifier them
709 case (shouldLink, isLocal node (ProcessIdentifier from)) of
710 (True, _) -> -- [Unified: first rule]
712 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
713 Nothing -> modify' $ linksFor them ^: Set.insert from
714 (False, True) -> -- [Unified: second rule]
715 notifyDied from them DiedUnknownId mRef
716 (False, False) -> -- [Unified: third rule]
717 -- TODO: this is the right sender according to the Unified semantics,
718 -- but perhaps having 'them' as the sender would make more sense
719 -- (see also: notifyDied)
720 liftIO $ sendBinary node
721 (NodeIdentifier $ localNodeId node)
722 (NodeIdentifier $ processNodeId from)
723 WithImplicitReconnect
725 { ctrlMsgSender = NodeIdentifier (localNodeId node)
726 , ctrlMsgSignal = Died them DiedUnknownId
729-- [Unified: Table 11]
730ncEffectUnlink :: ProcessId -> Identifier -> NC ()
731ncEffectUnlink from them = do
733 when (isLocal node (ProcessIdentifier from)) $
735 ProcessIdentifier pid ->
736 postAsMessage from $ DidUnlinkProcess pid
737 NodeIdentifier nid ->
738 postAsMessage from $ DidUnlinkNode nid
739 SendPortIdentifier cid ->
740 postAsMessage from $ DidUnlinkPort cid
741 modify' $ linksFor them ^: Set.delete from
743-- [Unified: Table 11]
744ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
745ncEffectUnmonitor from ref = do
747 when (isLocal node (ProcessIdentifier from)) $
748 postAsMessage from $ DidUnmonitor ref
749 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
751-- [Unified: Table 12]
752ncEffectDied :: Identifier -> DiedReason -> NC ()
753ncEffectDied ident reason = do
755 traceNotifyDied node ident reason
756 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
757 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
759-- _registry :: !(Map (String,NodeId) ProcessId)
761 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
763 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
765 when (localOnly <= isLocal node (ProcessIdentifier us)) $
766 notifyDied us them reason Nothing
768 forM_ (Map.toList affectedMons) $ \(them, refs) ->
769 forM_ refs $ \(us, ref) ->
770 when (localOnly <= isLocal node (ProcessIdentifier us)) $
771 notifyDied us them reason (Just ref)
773 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
775 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
777 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
778 mapM (\(pid,nidlist) ->
779 case ident `impliesDeathOf` ProcessIdentifier pid of
781 do forM_ nidlist $ \(nid,_) ->
782 when (not $ isLocal node (NodeIdentifier nid))
783 (forwardNameDeath node nid)
785 False -> return $ Just (pid,nidlist) )
786 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
788 forwardNameDeath node nid =
789 liftIO $ sendBinary node
790 (NodeIdentifier $ localNodeId node)
791 (NodeIdentifier $ nid)
792 WithImplicitReconnect
794 { ctrlMsgSender = NodeIdentifier (localNodeId node)
795 , ctrlMsgSignal = Died ident reason
798-- [Unified: Table 13]
799ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
800ncEffectSpawn pid cProc ref = do
801 mProc <- unClosure cProc
802 -- If the closure does not exist, we spawn a process that throws an exception
803 -- This allows the remote node to find out what's happening
805 let proc = case mProc of
806 Left err -> fail $ "Error: Could not resolve closure: " ++ err
809 pid' <- liftIO $ forkProcess node proc
810 liftIO $ sendMessage node
811 (NodeIdentifier (localNodeId node))
812 (ProcessIdentifier pid)
813 WithImplicitReconnect
816-- Unified semantics does not explicitly describe how to implement 'register',
817-- but mentions it's "very similar to nsend" (Table 14)
818-- We send a response indicated if the operation is invalid
819ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
820ncEffectRegister from label atnode mPid reregistration = do
822 currentVal <- gets (^. registeredHereFor label)
825 Nothing -> -- unregister request
826 return $ isJust currentVal
827 Just thepid -> -- register request
828 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
829 return $ (isNothing currentVal /= reregistration) &&
830 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
831 if isLocal node (NodeIdentifier atnode)
832 then do when (isOk) $
833 do modify' $ registeredHereFor label ^= mPid
834 updateRemote node currentVal mPid
836 (Just p) -> liftIO $ trace node (MxRegistered p label)
837 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
838 liftIO $ sendMessage node
839 (NodeIdentifier (localNodeId node))
840 (ProcessIdentifier from)
841 WithImplicitReconnect
842 (RegisterReply label isOk)
844 case reregistration of
846 False -> flip incList
849 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
850 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
851 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
852 forward node (processNodeId newval) (Register label atnode (Just newval) False)
853 updateRemote node Nothing (Just newval) =
854 forward node (processNodeId newval) (Register label atnode (Just newval) False)
855 updateRemote node (Just oldval) Nothing =
856 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
857 updateRemote _ _ _ = return ()
858 maybeify f Nothing = unmaybeify $ f []
859 maybeify f (Just x) = unmaybeify $ f x
861 unmaybeify [] = Nothing
862 unmaybeify x = Just x
863 incList [] tag = [(tag,1)]
864 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
865 incList (x:xs) tag = x : incList xs tag
867 decList ((atag,1):xs) tag | atag == tag = xs
868 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
869 decList (x:xs) tag = x:decList xs tag
870 forward node to reg =
871 when (not $ isLocal node (NodeIdentifier to)) $
872 liftIO $ sendBinary node
873 (ProcessIdentifier from)
875 WithImplicitReconnect
877 { ctrlMsgSender = ProcessIdentifier from
878 , ctrlMsgSignal = reg
882-- Unified semantics does not explicitly describe 'whereis'
883ncEffectWhereIs :: ProcessId -> String -> NC ()
884ncEffectWhereIs from label = do
886 mPid <- gets (^. registeredHereFor label)
887 liftIO $ sendMessage node
888 (NodeIdentifier (localNodeId node))
889 (ProcessIdentifier from)
890 WithImplicitReconnect
891 (WhereIsReply label mPid)
893-- [Unified: Table 14]
894ncEffectNamedSend :: String -> Message -> NC ()
895ncEffectNamedSend label msg = do
896 mPid <- gets (^. registeredHereFor label)
897 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
898 forM_ mPid $ \pid -> postMessage pid msg
901ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
902ncEffectLocalSend node to msg =
903 liftIO $ withLocalProc node to $ \p -> do
904 enqueue (processQueue p) msg
905 trace node (MxReceived to msg)
908ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
909ncEffectLocalPortSend from msg = do
911 let pid = sendPortProcessId from
912 cid = sendPortLocalId from
913 liftIO $ withLocalProc node pid $ \proc -> do
914 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
916 -- in the unlikely event we know nothing about this channel id,
917 -- there's little to be done - perhaps some logging/tracing though...
919 Just (TypedChannel chan') -> do
920 -- If ch is Nothing, the process has given up the read end of
921 -- the channel and we simply ignore the incoming message - this
922 ch <- deRefWeak chan'
923 forM_ ch $ \chan -> deliverChan msg chan
924 where deliverChan :: forall a . Message -> TQueue a -> IO ()
925 deliverChan (UnencodedMessage _ raw) chan' =
926 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
927 deliverChan (EncodedMessage _ _) _ =
928 -- this will not happen unless someone screws with Primitives.hs
929 error "invalid local channel delivery"
932ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
933ncEffectKill from to reason = do
935 when (isLocal node (ProcessIdentifier to)) $
936 throwException to $ ProcessKillException from reason
939ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
940ncEffectExit from to reason = do
942 when (isLocal node (ProcessIdentifier to)) $
943 throwException to $ ProcessExitException from reason
946ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
947ncEffectGetInfo from pid =
948 let lpid = processLocalId pid
949 them = (ProcessIdentifier pid)
953 withMVar (localState node) $ return . (^. localProcessWithId lpid)
955 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
956 from node (ProcessInfoNone DiedUnknownId)
958 itsLinks <- gets (^. linksFor them)
959 itsMons <- gets (^. monitorsFor them)
960 registered <- gets (^. registeredHere)
962 let reg = registeredNames registered
963 dispatch (isLocal node (ProcessIdentifier from))
967 infoNode = (processNodeId pid)
968 , infoRegisteredNames = reg
969 -- we cannot populate this field
970 , infoMessageQueueLength = Nothing
971 , infoMonitors = Set.toList itsMons
972 , infoLinks = Set.toList itsLinks
974 where dispatch :: (Serializable a, Show a)
980 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
981 dispatch False dest node pInfo = do
982 liftIO $ sendMessage node
983 (NodeIdentifier (localNodeId node))
984 (ProcessIdentifier dest)
985 WithImplicitReconnect
988 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
992ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
993ncEffectGetNodeStats from _nid = do
995 ncState <- StateT.get
996 nodeState <- liftIO $ readMVar (localState node)
997 let localProcesses' = nodeState ^. localProcesses
1000 nodeStatsNode = localNodeId node
1001 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1002 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1003 , nodeStatsLinks = Map.size $ ncState ^. links
1004 , nodeStatsProcesses = Map.size localProcesses'
1006 postAsMessage from stats
1007--------------------------------------------------------------------------------
1009--------------------------------------------------------------------------------
1011notifyDied :: ProcessId -- ^ Who to notify?
1012 -> Identifier -- ^ Who died?
1013 -> DiedReason -- ^ How did they die?
1014 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1016notifyDied dest src reason mRef = do
1018 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1019 (True, Just ref, ProcessIdentifier pid) ->
1020 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1021 (True, Just ref, NodeIdentifier nid) ->
1022 postAsMessage dest $ NodeMonitorNotification ref nid reason
1023 (True, Just ref, SendPortIdentifier cid) ->
1024 postAsMessage dest $ PortMonitorNotification ref cid reason
1025 (True, Nothing, ProcessIdentifier pid) ->
1026 throwException dest $ ProcessLinkException pid reason
1027 (True, Nothing, NodeIdentifier pid) ->
1028 throwException dest $ NodeLinkException pid reason
1029 (True, Nothing, SendPortIdentifier pid) ->
1030 throwException dest $ PortLinkException pid reason
1032 -- The change in sender comes from [Unified: Table 10]
1033 liftIO $ sendBinary node
1034 (NodeIdentifier $ localNodeId node)
1035 (NodeIdentifier $ processNodeId dest)
1036 WithImplicitReconnect
1038 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1039 , ctrlMsgSignal = Died src reason
1042-- | [Unified: Table 8]
1043destNid :: ProcessSignal -> Maybe NodeId
1044destNid (Link ident) = Just $ nodeOf ident
1045destNid (Unlink ident) = Just $ nodeOf ident
1046destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1047destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1048destNid (Spawn _ _) = Nothing
1049destNid (Register _ _ _ _) = Nothing
1050destNid (WhereIs _) = Nothing
1051destNid (NamedSend _ _) = Nothing
1052-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1053-- then when a local process dies the monitoring/linking machinery will take
1054-- care of notifying remote nodes
1055destNid (Died _ _) = Nothing
1056destNid (Kill pid _) = Just $ processNodeId pid
1057destNid (Exit pid _) = Just $ processNodeId pid
1058destNid (GetInfo pid) = Just $ processNodeId pid
1059destNid (GetNodeStats nid) = Just nid
1060destNid (LocalSend pid _) = Just $ processNodeId pid
1061destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1062destNid (SigShutdown) = Nothing
1064-- | Check if a process is local to our own node
1065isLocal :: LocalNode -> Identifier -> Bool
1066isLocal nid ident = nodeOf ident == localNodeId nid
1068-- | Lookup a local closure
1069unClosure :: Typeable a => Closure a -> NC (Either String a)
1070unClosure closure = do
1071 rtable <- remoteTable <$> ask
1072 return (Static.unclosure rtable closure)
1074-- | Check if an identifier refers to a valid local object
1075isValidLocalIdentifier :: Identifier -> NC Bool
1076isValidLocalIdentifier ident = do
1078 liftIO . withMVar (localState node) $ \nSt ->
1080 NodeIdentifier nid ->
1081 return $ nid == localNodeId node
1082 ProcessIdentifier pid -> do
1083 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1084 return $ isJust mProc
1085 SendPortIdentifier cid -> do
1086 let pid = sendPortProcessId cid
1087 mProc = nSt ^. localProcessWithId (processLocalId pid)
1089 Nothing -> return False
1090 Just proc -> withMVar (processState proc) $ \pSt -> do
1091 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1094--------------------------------------------------------------------------------
1095-- Messages to local processes --
1096--------------------------------------------------------------------------------
1098postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1099postAsMessage pid = postMessage pid . createUnencodedMessage
1101postMessage :: ProcessId -> Message -> NC ()
1102postMessage pid msg = do
1104 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1106throwException :: Exception e => ProcessId -> e -> NC ()
1107throwException pid e = do
1109 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1111withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1112withLocalProc node pid p =
1113 -- By [Unified: table 6, rule missing_process] messages to dead processes
1114 -- can silently be dropped
1115 let lpid = processLocalId pid in do
1116 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1119--------------------------------------------------------------------------------
1121--------------------------------------------------------------------------------
1123links :: Accessor NCState (Map Identifier (Set ProcessId))
1124links = accessor _links (\ls st -> st { _links = ls })
1126monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1127monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1129registeredHere :: Accessor NCState (Map String ProcessId)
1130registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1132registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1133registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1135linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1136linksFor ident = links >>> DAC.mapDefault Set.empty ident
1138monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1139monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1141registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1142registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1144registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1145registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1147-- | @splitNotif ident@ splits a notifications map into those
1148-- notifications that should trigger when 'ident' fails and those links that
1151-- There is a hierarchy between identifiers: failure of a node implies failure
1152-- of all processes on that node, and failure of a process implies failure of
1153-- all typed channels to that process. In other words, if 'ident' refers to a
1154-- node, then the /should trigger/ set will include
1156-- * the notifications for the node specifically
1157-- * the notifications for processes on that node, and
1158-- * the notifications for typed channels to processes on that node.
1160-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1163-- * the notifications for that process specifically and
1164-- * the notifications for typed channels to that process.
1166-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1167splitNotif :: Identifier
1169 -> (Map Identifier a, Map Identifier a)
1170splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1172--------------------------------------------------------------------------------
1174--------------------------------------------------------------------------------
1176-- | Modify and evaluate the state
1177modify' :: MonadState s m => (s -> s) -> m ()
1178modify' f = StateT.get >>= \s -> StateT.put $! f s
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list