2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE BangPatterns #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE RecordWildCards #-}
11module Control.Distributed.Process.Node
21-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
23#if ! MIN_VERSION_base(4,6,0)
24import Prelude hiding (catch)
27import System.IO (fixIO, hPutStrLn, stderr)
28import System.Mem.Weak (Weak, deRefWeak)
29import qualified Data.ByteString.Lazy as BSL (fromChunks)
30import Data.Binary (decode)
32import qualified Data.Map as Map
44import qualified Data.Set as Set
51import Data.Foldable (forM_)
52import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
53import Data.Typeable (Typeable)
54import Control.Category ((>>>))
55import Control.Applicative (Applicative, (<$>))
56import Control.Monad (void, when)
57import Control.Monad.IO.Class (MonadIO, liftIO)
58import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
59import qualified Control.Monad.State.Strict as StateT (get, put)
60import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
61import Control.Exception
63 , AsyncException(ThreadKilled)
67 , uninterruptibleMask_
69import qualified Control.Exception as Exception (Handler(..), catches, finally)
70import Control.Concurrent (forkIO, forkIOWithUnmask, myThreadId)
71import Control.Distributed.Process.Internal.StrictMVar
80import Control.Concurrent.Chan (newChan, writeChan, readChan)
81import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
82import Control.Concurrent.STM
85import Control.Distributed.Process.Internal.CQueue
92import qualified Network.Transport as NT
105 , Reliability(ReliableOrdered)
107import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
108import System.Random (randomIO)
109import Control.Distributed.Static (RemoteTable, Closure)
110import qualified Control.Distributed.Static as Static
114import Control.Distributed.Process.Internal.Types
121 , ValidLocalNodeState(..)
122 , withValidLocalState
123 , modifyValidLocalState_
125 , LocalProcessState(..)
137 , ProcessMonitorNotification(..)
138 , NodeMonitorNotification(..)
139 , PortMonitorNotification(..)
140 , ProcessExitException(..)
141 , ProcessLinkException(..)
142 , NodeLinkException(..)
143 , PortLinkException(..)
145 , DidUnlinkProcess(..)
155 , ProcessInfoNone(..)
162 , createUnencodedMessage
164 , firstNonReservedProcessId
165 , ImplicitReconnect(WithImplicitReconnect)
167import Control.Distributed.Process.Management.Internal.Agent
170import qualified Control.Distributed.Process.Management.Internal.Table as Table
172 , startTableCoordinator
174import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
177import Control.Distributed.Process.Management.Internal.Trace.Tracer
180import Control.Distributed.Process.Management.Internal.Trace.Types
186import Control.Distributed.Process.Management.Internal.Types
189import Control.Distributed.Process.Serializable (Serializable)
190import Control.Distributed.Process.Internal.Messaging
193 , closeImplicitReconnections
196import Control.Distributed.Process.Internal.Primitives
205import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
206import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
207import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
208import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
214--------------------------------------------------------------------------------
216--------------------------------------------------------------------------------
218initRemoteTable :: RemoteTable
219initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
221-- | Initialize a new local node.
222newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
223newLocalNode transport rtable = do
224 mEndPoint <- NT.newEndPoint transport
226 Left ex -> throwIO ex
228 localNode <- createBareLocalNode endPoint rtable
229 startServiceProcesses localNode
232-- | Create a new local node (without any service processes running)
233createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
234createBareLocalNode endPoint rtable = do
236 state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
237 { _localProcesses = Map.empty
238 , _localPidCounter = firstNonReservedProcessId
239 , _localPidUnique = unq
240 , _localConnections = Map.empty
243 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
244 , localEndPoint = endPoint
246 , localCtrlChan = ctrlChan
247 , localEventBus = MxEventBusInitialising
248 , remoteTable = rtable
250 tracedNode <- startMxAgent node
252 -- Once the NC terminates, the endpoint isn't much use,
253 void $ forkIO $ Exception.finally (runNodeController tracedNode)
254 (NT.closeEndPoint (localEndPoint node))
256 -- whilst a closed/failing endpoint will terminate the NC
257 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
263 writeChan (localCtrlChan node) NCMsg
264 { ctrlMsgSender = NodeIdentifier (localNodeId node)
265 , ctrlMsgSignal = SigShutdown
268startMxAgent :: LocalNode -> IO LocalNode
269startMxAgent node = do
270 -- see note [tracer/forkProcess races]
271 let fork = forkProcess node
272 mv <- MVar.newEmptyMVar
273 pid <- fork $ mxAgentController fork mv
274 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
275 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
277startDefaultTracer :: LocalNode -> IO ()
278startDefaultTracer node' = do
279 let t = localEventBus node'
281 MxEventBus _ (Tracer pid _) _ _ -> do
282 runProcess node' $ register "trace.controller" pid
283 pid' <- forkProcess node' defaultTracer
284 enableTrace (localEventBus node') pid'
285 runProcess node' $ register "tracer.initial" pid'
288-- TODO: we need a better mechanism for defining and registering services
290-- | Start and register the service processes on a node
291startServiceProcesses :: LocalNode -> IO ()
292startServiceProcesses node = do
293 -- tracing /spawns/ relies on the tracer being enabled, but we start
294 -- the default tracer first, even though it might @nsend@ to the logger
295 -- before /that/ process has started - this is a totally harmless race
296 -- however, so we deliberably ignore it
297 startDefaultTracer node
298 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
299 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
300 logger <- forkProcess node loop
302 register "logger" logger
303 -- The trace.logger is used for tracing to the console to avoid feedback
304 -- loops during tracing if the user reregisters the "logger" with a custom
305 -- process which uses 'send' or other primitives which are traced.
306 register "trace.logger" logger
308 fork = forkProcess node
312 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
313 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
315 , match $ \((time, string) :: (String, String)) -> do
316 -- this is a 'trace' message from the local node tracer
317 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
319 , match $ \(ch :: SendPort ()) -> -- a shutdown request
323-- | Force-close a local node
325-- TODO: for now we just close the associated endpoint
326closeLocalNode :: LocalNode -> IO ()
327closeLocalNode node = do
328 modifyMVar_ (localState node) $ const $ return LocalNodeClosed
329 -- TODO: close all our processes, surely!?
330 NT.closeEndPoint (localEndPoint node)
332-- | Run a process on a local node and wait for it to finish
333runProcess :: LocalNode -> Process () -> IO ()
334runProcess node proc = do
337 void $ forkProcess node $ do
338 catch (proc `finally` liftIO (putMVar done ()))
339 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
342-- | Spawn a new process on a local node
343forkProcess :: LocalNode -> Process () -> IO ProcessId
344forkProcess node proc =
345 modifyMVarMasked (localState node) startProcess
347 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
348 startProcess (LocalNodeValid vst) = do
349 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
350 , lpidUnique = vst ^. localPidUnique
352 let pid = ProcessId { processNodeId = localNodeId node
353 , processLocalId = lpid
355 pst <- newMVar LocalProcessState { _monitorCounter = 0
357 , _channelCounter = 0
358 , _typedChannels = Map.empty
361 weakQueue <- mkWeakCQueue queue (return ())
362 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
363 let lproc = LocalProcess { processQueue = queue
364 , processWeakQ = weakQueue
367 , processThread = tid
370 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
371 reason <- Exception.catches
372 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
373 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
374 mMsg <- unwrapMessage msg :: IO (Maybe String)
376 Nothing -> return $ DiedException $ show ex
377 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
379 (return . DiedException . (show :: SomeException -> String)))]
381 -- [Unified: Table 4, rules termination and exiting]
382 modifyValidLocalState_ (localState node) (cleanupProcess pid)
383 writeChan (localCtrlChan node) NCMsg
384 { ctrlMsgSender = ProcessIdentifier pid
385 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
389 -- see note [tracer/forkProcess races]
390 trace node (MxSpawned pid)
392 if lpidCounter lpid == maxBound
394 -- TODO: this doesn't look right at all - how do we know
395 -- that newUnique represents a process id that is available!?
396 newUnique <- randomIO
397 return ( LocalNodeValid
398 $ (localProcessWithId lpid ^= Just lproc)
399 . (localPidCounter ^= firstNonReservedProcessId)
400 . (localPidUnique ^= newUnique)
405 return ( LocalNodeValid
406 $ (localProcessWithId lpid ^= Just lproc)
407 . (localPidCounter ^: (+ 1))
411 startProcess LocalNodeClosed = throwIO $ userError "LocalNode closed"
413 cleanupProcess :: ProcessId -> ValidLocalNodeState -> IO ValidLocalNodeState
414 cleanupProcess pid vst = do
415 let pid' = ProcessIdentifier pid
416 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
417 mapM_ (NT.close . fst) (Map.elems affected)
418 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
419 . (localConnections ^= unaffected)
422-- note [tracer/forkProcess races]
424-- Our startTracing function uses forkProcess to start the trace controller
425-- process, and of course forkProcess attempts to call traceEvent once the
426-- process has started. This is harmless, as the localEventBus is not updated
427-- until /after/ the initial forkProcess completes, so the first call to
428-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
431--------------------------------------------------------------------------------
432-- Handle incoming messages --
433--------------------------------------------------------------------------------
435type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
439 | ToProc ProcessId (Weak (CQueue Message))
440 | ToChan TypedChannel
443data ConnectionState = ConnectionState {
444 _incoming :: !(Map NT.ConnectionId IncomingConnection)
445 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
448initConnectionState :: ConnectionState
449initConnectionState = ConnectionState {
450 _incoming = Map.empty
451 , _incomingFrom = Map.empty
454incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
455incoming = accessor _incoming (\conns st -> st { _incoming = conns })
457incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
458incomingAt cid = incoming >>> DAC.mapMaybe cid
460incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
461incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
463 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
465handleIncomingMessages :: LocalNode -> IO ()
466handleIncomingMessages node = go initConnectionState
468 go :: ConnectionState -> IO ()
470 event <- NT.receive endpoint
472 NT.ConnectionOpened cid rel theirAddr ->
473 if rel == NT.ReliableOrdered
475 trace node (MxConnected cid theirAddr)
477 (incomingAt cid ^= Just (theirAddr, Uninit))
478 . (incomingFrom theirAddr ^: Set.insert cid)
481 else invalidRequest cid st
482 NT.Received cid payload ->
483 case st ^. incomingAt cid of
484 Just (_, ToProc pid weakQueue) -> do
485 mQueue <- deRefWeak weakQueue
486 forM_ mQueue $ \queue -> do
487 -- TODO: if we find that the queue is Nothing, should we remove
488 -- it from the NC state? (and same for channels, below)
489 let msg = payloadToMessage payload
490 enqueue queue msg -- 'enqueue' is strict
491 trace node (MxReceived pid msg)
493 Just (_, ToChan (TypedChannel chan')) -> do
494 mChan <- deRefWeak chan'
495 -- If mChan is Nothing, the process has given up the read end of
496 -- the channel and we simply ignore the incoming message
497 forM_ mChan $ \chan -> atomically $
498 -- We make sure the message is fully decoded when it is enqueued
499 writeTQueue chan $! decode (BSL.fromChunks payload)
501 Just (_, ToNode) -> do
502 let ctrlMsg = decode . BSL.fromChunks $ payload
503 writeChan ctrlChan $! ctrlMsg
505 Just (src, Uninit) ->
506 case decode (BSL.fromChunks payload) of
507 ProcessIdentifier pid -> do
508 let lpid = processLocalId pid
509 mProc <- withValidLocalState state $ return . (^. localProcessWithId lpid)
512 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
514 invalidRequest cid st
515 SendPortIdentifier chId -> do
516 let lcid = sendPortLocalId chId
517 lpid = processLocalId (sendPortProcessId chId)
518 mProc <- withValidLocalState state $ return . (^. localProcessWithId lpid)
521 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
524 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
526 invalidRequest cid st
528 invalidRequest cid st
529 NodeIdentifier nid ->
530 if nid == localNodeId node
531 then go (incomingAt cid ^= Just (src, ToNode) $ st)
532 else invalidRequest cid st
534 invalidRequest cid st
535 NT.ConnectionClosed cid ->
536 case st ^. incomingAt cid of
538 invalidRequest cid st
540 trace node (MxDisconnected cid src)
541 go ( (incomingAt cid ^= Nothing)
542 . (incomingFrom src ^: Set.delete cid)
545 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
546 -- [Unified table 9, rule node_disconnect]
547 let nid = NodeIdentifier $ NodeId theirAddr
548 writeChan ctrlChan NCMsg
549 { ctrlMsgSender = nid
550 , ctrlMsgSignal = Died nid DiedDisconnect
552 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
553 closeImplicitReconnections node nid
554 go ( (incomingFrom theirAddr ^= Set.empty)
555 . (incoming ^: Map.filterWithKey (const . notLost))
558 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
559 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
560 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
561 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
564 NT.ReceivedMulticast _ _ ->
565 -- If we received a multicast message, something went horribly wrong
566 -- and we just give up
567 fail "Cloud Haskell fatal error: received unexpected multicast"
569 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
570 invalidRequest cid st = do
571 -- TODO: We should treat this as a fatal error on the part of the remote
572 -- node. That is, we should report the remote node as having died, and we
573 -- should close incoming connections (this requires a Transport layer
575 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
577 go ( incomingAt cid ^= Nothing
581 state = localState node
582 endpoint = localEndPoint node
583 ctrlChan = localCtrlChan node
585--------------------------------------------------------------------------------
586-- Top-level access to the node controller --
587--------------------------------------------------------------------------------
589runNodeController :: LocalNode -> IO ()
591 runReaderT (evalStateT (unNC nodeController) initNCState)
593--------------------------------------------------------------------------------
594-- Internal data types --
595--------------------------------------------------------------------------------
597data NCState = NCState
598 { -- Mapping from remote processes to linked local processes
599 _links :: !(Map Identifier (Set ProcessId))
600 -- Mapping from remote processes to monitoring local processes
601 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
602 -- Process registry: names and where they live, mapped to the PIDs
603 , _registeredHere :: !(Map String ProcessId)
604 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
607newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
608 deriving ( Applicative
613 , MonadReader LocalNode
616initNCState :: NCState
617initNCState = NCState { _links = Map.empty
618 , _monitors = Map.empty
619 , _registeredHere = Map.empty
620 , _registeredOnNodes = Map.empty
623-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
624-- type is deliberately not exported so it cannot be caught explicitly.
625data ProcessKillException =
626 ProcessKillException !ProcessId !String
629instance Exception ProcessKillException
630instance Show ProcessKillException where
631 show (ProcessKillException pid reason) =
632 "killed-by=" ++ show pid ++ ",reason=" ++ reason
634--------------------------------------------------------------------------------
635-- Tracing/Debugging --
636--------------------------------------------------------------------------------
638-- [Issue #104 / DP-13]
640traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
641traceNotifyDied node ident reason =
642 -- TODO: sendPortDied notifications
643 liftIO $ withLocalTracer node $ \t ->
645 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
646 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
649traceEventFmtIO :: LocalNode
653traceEventFmtIO node fmt args =
654 withLocalTracer node $ \t -> traceLogFmt t fmt args
656trace :: LocalNode -> MxEvent -> IO ()
657trace node ev = withLocalTracer node $ \t -> traceEvent t ev
659withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
660withLocalTracer node act = act (localEventBus node)
662--------------------------------------------------------------------------------
663-- Core functionality --
664--------------------------------------------------------------------------------
667nodeController :: NC ()
671 msg <- liftIO $ readChan (localCtrlChan node)
673 -- [Unified: Table 7, rule nc_forward]
674 case destNid (ctrlMsgSignal msg) of
675 Just nid' | nid' /= localNodeId node ->
676 liftIO $ sendBinary node
678 (NodeIdentifier nid')
679 WithImplicitReconnect
685 NCMsg (ProcessIdentifier from) (Link them) ->
686 ncEffectMonitor from them Nothing
687 NCMsg (ProcessIdentifier from) (Monitor ref) ->
688 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
689 NCMsg (ProcessIdentifier from) (Unlink them) ->
690 ncEffectUnlink from them
691 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
692 ncEffectUnmonitor from ref
693 NCMsg _from (Died ident reason) ->
694 ncEffectDied ident reason
695 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
696 ncEffectSpawn from proc ref
697 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
698 ncEffectRegister from label atnode pid force
699 NCMsg (ProcessIdentifier from) (WhereIs label) ->
700 ncEffectWhereIs from label
701 NCMsg _ (NamedSend label msg') ->
702 ncEffectNamedSend label msg'
703 NCMsg _ (UnreliableSend lpid msg') ->
704 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
705 NCMsg _ (LocalSend to msg') ->
706 ncEffectLocalSend node to msg'
707 NCMsg _ (LocalPortSend to msg') ->
708 ncEffectLocalPortSend to msg'
709 NCMsg (ProcessIdentifier from) (Kill to reason) ->
710 ncEffectKill from to reason
711 NCMsg (ProcessIdentifier from) (Exit to reason) ->
712 ncEffectExit from to reason
713 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
714 ncEffectGetInfo from pid
715 NCMsg _ SigShutdown ->
717 NT.closeEndPoint (localEndPoint node)
718 `Exception.finally` throwIO ThreadKilled
719 -- ThreadKilled seems to make more sense than fail/error here
720 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
721 ncEffectGetNodeStats from nid
723 error $ "nodeController: unexpected message " ++ show unexpected
725-- [Unified: Table 10]
726ncEffectMonitor :: ProcessId -- ^ Who's watching?
727 -> Identifier -- ^ Who's being watched?
728 -> Maybe MonitorRef -- ^ 'Nothing' to link
730ncEffectMonitor from them mRef = do
733 if not (isLocal node them)
735 else isValidLocalIdentifier them
736 case (shouldLink, isLocal node (ProcessIdentifier from)) of
737 (True, _) -> -- [Unified: first rule]
739 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
740 Nothing -> modify' $ linksFor them ^: Set.insert from
741 (False, True) -> -- [Unified: second rule]
742 notifyDied from them DiedUnknownId mRef
743 (False, False) -> -- [Unified: third rule]
744 -- TODO: this is the right sender according to the Unified semantics,
745 -- but perhaps having 'them' as the sender would make more sense
746 -- (see also: notifyDied)
747 liftIO $ sendBinary node
748 (NodeIdentifier $ localNodeId node)
749 (NodeIdentifier $ processNodeId from)
750 WithImplicitReconnect
752 { ctrlMsgSender = NodeIdentifier (localNodeId node)
753 , ctrlMsgSignal = Died them DiedUnknownId
756-- [Unified: Table 11]
757ncEffectUnlink :: ProcessId -> Identifier -> NC ()
758ncEffectUnlink from them = do
760 when (isLocal node (ProcessIdentifier from)) $
762 ProcessIdentifier pid ->
763 postAsMessage from $ DidUnlinkProcess pid
764 NodeIdentifier nid ->
765 postAsMessage from $ DidUnlinkNode nid
766 SendPortIdentifier cid ->
767 postAsMessage from $ DidUnlinkPort cid
768 modify' $ linksFor them ^: Set.delete from
770-- [Unified: Table 11]
771ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
772ncEffectUnmonitor from ref = do
774 when (isLocal node (ProcessIdentifier from)) $
775 postAsMessage from $ DidUnmonitor ref
776 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
778-- [Unified: Table 12]
779ncEffectDied :: Identifier -> DiedReason -> NC ()
780ncEffectDied ident reason = do
782 traceNotifyDied node ident reason
783 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
784 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
786-- _registry :: !(Map (String,NodeId) ProcessId)
788 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
790 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
792 when (localOnly <= isLocal node (ProcessIdentifier us)) $
793 notifyDied us them reason Nothing
795 forM_ (Map.toList affectedMons) $ \(them, refs) ->
796 forM_ refs $ \(us, ref) ->
797 when (localOnly <= isLocal node (ProcessIdentifier us)) $
798 notifyDied us them reason (Just ref)
800 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
802 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
804 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
805 mapM (\(pid,nidlist) ->
806 case ident `impliesDeathOf` ProcessIdentifier pid of
808 do forM_ nidlist $ \(nid,_) ->
809 when (not $ isLocal node (NodeIdentifier nid))
810 (forwardNameDeath node nid)
812 False -> return $ Just (pid,nidlist) )
813 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
815 forwardNameDeath node nid =
816 liftIO $ sendBinary node
817 (NodeIdentifier $ localNodeId node)
818 (NodeIdentifier $ nid)
819 WithImplicitReconnect
821 { ctrlMsgSender = NodeIdentifier (localNodeId node)
822 , ctrlMsgSignal = Died ident reason
825-- [Unified: Table 13]
826ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
827ncEffectSpawn pid cProc ref = do
828 mProc <- unClosure cProc
829 -- If the closure does not exist, we spawn a process that throws an exception
830 -- This allows the remote node to find out what's happening
832 let proc = case mProc of
833 Left err -> fail $ "Error: Could not resolve closure: " ++ err
836 pid' <- liftIO $ forkProcess node proc
837 liftIO $ sendMessage node
838 (NodeIdentifier (localNodeId node))
839 (ProcessIdentifier pid)
840 WithImplicitReconnect
843-- Unified semantics does not explicitly describe how to implement 'register',
844-- but mentions it's "very similar to nsend" (Table 14)
845-- We send a response indicated if the operation is invalid
846ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
847ncEffectRegister from label atnode mPid reregistration = do
849 currentVal <- gets (^. registeredHereFor label)
852 Nothing -> -- unregister request
853 return $ isJust currentVal
854 Just thepid -> -- register request
855 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
856 return $ (isNothing currentVal /= reregistration) &&
857 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
858 if isLocal node (NodeIdentifier atnode)
859 then do when (isOk) $
860 do modify' $ registeredHereFor label ^= mPid
861 updateRemote node currentVal mPid
863 (Just p) -> liftIO $ trace node (MxRegistered p label)
864 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
865 liftIO $ sendMessage node
866 (NodeIdentifier (localNodeId node))
867 (ProcessIdentifier from)
868 WithImplicitReconnect
869 (RegisterReply label isOk)
871 case reregistration of
873 False -> flip incList
876 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
877 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
878 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
879 forward node (processNodeId newval) (Register label atnode (Just newval) False)
880 updateRemote node Nothing (Just newval) =
881 forward node (processNodeId newval) (Register label atnode (Just newval) False)
882 updateRemote node (Just oldval) Nothing =
883 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
884 updateRemote _ _ _ = return ()
885 maybeify f Nothing = unmaybeify $ f []
886 maybeify f (Just x) = unmaybeify $ f x
888 unmaybeify [] = Nothing
889 unmaybeify x = Just x
890 incList [] tag = [(tag,1)]
891 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
892 incList (x:xs) tag = x : incList xs tag
894 decList ((atag,1):xs) tag | atag == tag = xs
895 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
896 decList (x:xs) tag = x:decList xs tag
897 forward node to reg =
898 when (not $ isLocal node (NodeIdentifier to)) $
899 liftIO $ sendBinary node
900 (ProcessIdentifier from)
902 WithImplicitReconnect
904 { ctrlMsgSender = ProcessIdentifier from
905 , ctrlMsgSignal = reg
909-- Unified semantics does not explicitly describe 'whereis'
910ncEffectWhereIs :: ProcessId -> String -> NC ()
911ncEffectWhereIs from label = do
913 mPid <- gets (^. registeredHereFor label)
914 liftIO $ sendMessage node
915 (NodeIdentifier (localNodeId node))
916 (ProcessIdentifier from)
917 WithImplicitReconnect
918 (WhereIsReply label mPid)
920-- [Unified: Table 14]
921ncEffectNamedSend :: String -> Message -> NC ()
922ncEffectNamedSend label msg = do
923 mPid <- gets (^. registeredHereFor label)
924 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
925 forM_ mPid $ \pid -> postMessage pid msg
928ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
929ncEffectLocalSend node to msg =
930 liftIO $ withLocalProc node to $ \p -> do
931 enqueue (processQueue p) msg
932 trace node (MxReceived to msg)
935ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
936ncEffectLocalPortSend from msg = do
938 let pid = sendPortProcessId from
939 cid = sendPortLocalId from
940 liftIO $ withLocalProc node pid $ \proc -> do
941 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
943 -- in the unlikely event we know nothing about this channel id,
944 -- there's little to be done - perhaps some logging/tracing though...
946 Just (TypedChannel chan') -> do
947 -- If ch is Nothing, the process has given up the read end of
948 -- the channel and we simply ignore the incoming message - this
949 ch <- deRefWeak chan'
950 forM_ ch $ \chan -> deliverChan msg chan
951 where deliverChan :: forall a . Message -> TQueue a -> IO ()
952 deliverChan (UnencodedMessage _ raw) chan' =
953 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
954 deliverChan (EncodedMessage _ _) _ =
955 -- this will not happen unless someone screws with Primitives.hs
956 error "invalid local channel delivery"
959ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
960ncEffectKill from to reason = do
962 when (isLocal node (ProcessIdentifier to)) $
963 throwException to $ ProcessKillException from reason
966ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
967ncEffectExit from to reason = do
969 when (isLocal node (ProcessIdentifier to)) $
970 throwException to $ ProcessExitException from reason
973ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
974ncEffectGetInfo from pid =
975 let lpid = processLocalId pid
976 them = (ProcessIdentifier pid)
979 mProc <- liftIO $ withValidLocalState (localState node)
980 $ return . (^. localProcessWithId lpid)
982 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
983 from node (ProcessInfoNone DiedUnknownId)
985 itsLinks <- gets (^. linksFor them)
986 itsMons <- gets (^. monitorsFor them)
987 registered <- gets (^. registeredHere)
988 size <- liftIO $ queueSize $ processQueue $ proc
990 let reg = registeredNames registered
991 dispatch (isLocal node (ProcessIdentifier from))
995 infoNode = (processNodeId pid)
996 , infoRegisteredNames = reg
997 , infoMessageQueueLength = size
998 , infoMonitors = Set.toList itsMons
999 , infoLinks = Set.toList itsLinks
1001 where dispatch :: (Serializable a, Show a)
1007 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1008 dispatch False dest node pInfo = do
1009 liftIO $ sendMessage node
1010 (NodeIdentifier (localNodeId node))
1011 (ProcessIdentifier dest)
1012 WithImplicitReconnect
1015 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1019ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1020ncEffectGetNodeStats from _nid = do
1022 ncState <- StateT.get
1023 nodeState <- liftIO $ withValidLocalState (localState node) return
1026 nodeStatsNode = localNodeId node
1027 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1028 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1029 , nodeStatsLinks = Map.size $ ncState ^. links
1030 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1032 postAsMessage from stats
1034--------------------------------------------------------------------------------
1036--------------------------------------------------------------------------------
1038notifyDied :: ProcessId -- ^ Who to notify?
1039 -> Identifier -- ^ Who died?
1040 -> DiedReason -- ^ How did they die?
1041 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1043notifyDied dest src reason mRef = do
1045 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1046 (True, Just ref, ProcessIdentifier pid) ->
1047 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1048 (True, Just ref, NodeIdentifier nid) ->
1049 postAsMessage dest $ NodeMonitorNotification ref nid reason
1050 (True, Just ref, SendPortIdentifier cid) ->
1051 postAsMessage dest $ PortMonitorNotification ref cid reason
1052 (True, Nothing, ProcessIdentifier pid) ->
1053 throwException dest $ ProcessLinkException pid reason
1054 (True, Nothing, NodeIdentifier pid) ->
1055 throwException dest $ NodeLinkException pid reason
1056 (True, Nothing, SendPortIdentifier pid) ->
1057 throwException dest $ PortLinkException pid reason
1059 -- The change in sender comes from [Unified: Table 10]
1060 liftIO $ sendBinary node
1061 (NodeIdentifier $ localNodeId node)
1062 (NodeIdentifier $ processNodeId dest)
1063 WithImplicitReconnect
1065 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1066 , ctrlMsgSignal = Died src reason
1069-- | [Unified: Table 8]
1070destNid :: ProcessSignal -> Maybe NodeId
1071destNid (Link ident) = Just $ nodeOf ident
1072destNid (Unlink ident) = Just $ nodeOf ident
1073destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1074destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1075destNid (Spawn _ _) = Nothing
1076destNid (Register _ _ _ _) = Nothing
1077destNid (WhereIs _) = Nothing
1078destNid (NamedSend _ _) = Nothing
1079destNid (UnreliableSend _ _) = Nothing
1080-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1081-- then when a local process dies the monitoring/linking machinery will take
1082-- care of notifying remote nodes
1083destNid (Died _ _) = Nothing
1084destNid (Kill pid _) = Just $ processNodeId pid
1085destNid (Exit pid _) = Just $ processNodeId pid
1086destNid (GetInfo pid) = Just $ processNodeId pid
1087destNid (GetNodeStats nid) = Just nid
1088destNid (LocalSend pid _) = Just $ processNodeId pid
1089destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1090destNid (SigShutdown) = Nothing
1092-- | Check if a process is local to our own node
1093isLocal :: LocalNode -> Identifier -> Bool
1094isLocal nid ident = nodeOf ident == localNodeId nid
1096-- | Lookup a local closure
1097unClosure :: Typeable a => Closure a -> NC (Either String a)
1098unClosure closure = do
1099 rtable <- remoteTable <$> ask
1100 return (Static.unclosure rtable closure)
1102-- | Check if an identifier refers to a valid local object
1103isValidLocalIdentifier :: Identifier -> NC Bool
1104isValidLocalIdentifier ident = do
1106 liftIO . withValidLocalState (localState node) $ \nSt ->
1108 NodeIdentifier nid ->
1109 return $ nid == localNodeId node
1110 ProcessIdentifier pid -> do
1111 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1112 return $ isJust mProc
1113 SendPortIdentifier cid -> do
1114 let pid = sendPortProcessId cid
1115 mProc = nSt ^. localProcessWithId (processLocalId pid)
1117 Nothing -> return False
1118 Just proc -> withMVar (processState proc) $ \pSt -> do
1119 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1122--------------------------------------------------------------------------------
1123-- Messages to local processes --
1124--------------------------------------------------------------------------------
1126postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1127postAsMessage pid = postMessage pid . createUnencodedMessage
1129postMessage :: ProcessId -> Message -> NC ()
1130postMessage pid msg = do
1132 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1134throwException :: Exception e => ProcessId -> e -> NC ()
1135throwException pid e = do
1137 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1139withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1140withLocalProc node pid p =
1141 -- By [Unified: table 6, rule missing_process] messages to dead processes
1142 -- can silently be dropped
1143 let lpid = processLocalId pid in do
1144 withValidLocalState (localState node) $ \vst ->
1145 forM_ (vst ^. localProcessWithId lpid) p
1147--------------------------------------------------------------------------------
1149--------------------------------------------------------------------------------
1151links :: Accessor NCState (Map Identifier (Set ProcessId))
1152links = accessor _links (\ls st -> st { _links = ls })
1154monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1155monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1157registeredHere :: Accessor NCState (Map String ProcessId)
1158registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1160registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1161registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1163linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1164linksFor ident = links >>> DAC.mapDefault Set.empty ident
1166monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1167monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1169registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1170registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1172registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1173registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1175-- | @splitNotif ident@ splits a notifications map into those
1176-- notifications that should trigger when 'ident' fails and those links that
1179-- There is a hierarchy between identifiers: failure of a node implies failure
1180-- of all processes on that node, and failure of a process implies failure of
1181-- all typed channels to that process. In other words, if 'ident' refers to a
1182-- node, then the /should trigger/ set will include
1184-- * the notifications for the node specifically
1185-- * the notifications for processes on that node, and
1186-- * the notifications for typed channels to processes on that node.
1188-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1191-- * the notifications for that process specifically and
1192-- * the notifications for typed channels to that process.
1194-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1195splitNotif :: Identifier
1197 -> (Map Identifier a, Map Identifier a)
1198splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1200--------------------------------------------------------------------------------
1202--------------------------------------------------------------------------------
1204-- | Modify and evaluate the state
1205modify' :: MonadState s m => (s -> s) -> m ()
1206modify' 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