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
81import Control.Concurrent.Chan (newChan, writeChan, readChan)
82import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
83import Control.Concurrent.STM
86import Control.Distributed.Process.Internal.CQueue
93import qualified Network.Transport as NT
106 , Reliability(ReliableOrdered)
108import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
109import System.Random (randomIO)
110import Control.Distributed.Static (RemoteTable, Closure)
111import qualified Control.Distributed.Static as Static
115import Control.Distributed.Process.Internal.Types
123 , LocalProcessState(..)
135 , ProcessMonitorNotification(..)
136 , NodeMonitorNotification(..)
137 , PortMonitorNotification(..)
138 , ProcessExitException(..)
139 , ProcessLinkException(..)
140 , NodeLinkException(..)
141 , PortLinkException(..)
143 , DidUnlinkProcess(..)
153 , ProcessInfoNone(..)
161 , createUnencodedMessage
163 , firstNonReservedProcessId
164 , ImplicitReconnect(WithImplicitReconnect,NoImplicitReconnect)
166import Control.Distributed.Process.Management.Internal.Agent
169import qualified Control.Distributed.Process.Management.Internal.Table as Table
171 , startTableCoordinator
173import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
176import Control.Distributed.Process.Management.Internal.Trace.Tracer
179import Control.Distributed.Process.Management.Internal.Trace.Types
185import Control.Distributed.Process.Management.Internal.Types
188import Control.Distributed.Process.Serializable (Serializable)
189import 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 LocalNodeState
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 ()
328 -- TODO: close all our processes, surely!?
329 NT.closeEndPoint (localEndPoint node)
331-- | Run a process on a local node and wait for it to finish
332runProcess :: LocalNode -> Process () -> IO ()
333runProcess node proc = do
336 void $ forkProcess node $ do
337 catch (proc `finally` liftIO (putMVar done ()))
338 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
341-- | Spawn a new process on a local node
342forkProcess :: LocalNode -> Process () -> IO ProcessId
343forkProcess node proc =
344 modifyMVarMasked (localState node) startProcess
346 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
348 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
349 , lpidUnique = st ^. localPidUnique
351 let pid = ProcessId { processNodeId = localNodeId node
352 , processLocalId = lpid
354 pst <- newMVar LocalProcessState { _monitorCounter = 0
356 , _channelCounter = 0
357 , _typedChannels = Map.empty
360 weakQueue <- mkWeakCQueue queue (return ())
361 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
362 let lproc = LocalProcess { processQueue = queue
363 , processWeakQ = weakQueue
366 , processThread = tid
369 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
370 reason <- Exception.catches
371 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
372 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
373 mMsg <- unwrapMessage msg :: IO (Maybe String)
375 Nothing -> return $ DiedException $ show ex
376 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
378 (return . DiedException . (show :: SomeException -> String)))]
380 -- [Unified: Table 4, rules termination and exiting]
381 modifyMVar_ (localState node) (cleanupProcess pid)
382 writeChan (localCtrlChan node) NCMsg
383 { ctrlMsgSender = ProcessIdentifier pid
384 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
388 -- see note [tracer/forkProcess races]
389 trace node (MxSpawned pid)
391 if lpidCounter lpid == maxBound
393 -- TODO: this doesn't look right at all - how do we know
394 -- that newUnique represents a process id that is available!?
395 newUnique <- randomIO
396 return ( (localProcessWithId lpid ^= Just lproc)
397 . (localPidCounter ^= firstNonReservedProcessId)
398 . (localPidUnique ^= newUnique)
403 return ( (localProcessWithId lpid ^= Just lproc)
404 . (localPidCounter ^: (+ 1))
409 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
410 cleanupProcess pid st = do
411 let pid' = ProcessIdentifier pid
412 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
413 mapM_ (NT.close . fst) (Map.elems affected)
414 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
415 . (localConnections ^= unaffected)
418-- note [tracer/forkProcess races]
420-- Our startTracing function uses forkProcess to start the trace controller
421-- process, and of course forkProcess attempts to call traceEvent once the
422-- process has started. This is harmless, as the localEventBus is not updated
423-- until /after/ the initial forkProcess completes, so the first call to
424-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
427--------------------------------------------------------------------------------
428-- Handle incoming messages --
429--------------------------------------------------------------------------------
431type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
435 | ToProc ProcessId (Weak (CQueue Message))
436 | ToChan TypedChannel
439data ConnectionState = ConnectionState {
440 _incoming :: !(Map NT.ConnectionId IncomingConnection)
441 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
444initConnectionState :: ConnectionState
445initConnectionState = ConnectionState {
446 _incoming = Map.empty
447 , _incomingFrom = Map.empty
450incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
451incoming = accessor _incoming (\conns st -> st { _incoming = conns })
453incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
454incomingAt cid = incoming >>> DAC.mapMaybe cid
456incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
457incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
459 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
461handleIncomingMessages :: LocalNode -> IO ()
462handleIncomingMessages node = go initConnectionState
464 go :: ConnectionState -> IO ()
466 event <- NT.receive endpoint
468 NT.ConnectionOpened cid rel theirAddr ->
469 if rel == NT.ReliableOrdered
471 trace node (MxConnected cid theirAddr)
473 (incomingAt cid ^= Just (theirAddr, Uninit))
474 . (incomingFrom theirAddr ^: Set.insert cid)
477 else invalidRequest cid st
478 NT.Received cid payload ->
479 case st ^. incomingAt cid of
480 Just (_, ToProc pid weakQueue) -> do
481 mQueue <- deRefWeak weakQueue
482 forM_ mQueue $ \queue -> do
483 -- TODO: if we find that the queue is Nothing, should we remove
484 -- it from the NC state? (and same for channels, below)
485 let msg = payloadToMessage payload
486 enqueue queue msg -- 'enqueue' is strict
487 trace node (MxReceived pid msg)
489 Just (_, ToChan (TypedChannel chan')) -> do
490 mChan <- deRefWeak chan'
491 -- If mChan is Nothing, the process has given up the read end of
492 -- the channel and we simply ignore the incoming message
493 forM_ mChan $ \chan -> atomically $
494 -- We make sure the message is fully decoded when it is enqueued
495 writeTQueue chan $! decode (BSL.fromChunks payload)
497 Just (_, ToNode) -> do
498 let ctrlMsg = decode . BSL.fromChunks $ payload
499 writeChan ctrlChan $! ctrlMsg
501 Just (src, Uninit) ->
502 case decode (BSL.fromChunks payload) of
503 ProcessIdentifier pid -> do
504 let lpid = processLocalId pid
505 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
508 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
510 invalidRequest cid st
511 SendPortIdentifier chId -> do
512 let lcid = sendPortLocalId chId
513 lpid = processLocalId (sendPortProcessId chId)
514 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
517 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
520 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
522 invalidRequest cid st
524 invalidRequest cid st
525 NodeIdentifier nid ->
526 if nid == localNodeId node
527 then go (incomingAt cid ^= Just (src, ToNode) $ st)
528 else invalidRequest cid st
530 invalidRequest cid st
531 NT.ConnectionClosed cid ->
532 case st ^. incomingAt cid of
534 invalidRequest cid st
536 trace node (MxDisconnected cid src)
537 go ( (incomingAt cid ^= Nothing)
538 . (incomingFrom src ^: Set.delete cid)
541 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
542 -- [Unified table 9, rule node_disconnect]
543 let nid = NodeIdentifier $ NodeId theirAddr
544 writeChan ctrlChan NCMsg
545 { ctrlMsgSender = nid
546 , ctrlMsgSignal = Died nid DiedDisconnect
548 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
549 closeImplicitReconnections node nid
550 go ( (incomingFrom theirAddr ^= Set.empty)
551 . (incoming ^: Map.filterWithKey (const . notLost))
554 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
555 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
556 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
557 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
560 NT.ReceivedMulticast _ _ ->
561 -- If we received a multicast message, something went horribly wrong
562 -- and we just give up
563 fail "Cloud Haskell fatal error: received unexpected multicast"
565 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
566 invalidRequest cid st = do
567 -- TODO: We should treat this as a fatal error on the part of the remote
568 -- node. That is, we should report the remote node as having died, and we
569 -- should close incoming connections (this requires a Transport layer
571 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
573 go ( incomingAt cid ^= Nothing
577 state = localState node
578 endpoint = localEndPoint node
579 ctrlChan = localCtrlChan node
581--------------------------------------------------------------------------------
582-- Top-level access to the node controller --
583--------------------------------------------------------------------------------
585runNodeController :: LocalNode -> IO ()
587 runReaderT (evalStateT (unNC nodeController) initNCState)
589--------------------------------------------------------------------------------
590-- Internal data types --
591--------------------------------------------------------------------------------
593data NCState = NCState
594 { -- Mapping from remote processes to linked local processes
595 _links :: !(Map Identifier (Set ProcessId))
596 -- Mapping from remote processes to monitoring local processes
597 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
598 -- Process registry: names and where they live, mapped to the PIDs
599 , _registeredHere :: !(Map String ProcessId)
600 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
603newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
604 deriving ( Applicative
609 , MonadReader LocalNode
612initNCState :: NCState
613initNCState = NCState { _links = Map.empty
614 , _monitors = Map.empty
615 , _registeredHere = Map.empty
616 , _registeredOnNodes = Map.empty
619-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
620-- type is deliberately not exported so it cannot be caught explicitly.
621data ProcessKillException =
622 ProcessKillException !ProcessId !String
625instance Exception ProcessKillException
626instance Show ProcessKillException where
627 show (ProcessKillException pid reason) =
628 "killed-by=" ++ show pid ++ ",reason=" ++ reason
630--------------------------------------------------------------------------------
631-- Tracing/Debugging --
632--------------------------------------------------------------------------------
634-- [Issue #104 / DP-13]
636traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
637traceNotifyDied node ident reason =
638 -- TODO: sendPortDied notifications
639 liftIO $ withLocalTracer node $ \t ->
641 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
642 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
645traceEventFmtIO :: LocalNode
649traceEventFmtIO node fmt args =
650 withLocalTracer node $ \t -> traceLogFmt t fmt args
652trace :: LocalNode -> MxEvent -> IO ()
653trace node ev = withLocalTracer node $ \t -> traceEvent t ev
655withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
656withLocalTracer node act = act (localEventBus node)
658--------------------------------------------------------------------------------
659-- Core functionality --
660--------------------------------------------------------------------------------
663nodeController :: NC ()
667 msg <- liftIO $ readChan (localCtrlChan node)
669 -- [Unified: Table 7, rule nc_forward]
670 case destNid (ctrlMsgSignal msg) of
671 Just nid' | nid' /= localNodeId node ->
672 liftIO $ sendBinary node
674 (NodeIdentifier nid')
675 WithImplicitReconnect
681 NCMsg (ProcessIdentifier from) (Link them) ->
682 ncEffectMonitor from them Nothing
683 NCMsg (ProcessIdentifier from) (Monitor ref) ->
684 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
685 NCMsg (ProcessIdentifier from) (Unlink them) ->
686 ncEffectUnlink from them
687 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
688 ncEffectUnmonitor from ref
689 NCMsg _from (Died ident reason) ->
690 ncEffectDied ident reason
691 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
692 ncEffectSpawn from proc ref
693 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
694 ncEffectRegister from label atnode pid force
695 NCMsg (ProcessIdentifier from) (WhereIs label) ->
696 ncEffectWhereIs from label
697 NCMsg (ProcessIdentifier from) (NamedSend label msg') ->
698 ncEffectNamedSend from label msg'
699 NCMsg _ (UnreliableSend lpid msg') ->
700 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
701 NCMsg _ (LocalSend to msg') ->
702 ncEffectLocalSend node to msg'
703 NCMsg _ (LocalPortSend to msg') ->
704 ncEffectLocalPortSend to msg'
705 NCMsg (ProcessIdentifier from) (Kill to reason) ->
706 ncEffectKill from to reason
707 NCMsg (ProcessIdentifier from) (Exit to reason) ->
708 ncEffectExit from to reason
709 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
710 ncEffectGetInfo from pid
711 NCMsg _ SigShutdown ->
713 NT.closeEndPoint (localEndPoint node)
714 `Exception.finally` throwIO ThreadKilled
715 -- ThreadKilled seems to make more sense than fail/error here
716 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
717 ncEffectGetNodeStats from nid
719 error $ "nodeController: unexpected message " ++ show unexpected
721-- [Unified: Table 10]
722ncEffectMonitor :: ProcessId -- ^ Who's watching?
723 -> Identifier -- ^ Who's being watched?
724 -> Maybe MonitorRef -- ^ 'Nothing' to link
726ncEffectMonitor from them mRef = do
729 if not (isLocal node them)
731 else isValidLocalIdentifier them
732 case (shouldLink, isLocal node (ProcessIdentifier from)) of
733 (True, _) -> -- [Unified: first rule]
735 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
736 Nothing -> modify' $ linksFor them ^: Set.insert from
737 (False, True) -> -- [Unified: second rule]
738 notifyDied from them DiedUnknownId mRef
739 (False, False) -> -- [Unified: third rule]
740 -- TODO: this is the right sender according to the Unified semantics,
741 -- but perhaps having 'them' as the sender would make more sense
742 -- (see also: notifyDied)
743 liftIO $ sendBinary node
744 (NodeIdentifier $ localNodeId node)
745 (NodeIdentifier $ processNodeId from)
746 WithImplicitReconnect
748 { ctrlMsgSender = NodeIdentifier (localNodeId node)
749 , ctrlMsgSignal = Died them DiedUnknownId
752-- [Unified: Table 11]
753ncEffectUnlink :: ProcessId -> Identifier -> NC ()
754ncEffectUnlink from them = do
756 when (isLocal node (ProcessIdentifier from)) $
758 ProcessIdentifier pid ->
759 postAsMessage from $ DidUnlinkProcess pid
760 NodeIdentifier nid ->
761 postAsMessage from $ DidUnlinkNode nid
762 SendPortIdentifier cid ->
763 postAsMessage from $ DidUnlinkPort cid
764 modify' $ linksFor them ^: Set.delete from
766-- [Unified: Table 11]
767ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
768ncEffectUnmonitor from ref = do
770 when (isLocal node (ProcessIdentifier from)) $
771 postAsMessage from $ DidUnmonitor ref
772 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
774-- [Unified: Table 12]
775ncEffectDied :: Identifier -> DiedReason -> NC ()
776ncEffectDied ident reason = do
778 traceNotifyDied node ident reason
779 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
780 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
782-- _registry :: !(Map (String,NodeId) ProcessId)
784 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
786 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
788 when (localOnly <= isLocal node (ProcessIdentifier us)) $
789 notifyDied us them reason Nothing
791 forM_ (Map.toList affectedMons) $ \(them, refs) ->
792 forM_ refs $ \(us, ref) ->
793 when (localOnly <= isLocal node (ProcessIdentifier us)) $
794 notifyDied us them reason (Just ref)
796 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
798 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
800 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
801 mapM (\(pid,nidlist) ->
802 case ident `impliesDeathOf` ProcessIdentifier pid of
804 do forM_ nidlist $ \(nid,_) ->
805 when (not $ isLocal node (NodeIdentifier nid))
806 (forwardNameDeath node nid)
808 False -> return $ Just (pid,nidlist) )
809 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
811 forwardNameDeath node nid =
812 liftIO $ sendBinary node
813 (NodeIdentifier $ localNodeId node)
814 (NodeIdentifier $ nid)
815 WithImplicitReconnect
817 { ctrlMsgSender = NodeIdentifier (localNodeId node)
818 , ctrlMsgSignal = Died ident reason
821-- [Unified: Table 13]
822ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
823ncEffectSpawn pid cProc ref = do
824 mProc <- unClosure cProc
825 -- If the closure does not exist, we spawn a process that throws an exception
826 -- This allows the remote node to find out what's happening
828 let proc = case mProc of
829 Left err -> fail $ "Error: Could not resolve closure: " ++ err
832 pid' <- liftIO $ forkProcess node proc
833 liftIO $ sendMessage node
834 (NodeIdentifier (localNodeId node))
835 (ProcessIdentifier pid)
836 WithImplicitReconnect
839-- Unified semantics does not explicitly describe how to implement 'register',
840-- but mentions it's "very similar to nsend" (Table 14)
841-- We send a response indicated if the operation is invalid
842ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
843ncEffectRegister from label atnode mPid reregistration = do
845 currentVal <- gets (^. registeredHereFor label)
848 Nothing -> -- unregister request
849 return $ isJust currentVal
850 Just thepid -> -- register request
851 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
852 return $ (isNothing currentVal /= reregistration) &&
853 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
854 if isLocal node (NodeIdentifier atnode)
855 then do when (isOk) $
856 do modify' $ registeredHereFor label ^= mPid
857 updateRemote node currentVal mPid
859 (Just p) -> liftIO $ trace node (MxRegistered p label)
860 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
861 liftIO $ sendMessage node
862 (NodeIdentifier (localNodeId node))
863 (ProcessIdentifier from)
864 WithImplicitReconnect
865 (RegisterReply label isOk)
867 case reregistration of
869 False -> flip incList
872 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
873 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
874 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
875 forward node (processNodeId newval) (Register label atnode (Just newval) False)
876 updateRemote node Nothing (Just newval) =
877 forward node (processNodeId newval) (Register label atnode (Just newval) False)
878 updateRemote node (Just oldval) Nothing =
879 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
880 updateRemote _ _ _ = return ()
881 maybeify f Nothing = unmaybeify $ f []
882 maybeify f (Just x) = unmaybeify $ f x
884 unmaybeify [] = Nothing
885 unmaybeify x = Just x
886 incList [] tag = [(tag,1)]
887 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
888 incList (x:xs) tag = x : incList xs tag
890 decList ((atag,1):xs) tag | atag == tag = xs
891 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
892 decList (x:xs) tag = x:decList xs tag
893 forward node to reg =
894 when (not $ isLocal node (NodeIdentifier to)) $
895 liftIO $ sendBinary node
896 (ProcessIdentifier from)
898 WithImplicitReconnect
900 { ctrlMsgSender = ProcessIdentifier from
901 , ctrlMsgSignal = reg
905-- Unified semantics does not explicitly describe 'whereis'
906ncEffectWhereIs :: ProcessId -> String -> NC ()
907ncEffectWhereIs from label = do
909 mPid <- gets (^. registeredHereFor label)
910 liftIO $ sendMessage node
911 (NodeIdentifier (localNodeId node))
912 (ProcessIdentifier from)
913 WithImplicitReconnect
914 (WhereIsReply label mPid)
916-- [Unified: Table 14]
917ncEffectNamedSend :: ProcessId -> String -> Message -> NC ()
918ncEffectNamedSend from label msg = do
919 mPid <- gets (^. registeredHereFor label)
921 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
923 liftIO $ sendPayload node
924 (ProcessIdentifier from)
925 (ProcessIdentifier pid)
927 (messageToPayload msg)
930ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
931ncEffectLocalSend node to msg =
932 liftIO $ withLocalProc node to $ \p -> do
933 enqueue (processQueue p) msg
934 trace node (MxReceived to msg)
937ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
938ncEffectLocalPortSend from msg = do
940 let pid = sendPortProcessId from
941 cid = sendPortLocalId from
942 liftIO $ withLocalProc node pid $ \proc -> do
943 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
945 -- in the unlikely event we know nothing about this channel id,
946 -- there's little to be done - perhaps some logging/tracing though...
948 Just (TypedChannel chan') -> do
949 -- If ch is Nothing, the process has given up the read end of
950 -- the channel and we simply ignore the incoming message - this
951 ch <- deRefWeak chan'
952 forM_ ch $ \chan -> deliverChan msg chan
953 where deliverChan :: forall a . Message -> TQueue a -> IO ()
954 deliverChan (UnencodedMessage _ raw) chan' =
955 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
956 deliverChan (EncodedMessage _ _) _ =
957 -- this will not happen unless someone screws with Primitives.hs
958 error "invalid local channel delivery"
961ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
962ncEffectKill from to reason = do
964 when (isLocal node (ProcessIdentifier to)) $
965 throwException to $ ProcessKillException from reason
968ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
969ncEffectExit from to reason = do
971 when (isLocal node (ProcessIdentifier to)) $
972 throwException to $ ProcessExitException from reason
975ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
976ncEffectGetInfo from pid =
977 let lpid = processLocalId pid
978 them = (ProcessIdentifier pid)
982 withMVar (localState node) $ return . (^. localProcessWithId lpid)
984 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
985 from node (ProcessInfoNone DiedUnknownId)
987 itsLinks <- gets (^. linksFor them)
988 itsMons <- gets (^. monitorsFor them)
989 registered <- gets (^. registeredHere)
990 size <- liftIO $ queueSize $ processQueue $ proc
992 let reg = registeredNames registered
993 dispatch (isLocal node (ProcessIdentifier from))
997 infoNode = (processNodeId pid)
998 , infoRegisteredNames = reg
999 , infoMessageQueueLength = size
1000 , infoMonitors = Set.toList itsMons
1001 , infoLinks = Set.toList itsLinks
1003 where dispatch :: (Serializable a, Show a)
1009 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1010 dispatch False dest node pInfo = do
1011 liftIO $ sendMessage node
1012 (NodeIdentifier (localNodeId node))
1013 (ProcessIdentifier dest)
1014 WithImplicitReconnect
1017 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1021ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1022ncEffectGetNodeStats from _nid = do
1024 ncState <- StateT.get
1025 nodeState <- liftIO $ readMVar (localState node)
1026 let localProcesses' = nodeState ^. localProcesses
1029 nodeStatsNode = localNodeId node
1030 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1031 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1032 , nodeStatsLinks = Map.size $ ncState ^. links
1033 , nodeStatsProcesses = Map.size localProcesses'
1035 postAsMessage from stats
1036--------------------------------------------------------------------------------
1038--------------------------------------------------------------------------------
1040notifyDied :: ProcessId -- ^ Who to notify?
1041 -> Identifier -- ^ Who died?
1042 -> DiedReason -- ^ How did they die?
1043 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1045notifyDied dest src reason mRef = do
1047 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1048 (True, Just ref, ProcessIdentifier pid) ->
1049 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1050 (True, Just ref, NodeIdentifier nid) ->
1051 postAsMessage dest $ NodeMonitorNotification ref nid reason
1052 (True, Just ref, SendPortIdentifier cid) ->
1053 postAsMessage dest $ PortMonitorNotification ref cid reason
1054 (True, Nothing, ProcessIdentifier pid) ->
1055 throwException dest $ ProcessLinkException pid reason
1056 (True, Nothing, NodeIdentifier pid) ->
1057 throwException dest $ NodeLinkException pid reason
1058 (True, Nothing, SendPortIdentifier pid) ->
1059 throwException dest $ PortLinkException pid reason
1061 -- The change in sender comes from [Unified: Table 10]
1062 liftIO $ sendBinary node
1063 (NodeIdentifier $ localNodeId node)
1064 (NodeIdentifier $ processNodeId dest)
1065 WithImplicitReconnect
1067 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1068 , ctrlMsgSignal = Died src reason
1071-- | [Unified: Table 8]
1072destNid :: ProcessSignal -> Maybe NodeId
1073destNid (Link ident) = Just $ nodeOf ident
1074destNid (Unlink ident) = Just $ nodeOf ident
1075destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1076destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1077destNid (Spawn _ _) = Nothing
1078destNid (Register _ _ _ _) = Nothing
1079destNid (WhereIs _) = Nothing
1080destNid (NamedSend _ _) = Nothing
1081destNid (UnreliableSend _ _) = Nothing
1082-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1083-- then when a local process dies the monitoring/linking machinery will take
1084-- care of notifying remote nodes
1085destNid (Died _ _) = Nothing
1086destNid (Kill pid _) = Just $ processNodeId pid
1087destNid (Exit pid _) = Just $ processNodeId pid
1088destNid (GetInfo pid) = Just $ processNodeId pid
1089destNid (GetNodeStats nid) = Just nid
1090destNid (LocalSend pid _) = Just $ processNodeId pid
1091destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1092destNid (SigShutdown) = Nothing
1094-- | Check if a process is local to our own node
1095isLocal :: LocalNode -> Identifier -> Bool
1096isLocal nid ident = nodeOf ident == localNodeId nid
1098-- | Lookup a local closure
1099unClosure :: Typeable a => Closure a -> NC (Either String a)
1100unClosure closure = do
1101 rtable <- remoteTable <$> ask
1102 return (Static.unclosure rtable closure)
1104-- | Check if an identifier refers to a valid local object
1105isValidLocalIdentifier :: Identifier -> NC Bool
1106isValidLocalIdentifier ident = do
1108 liftIO . withMVar (localState node) $ \nSt ->
1110 NodeIdentifier nid ->
1111 return $ nid == localNodeId node
1112 ProcessIdentifier pid -> do
1113 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1114 return $ isJust mProc
1115 SendPortIdentifier cid -> do
1116 let pid = sendPortProcessId cid
1117 mProc = nSt ^. localProcessWithId (processLocalId pid)
1119 Nothing -> return False
1120 Just proc -> withMVar (processState proc) $ \pSt -> do
1121 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1124--------------------------------------------------------------------------------
1125-- Messages to local processes --
1126--------------------------------------------------------------------------------
1128postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1129postAsMessage pid = postMessage pid . createUnencodedMessage
1131postMessage :: ProcessId -> Message -> NC ()
1132postMessage pid msg = do
1134 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1136throwException :: Exception e => ProcessId -> e -> NC ()
1137throwException pid e = do
1139 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1141withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1142withLocalProc node pid p =
1143 -- By [Unified: table 6, rule missing_process] messages to dead processes
1144 -- can silently be dropped
1145 let lpid = processLocalId pid in do
1146 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1149--------------------------------------------------------------------------------
1151--------------------------------------------------------------------------------
1153links :: Accessor NCState (Map Identifier (Set ProcessId))
1154links = accessor _links (\ls st -> st { _links = ls })
1156monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1157monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1159registeredHere :: Accessor NCState (Map String ProcessId)
1160registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1162registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1163registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1165linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1166linksFor ident = links >>> DAC.mapDefault Set.empty ident
1168monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1169monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1171registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1172registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1174registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1175registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1177-- | @splitNotif ident@ splits a notifications map into those
1178-- notifications that should trigger when 'ident' fails and those links that
1181-- There is a hierarchy between identifiers: failure of a node implies failure
1182-- of all processes on that node, and failure of a process implies failure of
1183-- all typed channels to that process. In other words, if 'ident' refers to a
1184-- node, then the /should trigger/ set will include
1186-- * the notifications for the node specifically
1187-- * the notifications for processes on that node, and
1188-- * the notifications for typed channels to processes on that node.
1190-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1193-- * the notifications for that process specifically and
1194-- * the notifications for typed channels to that process.
1196-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1197splitNotif :: Identifier
1199 -> (Map Identifier a, Map Identifier a)
1200splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1202--------------------------------------------------------------------------------
1204--------------------------------------------------------------------------------
1206-- | Modify and evaluate the state
1207modify' :: MonadState s m => (s -> s) -> m ()
1208modify' 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