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
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
122 , LocalProcessState(..)
134 , ProcessMonitorNotification(..)
135 , NodeMonitorNotification(..)
136 , PortMonitorNotification(..)
137 , ProcessExitException(..)
138 , ProcessLinkException(..)
139 , NodeLinkException(..)
140 , PortLinkException(..)
142 , DidUnlinkProcess(..)
152 , ProcessInfoNone(..)
159 , createUnencodedMessage
161 , firstNonReservedProcessId
162 , ImplicitReconnect(WithImplicitReconnect)
164import Control.Distributed.Process.Management.Internal.Agent
167import qualified Control.Distributed.Process.Management.Internal.Table as Table
169 , startTableCoordinator
171import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
174import Control.Distributed.Process.Management.Internal.Trace.Tracer
177import Control.Distributed.Process.Management.Internal.Trace.Types
183import Control.Distributed.Process.Management.Internal.Types
186import Control.Distributed.Process.Serializable (Serializable)
187import Control.Distributed.Process.Internal.Messaging
190 , closeImplicitReconnections
193import Control.Distributed.Process.Internal.Primitives
202import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
203import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
204import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
205import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
211--------------------------------------------------------------------------------
213--------------------------------------------------------------------------------
215initRemoteTable :: RemoteTable
216initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
218-- | Initialize a new local node.
219newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
220newLocalNode transport rtable = do
221 mEndPoint <- NT.newEndPoint transport
223 Left ex -> throwIO ex
225 localNode <- createBareLocalNode endPoint rtable
226 startServiceProcesses localNode
229-- | Create a new local node (without any service processes running)
230createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
231createBareLocalNode endPoint rtable = do
233 state <- newMVar LocalNodeState
234 { _localProcesses = Map.empty
235 , _localPidCounter = firstNonReservedProcessId
236 , _localPidUnique = unq
237 , _localConnections = Map.empty
240 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
241 , localEndPoint = endPoint
243 , localCtrlChan = ctrlChan
244 , localEventBus = MxEventBusInitialising
245 , remoteTable = rtable
247 tracedNode <- startMxAgent node
249 -- Once the NC terminates, the endpoint isn't much use,
250 void $ forkIO $ Exception.finally (runNodeController tracedNode)
251 (NT.closeEndPoint (localEndPoint node))
253 -- whilst a closed/failing endpoint will terminate the NC
254 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
260 writeChan (localCtrlChan node) NCMsg
261 { ctrlMsgSender = NodeIdentifier (localNodeId node)
262 , ctrlMsgSignal = SigShutdown
265startMxAgent :: LocalNode -> IO LocalNode
266startMxAgent node = do
267 -- see note [tracer/forkProcess races]
268 let fork = forkProcess node
269 mv <- MVar.newEmptyMVar
270 pid <- fork $ mxAgentController fork mv
271 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
272 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
274startDefaultTracer :: LocalNode -> IO ()
275startDefaultTracer node' = do
276 let t = localEventBus node'
278 MxEventBus _ (Tracer pid _) _ _ -> do
279 runProcess node' $ register "trace.controller" pid
280 pid' <- forkProcess node' defaultTracer
281 enableTrace (localEventBus node') pid'
282 runProcess node' $ register "tracer.initial" pid'
285-- TODO: we need a better mechanism for defining and registering services
287-- | Start and register the service processes on a node
288startServiceProcesses :: LocalNode -> IO ()
289startServiceProcesses node = do
290 -- tracing /spawns/ relies on the tracer being enabled, but we start
291 -- the default tracer first, even though it might @nsend@ to the logger
292 -- before /that/ process has started - this is a totally harmless race
293 -- however, so we deliberably ignore it
294 startDefaultTracer node
295 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
296 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
297 logger <- forkProcess node loop
299 register "logger" logger
300 -- The trace.logger is used for tracing to the console to avoid feedback
301 -- loops during tracing if the user reregisters the "logger" with a custom
302 -- process which uses 'send' or other primitives which are traced.
303 register "trace.logger" logger
305 fork = forkProcess node
309 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
310 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
312 , match $ \((time, string) :: (String, String)) -> do
313 -- this is a 'trace' message from the local node tracer
314 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
316 , match $ \(ch :: SendPort ()) -> -- a shutdown request
320-- | Force-close a local node
322-- TODO: for now we just close the associated endpoint
323closeLocalNode :: LocalNode -> IO ()
325 -- TODO: close all our processes, surely!?
326 NT.closeEndPoint (localEndPoint node)
328-- | Run a process on a local node and wait for it to finish
329runProcess :: LocalNode -> Process () -> IO ()
330runProcess node proc = do
333 void $ forkProcess node $ do
334 catch (proc `finally` liftIO (putMVar done ()))
335 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
338-- | Spawn a new process on a local node
339forkProcess :: LocalNode -> Process () -> IO ProcessId
340forkProcess node proc =
341 modifyMVarMasked (localState node) startProcess
343 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
345 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
346 , lpidUnique = st ^. localPidUnique
348 let pid = ProcessId { processNodeId = localNodeId node
349 , processLocalId = lpid
351 pst <- newMVar LocalProcessState { _monitorCounter = 0
353 , _channelCounter = 0
354 , _typedChannels = Map.empty
357 weakQueue <- mkWeakCQueue queue (return ())
358 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
359 let lproc = LocalProcess { processQueue = queue
360 , processWeakQ = weakQueue
363 , processThread = tid
366 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
367 reason <- Exception.catches
368 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
369 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
370 mMsg <- unwrapMessage msg :: IO (Maybe String)
372 Nothing -> return $ DiedException $ show ex
373 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
375 (return . DiedException . (show :: SomeException -> String)))]
377 -- [Unified: Table 4, rules termination and exiting]
378 modifyMVar_ (localState node) (cleanupProcess pid)
379 writeChan (localCtrlChan node) NCMsg
380 { ctrlMsgSender = ProcessIdentifier pid
381 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
385 -- see note [tracer/forkProcess races]
386 trace node (MxSpawned pid)
388 if lpidCounter lpid == maxBound
390 -- TODO: this doesn't look right at all - how do we know
391 -- that newUnique represents a process id that is available!?
392 newUnique <- randomIO
393 return ( (localProcessWithId lpid ^= Just lproc)
394 . (localPidCounter ^= firstNonReservedProcessId)
395 . (localPidUnique ^= newUnique)
400 return ( (localProcessWithId lpid ^= Just lproc)
401 . (localPidCounter ^: (+ 1))
406 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
407 cleanupProcess pid st = do
408 let pid' = ProcessIdentifier pid
409 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
410 mapM_ (NT.close . fst) (Map.elems affected)
411 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
412 . (localConnections ^= unaffected)
415-- note [tracer/forkProcess races]
417-- Our startTracing function uses forkProcess to start the trace controller
418-- process, and of course forkProcess attempts to call traceEvent once the
419-- process has started. This is harmless, as the localEventBus is not updated
420-- until /after/ the initial forkProcess completes, so the first call to
421-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
424--------------------------------------------------------------------------------
425-- Handle incoming messages --
426--------------------------------------------------------------------------------
428type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
432 | ToProc ProcessId (Weak (CQueue Message))
433 | ToChan TypedChannel
436data ConnectionState = ConnectionState {
437 _incoming :: !(Map NT.ConnectionId IncomingConnection)
438 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
441initConnectionState :: ConnectionState
442initConnectionState = ConnectionState {
443 _incoming = Map.empty
444 , _incomingFrom = Map.empty
447incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
448incoming = accessor _incoming (\conns st -> st { _incoming = conns })
450incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
451incomingAt cid = incoming >>> DAC.mapMaybe cid
453incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
454incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
456 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
458handleIncomingMessages :: LocalNode -> IO ()
459handleIncomingMessages node = go initConnectionState
461 go :: ConnectionState -> IO ()
463 event <- NT.receive endpoint
465 NT.ConnectionOpened cid rel theirAddr ->
466 if rel == NT.ReliableOrdered
468 trace node (MxConnected cid theirAddr)
470 (incomingAt cid ^= Just (theirAddr, Uninit))
471 . (incomingFrom theirAddr ^: Set.insert cid)
474 else invalidRequest cid st
475 NT.Received cid payload ->
476 case st ^. incomingAt cid of
477 Just (_, ToProc pid weakQueue) -> do
478 mQueue <- deRefWeak weakQueue
479 forM_ mQueue $ \queue -> do
480 -- TODO: if we find that the queue is Nothing, should we remove
481 -- it from the NC state? (and same for channels, below)
482 let msg = payloadToMessage payload
483 enqueue queue msg -- 'enqueue' is strict
484 trace node (MxReceived pid msg)
486 Just (_, ToChan (TypedChannel chan')) -> do
487 mChan <- deRefWeak chan'
488 -- If mChan is Nothing, the process has given up the read end of
489 -- the channel and we simply ignore the incoming message
490 forM_ mChan $ \chan -> atomically $
491 -- We make sure the message is fully decoded when it is enqueued
492 writeTQueue chan $! decode (BSL.fromChunks payload)
494 Just (_, ToNode) -> do
495 let ctrlMsg = decode . BSL.fromChunks $ payload
496 writeChan ctrlChan $! ctrlMsg
498 Just (src, Uninit) ->
499 case decode (BSL.fromChunks payload) of
500 ProcessIdentifier pid -> do
501 let lpid = processLocalId pid
502 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
505 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
507 invalidRequest cid st
508 SendPortIdentifier chId -> do
509 let lcid = sendPortLocalId chId
510 lpid = processLocalId (sendPortProcessId chId)
511 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
514 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
517 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
519 invalidRequest cid st
521 invalidRequest cid st
522 NodeIdentifier nid ->
523 if nid == localNodeId node
524 then go (incomingAt cid ^= Just (src, ToNode) $ st)
525 else invalidRequest cid st
527 invalidRequest cid st
528 NT.ConnectionClosed cid ->
529 case st ^. incomingAt cid of
531 invalidRequest cid st
533 trace node (MxDisconnected cid src)
534 go ( (incomingAt cid ^= Nothing)
535 . (incomingFrom src ^: Set.delete cid)
538 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
539 -- [Unified table 9, rule node_disconnect]
540 let nid = NodeIdentifier $ NodeId theirAddr
541 writeChan ctrlChan NCMsg
542 { ctrlMsgSender = nid
543 , ctrlMsgSignal = Died nid DiedDisconnect
545 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
546 closeImplicitReconnections node nid
547 go ( (incomingFrom theirAddr ^= Set.empty)
548 . (incoming ^: Map.filterWithKey (const . notLost))
551 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
552 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
553 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
554 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
557 NT.ReceivedMulticast _ _ ->
558 -- If we received a multicast message, something went horribly wrong
559 -- and we just give up
560 fail "Cloud Haskell fatal error: received unexpected multicast"
562 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
563 invalidRequest cid st = do
564 -- TODO: We should treat this as a fatal error on the part of the remote
565 -- node. That is, we should report the remote node as having died, and we
566 -- should close incoming connections (this requires a Transport layer
568 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
570 go ( incomingAt cid ^= Nothing
574 state = localState node
575 endpoint = localEndPoint node
576 ctrlChan = localCtrlChan node
578--------------------------------------------------------------------------------
579-- Top-level access to the node controller --
580--------------------------------------------------------------------------------
582runNodeController :: LocalNode -> IO ()
584 runReaderT (evalStateT (unNC nodeController) initNCState)
586--------------------------------------------------------------------------------
587-- Internal data types --
588--------------------------------------------------------------------------------
590data NCState = NCState
591 { -- Mapping from remote processes to linked local processes
592 _links :: !(Map Identifier (Set ProcessId))
593 -- Mapping from remote processes to monitoring local processes
594 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
595 -- Process registry: names and where they live, mapped to the PIDs
596 , _registeredHere :: !(Map String ProcessId)
597 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
600newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
601 deriving ( Applicative
606 , MonadReader LocalNode
609initNCState :: NCState
610initNCState = NCState { _links = Map.empty
611 , _monitors = Map.empty
612 , _registeredHere = Map.empty
613 , _registeredOnNodes = Map.empty
616-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
617-- type is deliberately not exported so it cannot be caught explicitly.
618data ProcessKillException =
619 ProcessKillException !ProcessId !String
622instance Exception ProcessKillException
623instance Show ProcessKillException where
624 show (ProcessKillException pid reason) =
625 "killed-by=" ++ show pid ++ ",reason=" ++ reason
627--------------------------------------------------------------------------------
628-- Tracing/Debugging --
629--------------------------------------------------------------------------------
631-- [Issue #104 / DP-13]
633traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
634traceNotifyDied node ident reason =
635 -- TODO: sendPortDied notifications
636 liftIO $ withLocalTracer node $ \t ->
638 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
639 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
642traceEventFmtIO :: LocalNode
646traceEventFmtIO node fmt args =
647 withLocalTracer node $ \t -> traceLogFmt t fmt args
649trace :: LocalNode -> MxEvent -> IO ()
650trace node ev = withLocalTracer node $ \t -> traceEvent t ev
652withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
653withLocalTracer node act = act (localEventBus node)
655--------------------------------------------------------------------------------
656-- Core functionality --
657--------------------------------------------------------------------------------
660nodeController :: NC ()
664 msg <- liftIO $ readChan (localCtrlChan node)
666 -- [Unified: Table 7, rule nc_forward]
667 case destNid (ctrlMsgSignal msg) of
668 Just nid' | nid' /= localNodeId node ->
669 liftIO $ sendBinary node
671 (NodeIdentifier nid')
672 WithImplicitReconnect
678 NCMsg (ProcessIdentifier from) (Link them) ->
679 ncEffectMonitor from them Nothing
680 NCMsg (ProcessIdentifier from) (Monitor ref) ->
681 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
682 NCMsg (ProcessIdentifier from) (Unlink them) ->
683 ncEffectUnlink from them
684 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
685 ncEffectUnmonitor from ref
686 NCMsg _from (Died ident reason) ->
687 ncEffectDied ident reason
688 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
689 ncEffectSpawn from proc ref
690 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
691 ncEffectRegister from label atnode pid force
692 NCMsg (ProcessIdentifier from) (WhereIs label) ->
693 ncEffectWhereIs from label
694 NCMsg _ (NamedSend label msg') ->
695 ncEffectNamedSend label msg'
696 NCMsg _ (LocalSend to msg') ->
697 ncEffectLocalSend node to msg'
698 NCMsg _ (LocalPortSend to msg') ->
699 ncEffectLocalPortSend to msg'
700 NCMsg (ProcessIdentifier from) (Kill to reason) ->
701 ncEffectKill from to reason
702 NCMsg (ProcessIdentifier from) (Exit to reason) ->
703 ncEffectExit from to reason
704 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
705 ncEffectGetInfo from pid
706 NCMsg _ SigShutdown ->
708 NT.closeEndPoint (localEndPoint node)
709 `Exception.finally` throwIO ThreadKilled
710 -- ThreadKilled seems to make more sense than fail/error here
711 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
712 ncEffectGetNodeStats from nid
714 error $ "nodeController: unexpected message " ++ show unexpected
716-- [Unified: Table 10]
717ncEffectMonitor :: ProcessId -- ^ Who's watching?
718 -> Identifier -- ^ Who's being watched?
719 -> Maybe MonitorRef -- ^ 'Nothing' to link
721ncEffectMonitor from them mRef = do
724 if not (isLocal node them)
726 else isValidLocalIdentifier them
727 case (shouldLink, isLocal node (ProcessIdentifier from)) of
728 (True, _) -> -- [Unified: first rule]
730 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
731 Nothing -> modify' $ linksFor them ^: Set.insert from
732 (False, True) -> -- [Unified: second rule]
733 notifyDied from them DiedUnknownId mRef
734 (False, False) -> -- [Unified: third rule]
735 -- TODO: this is the right sender according to the Unified semantics,
736 -- but perhaps having 'them' as the sender would make more sense
737 -- (see also: notifyDied)
738 liftIO $ sendBinary node
739 (NodeIdentifier $ localNodeId node)
740 (NodeIdentifier $ processNodeId from)
741 WithImplicitReconnect
743 { ctrlMsgSender = NodeIdentifier (localNodeId node)
744 , ctrlMsgSignal = Died them DiedUnknownId
747-- [Unified: Table 11]
748ncEffectUnlink :: ProcessId -> Identifier -> NC ()
749ncEffectUnlink from them = do
751 when (isLocal node (ProcessIdentifier from)) $
753 ProcessIdentifier pid ->
754 postAsMessage from $ DidUnlinkProcess pid
755 NodeIdentifier nid ->
756 postAsMessage from $ DidUnlinkNode nid
757 SendPortIdentifier cid ->
758 postAsMessage from $ DidUnlinkPort cid
759 modify' $ linksFor them ^: Set.delete from
761-- [Unified: Table 11]
762ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
763ncEffectUnmonitor from ref = do
765 when (isLocal node (ProcessIdentifier from)) $
766 postAsMessage from $ DidUnmonitor ref
767 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
769-- [Unified: Table 12]
770ncEffectDied :: Identifier -> DiedReason -> NC ()
771ncEffectDied ident reason = do
773 traceNotifyDied node ident reason
774 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
775 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
777-- _registry :: !(Map (String,NodeId) ProcessId)
779 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
781 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
783 when (localOnly <= isLocal node (ProcessIdentifier us)) $
784 notifyDied us them reason Nothing
786 forM_ (Map.toList affectedMons) $ \(them, refs) ->
787 forM_ refs $ \(us, ref) ->
788 when (localOnly <= isLocal node (ProcessIdentifier us)) $
789 notifyDied us them reason (Just ref)
791 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
793 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
795 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
796 mapM (\(pid,nidlist) ->
797 case ident `impliesDeathOf` ProcessIdentifier pid of
799 do forM_ nidlist $ \(nid,_) ->
800 when (not $ isLocal node (NodeIdentifier nid))
801 (forwardNameDeath node nid)
803 False -> return $ Just (pid,nidlist) )
804 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
806 forwardNameDeath node nid =
807 liftIO $ sendBinary node
808 (NodeIdentifier $ localNodeId node)
809 (NodeIdentifier $ nid)
810 WithImplicitReconnect
812 { ctrlMsgSender = NodeIdentifier (localNodeId node)
813 , ctrlMsgSignal = Died ident reason
816-- [Unified: Table 13]
817ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
818ncEffectSpawn pid cProc ref = do
819 mProc <- unClosure cProc
820 -- If the closure does not exist, we spawn a process that throws an exception
821 -- This allows the remote node to find out what's happening
823 let proc = case mProc of
824 Left err -> fail $ "Error: Could not resolve closure: " ++ err
827 pid' <- liftIO $ forkProcess node proc
828 liftIO $ sendMessage node
829 (NodeIdentifier (localNodeId node))
830 (ProcessIdentifier pid)
831 WithImplicitReconnect
834-- Unified semantics does not explicitly describe how to implement 'register',
835-- but mentions it's "very similar to nsend" (Table 14)
836-- We send a response indicated if the operation is invalid
837ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
838ncEffectRegister from label atnode mPid reregistration = do
840 currentVal <- gets (^. registeredHereFor label)
843 Nothing -> -- unregister request
844 return $ isJust currentVal
845 Just thepid -> -- register request
846 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
847 return $ (isNothing currentVal /= reregistration) &&
848 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
849 if isLocal node (NodeIdentifier atnode)
850 then do when (isOk) $
851 do modify' $ registeredHereFor label ^= mPid
852 updateRemote node currentVal mPid
854 (Just p) -> liftIO $ trace node (MxRegistered p label)
855 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
856 liftIO $ sendMessage node
857 (NodeIdentifier (localNodeId node))
858 (ProcessIdentifier from)
859 WithImplicitReconnect
860 (RegisterReply label isOk)
862 case reregistration of
864 False -> flip incList
867 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
868 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
869 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
870 forward node (processNodeId newval) (Register label atnode (Just newval) False)
871 updateRemote node Nothing (Just newval) =
872 forward node (processNodeId newval) (Register label atnode (Just newval) False)
873 updateRemote node (Just oldval) Nothing =
874 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
875 updateRemote _ _ _ = return ()
876 maybeify f Nothing = unmaybeify $ f []
877 maybeify f (Just x) = unmaybeify $ f x
879 unmaybeify [] = Nothing
880 unmaybeify x = Just x
881 incList [] tag = [(tag,1)]
882 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
883 incList (x:xs) tag = x : incList xs tag
885 decList ((atag,1):xs) tag | atag == tag = xs
886 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
887 decList (x:xs) tag = x:decList xs tag
888 forward node to reg =
889 when (not $ isLocal node (NodeIdentifier to)) $
890 liftIO $ sendBinary node
891 (ProcessIdentifier from)
893 WithImplicitReconnect
895 { ctrlMsgSender = ProcessIdentifier from
896 , ctrlMsgSignal = reg
900-- Unified semantics does not explicitly describe 'whereis'
901ncEffectWhereIs :: ProcessId -> String -> NC ()
902ncEffectWhereIs from label = do
904 mPid <- gets (^. registeredHereFor label)
905 liftIO $ sendMessage node
906 (NodeIdentifier (localNodeId node))
907 (ProcessIdentifier from)
908 WithImplicitReconnect
909 (WhereIsReply label mPid)
911-- [Unified: Table 14]
912ncEffectNamedSend :: String -> Message -> NC ()
913ncEffectNamedSend label msg = do
914 mPid <- gets (^. registeredHereFor label)
915 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
916 forM_ mPid $ \pid -> postMessage pid msg
919ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
920ncEffectLocalSend node to msg =
921 liftIO $ withLocalProc node to $ \p -> do
922 enqueue (processQueue p) msg
923 trace node (MxReceived to msg)
926ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
927ncEffectLocalPortSend from msg = do
929 let pid = sendPortProcessId from
930 cid = sendPortLocalId from
931 liftIO $ withLocalProc node pid $ \proc -> do
932 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
934 -- in the unlikely event we know nothing about this channel id,
935 -- there's little to be done - perhaps some logging/tracing though...
937 Just (TypedChannel chan') -> do
938 -- If ch is Nothing, the process has given up the read end of
939 -- the channel and we simply ignore the incoming message - this
940 ch <- deRefWeak chan'
941 forM_ ch $ \chan -> deliverChan msg chan
942 where deliverChan :: forall a . Message -> TQueue a -> IO ()
943 deliverChan (UnencodedMessage _ raw) chan' =
944 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
945 deliverChan (EncodedMessage _ _) _ =
946 -- this will not happen unless someone screws with Primitives.hs
947 error "invalid local channel delivery"
950ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
951ncEffectKill from to reason = do
953 when (isLocal node (ProcessIdentifier to)) $
954 throwException to $ ProcessKillException from reason
957ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
958ncEffectExit from to reason = do
960 when (isLocal node (ProcessIdentifier to)) $
961 throwException to $ ProcessExitException from reason
964ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
965ncEffectGetInfo from pid =
966 let lpid = processLocalId pid
967 them = (ProcessIdentifier pid)
971 withMVar (localState node) $ return . (^. localProcessWithId lpid)
973 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
974 from node (ProcessInfoNone DiedUnknownId)
976 itsLinks <- gets (^. linksFor them)
977 itsMons <- gets (^. monitorsFor them)
978 registered <- gets (^. registeredHere)
980 let reg = registeredNames registered
981 dispatch (isLocal node (ProcessIdentifier from))
985 infoNode = (processNodeId pid)
986 , infoRegisteredNames = reg
987 -- we cannot populate this field
988 , infoMessageQueueLength = Nothing
989 , infoMonitors = Set.toList itsMons
990 , infoLinks = Set.toList itsLinks
992 where dispatch :: (Serializable a, Show a)
998 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
999 dispatch False dest node pInfo = do
1000 liftIO $ sendMessage node
1001 (NodeIdentifier (localNodeId node))
1002 (ProcessIdentifier dest)
1003 WithImplicitReconnect
1006 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1010ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1011ncEffectGetNodeStats from _nid = do
1013 ncState <- StateT.get
1014 nodeState <- liftIO $ readMVar (localState node)
1015 let localProcesses' = nodeState ^. localProcesses
1018 nodeStatsNode = localNodeId node
1019 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1020 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1021 , nodeStatsLinks = Map.size $ ncState ^. links
1022 , nodeStatsProcesses = Map.size localProcesses'
1024 postAsMessage from stats
1025--------------------------------------------------------------------------------
1027--------------------------------------------------------------------------------
1029notifyDied :: ProcessId -- ^ Who to notify?
1030 -> Identifier -- ^ Who died?
1031 -> DiedReason -- ^ How did they die?
1032 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1034notifyDied dest src reason mRef = do
1036 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1037 (True, Just ref, ProcessIdentifier pid) ->
1038 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1039 (True, Just ref, NodeIdentifier nid) ->
1040 postAsMessage dest $ NodeMonitorNotification ref nid reason
1041 (True, Just ref, SendPortIdentifier cid) ->
1042 postAsMessage dest $ PortMonitorNotification ref cid reason
1043 (True, Nothing, ProcessIdentifier pid) ->
1044 throwException dest $ ProcessLinkException pid reason
1045 (True, Nothing, NodeIdentifier pid) ->
1046 throwException dest $ NodeLinkException pid reason
1047 (True, Nothing, SendPortIdentifier pid) ->
1048 throwException dest $ PortLinkException pid reason
1050 -- The change in sender comes from [Unified: Table 10]
1051 liftIO $ sendBinary node
1052 (NodeIdentifier $ localNodeId node)
1053 (NodeIdentifier $ processNodeId dest)
1054 WithImplicitReconnect
1056 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1057 , ctrlMsgSignal = Died src reason
1060-- | [Unified: Table 8]
1061destNid :: ProcessSignal -> Maybe NodeId
1062destNid (Link ident) = Just $ nodeOf ident
1063destNid (Unlink ident) = Just $ nodeOf ident
1064destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1065destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1066destNid (Spawn _ _) = Nothing
1067destNid (Register _ _ _ _) = Nothing
1068destNid (WhereIs _) = Nothing
1069destNid (NamedSend _ _) = Nothing
1070-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1071-- then when a local process dies the monitoring/linking machinery will take
1072-- care of notifying remote nodes
1073destNid (Died _ _) = Nothing
1074destNid (Kill pid _) = Just $ processNodeId pid
1075destNid (Exit pid _) = Just $ processNodeId pid
1076destNid (GetInfo pid) = Just $ processNodeId pid
1077destNid (GetNodeStats nid) = Just nid
1078destNid (LocalSend pid _) = Just $ processNodeId pid
1079destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1080destNid (SigShutdown) = Nothing
1082-- | Check if a process is local to our own node
1083isLocal :: LocalNode -> Identifier -> Bool
1084isLocal nid ident = nodeOf ident == localNodeId nid
1086-- | Lookup a local closure
1087unClosure :: Typeable a => Closure a -> NC (Either String a)
1088unClosure closure = do
1089 rtable <- remoteTable <$> ask
1090 return (Static.unclosure rtable closure)
1092-- | Check if an identifier refers to a valid local object
1093isValidLocalIdentifier :: Identifier -> NC Bool
1094isValidLocalIdentifier ident = do
1096 liftIO . withMVar (localState node) $ \nSt ->
1098 NodeIdentifier nid ->
1099 return $ nid == localNodeId node
1100 ProcessIdentifier pid -> do
1101 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1102 return $ isJust mProc
1103 SendPortIdentifier cid -> do
1104 let pid = sendPortProcessId cid
1105 mProc = nSt ^. localProcessWithId (processLocalId pid)
1107 Nothing -> return False
1108 Just proc -> withMVar (processState proc) $ \pSt -> do
1109 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1112--------------------------------------------------------------------------------
1113-- Messages to local processes --
1114--------------------------------------------------------------------------------
1116postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1117postAsMessage pid = postMessage pid . createUnencodedMessage
1119postMessage :: ProcessId -> Message -> NC ()
1120postMessage pid msg = do
1122 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1124throwException :: Exception e => ProcessId -> e -> NC ()
1125throwException pid e = do
1127 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1129withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1130withLocalProc node pid p =
1131 -- By [Unified: table 6, rule missing_process] messages to dead processes
1132 -- can silently be dropped
1133 let lpid = processLocalId pid in do
1134 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1137--------------------------------------------------------------------------------
1139--------------------------------------------------------------------------------
1141links :: Accessor NCState (Map Identifier (Set ProcessId))
1142links = accessor _links (\ls st -> st { _links = ls })
1144monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1145monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1147registeredHere :: Accessor NCState (Map String ProcessId)
1148registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1150registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1151registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1153linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1154linksFor ident = links >>> DAC.mapDefault Set.empty ident
1156monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1157monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1159registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1160registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1162registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1163registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1165-- | @splitNotif ident@ splits a notifications map into those
1166-- notifications that should trigger when 'ident' fails and those links that
1169-- There is a hierarchy between identifiers: failure of a node implies failure
1170-- of all processes on that node, and failure of a process implies failure of
1171-- all typed channels to that process. In other words, if 'ident' refers to a
1172-- node, then the /should trigger/ set will include
1174-- * the notifications for the node specifically
1175-- * the notifications for processes on that node, and
1176-- * the notifications for typed channels to processes on that node.
1178-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1181-- * the notifications for that process specifically and
1182-- * the notifications for typed channels to that process.
1184-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1185splitNotif :: Identifier
1187 -> (Map Identifier a, Map Identifier a)
1188splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1190--------------------------------------------------------------------------------
1192--------------------------------------------------------------------------------
1194-- | Modify and evaluate the state
1195modify' :: MonadState s m => (s -> s) -> m ()
1196modify' 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