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
298 runProcess node $ register "logger" logger
300 fork = forkProcess node
304 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
305 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
307 , match $ \((time, string) :: (String, String)) -> do
308 -- this is a 'trace' message from the local node tracer
309 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
311 , match $ \(ch :: SendPort ()) -> -- a shutdown request
315-- | Force-close a local node
317-- TODO: for now we just close the associated endpoint
318closeLocalNode :: LocalNode -> IO ()
320 -- TODO: close all our processes, surely!?
321 NT.closeEndPoint (localEndPoint node)
323-- | Run a process on a local node and wait for it to finish
324runProcess :: LocalNode -> Process () -> IO ()
325runProcess node proc = do
328 void $ forkProcess node $ do
329 catch (proc `finally` liftIO (putMVar done ()))
330 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
333-- | Spawn a new process on a local node
334forkProcess :: LocalNode -> Process () -> IO ProcessId
335forkProcess node proc =
336 modifyMVarMasked (localState node) startProcess
338 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
340 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
341 , lpidUnique = st ^. localPidUnique
343 let pid = ProcessId { processNodeId = localNodeId node
344 , processLocalId = lpid
346 pst <- newMVar LocalProcessState { _monitorCounter = 0
348 , _channelCounter = 0
349 , _typedChannels = Map.empty
352 weakQueue <- mkWeakCQueue queue (return ())
353 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
354 let lproc = LocalProcess { processQueue = queue
355 , processWeakQ = weakQueue
358 , processThread = tid
361 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
362 reason <- Exception.catches
363 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
364 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
365 mMsg <- unwrapMessage msg :: IO (Maybe String)
367 Nothing -> return $ DiedException $ show ex
368 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
370 (return . DiedException . (show :: SomeException -> String)))]
372 -- [Unified: Table 4, rules termination and exiting]
373 modifyMVar_ (localState node) (cleanupProcess pid)
374 writeChan (localCtrlChan node) NCMsg
375 { ctrlMsgSender = ProcessIdentifier pid
376 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
380 -- see note [tracer/forkProcess races]
381 trace node (MxSpawned pid)
383 if lpidCounter lpid == maxBound
385 -- TODO: this doesn't look right at all - how do we know
386 -- that newUnique represents a process id that is available!?
387 newUnique <- randomIO
388 return ( (localProcessWithId lpid ^= Just lproc)
389 . (localPidCounter ^= firstNonReservedProcessId)
390 . (localPidUnique ^= newUnique)
395 return ( (localProcessWithId lpid ^= Just lproc)
396 . (localPidCounter ^: (+ 1))
401 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
402 cleanupProcess pid st = do
403 let pid' = ProcessIdentifier pid
404 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
405 mapM_ (NT.close . fst) (Map.elems affected)
406 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
407 . (localConnections ^= unaffected)
410-- note [tracer/forkProcess races]
412-- Our startTracing function uses forkProcess to start the trace controller
413-- process, and of course forkProcess attempts to call traceEvent once the
414-- process has started. This is harmless, as the localEventBus is not updated
415-- until /after/ the initial forkProcess completes, so the first call to
416-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
419--------------------------------------------------------------------------------
420-- Handle incoming messages --
421--------------------------------------------------------------------------------
423type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
427 | ToProc ProcessId (Weak (CQueue Message))
428 | ToChan TypedChannel
431data ConnectionState = ConnectionState {
432 _incoming :: !(Map NT.ConnectionId IncomingConnection)
433 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
436initConnectionState :: ConnectionState
437initConnectionState = ConnectionState {
438 _incoming = Map.empty
439 , _incomingFrom = Map.empty
442incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
443incoming = accessor _incoming (\conns st -> st { _incoming = conns })
445incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
446incomingAt cid = incoming >>> DAC.mapMaybe cid
448incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
449incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
451 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
453handleIncomingMessages :: LocalNode -> IO ()
454handleIncomingMessages node = go initConnectionState
456 go :: ConnectionState -> IO ()
458 event <- NT.receive endpoint
460 NT.ConnectionOpened cid rel theirAddr ->
461 if rel == NT.ReliableOrdered
463 trace node (MxConnected cid theirAddr)
465 (incomingAt cid ^= Just (theirAddr, Uninit))
466 . (incomingFrom theirAddr ^: Set.insert cid)
469 else invalidRequest cid st
470 NT.Received cid payload ->
471 case st ^. incomingAt cid of
472 Just (_, ToProc pid weakQueue) -> do
473 mQueue <- deRefWeak weakQueue
474 forM_ mQueue $ \queue -> do
475 -- TODO: if we find that the queue is Nothing, should we remove
476 -- it from the NC state? (and same for channels, below)
477 let msg = payloadToMessage payload
478 enqueue queue msg -- 'enqueue' is strict
479 trace node (MxReceived pid msg)
481 Just (_, ToChan (TypedChannel chan')) -> do
482 mChan <- deRefWeak chan'
483 -- If mChan is Nothing, the process has given up the read end of
484 -- the channel and we simply ignore the incoming message
485 forM_ mChan $ \chan -> atomically $
486 -- We make sure the message is fully decoded when it is enqueued
487 writeTQueue chan $! decode (BSL.fromChunks payload)
489 Just (_, ToNode) -> do
490 let ctrlMsg = decode . BSL.fromChunks $ payload
491 writeChan ctrlChan $! ctrlMsg
493 Just (src, Uninit) ->
494 case decode (BSL.fromChunks payload) of
495 ProcessIdentifier pid -> do
496 let lpid = processLocalId pid
497 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
500 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
502 invalidRequest cid st
503 SendPortIdentifier chId -> do
504 let lcid = sendPortLocalId chId
505 lpid = processLocalId (sendPortProcessId chId)
506 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
509 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
512 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
514 invalidRequest cid st
516 invalidRequest cid st
517 NodeIdentifier nid ->
518 if nid == localNodeId node
519 then go (incomingAt cid ^= Just (src, ToNode) $ st)
520 else invalidRequest cid st
522 invalidRequest cid st
523 NT.ConnectionClosed cid ->
524 case st ^. incomingAt cid of
526 invalidRequest cid st
528 trace node (MxDisconnected cid src)
529 go ( (incomingAt cid ^= Nothing)
530 . (incomingFrom src ^: Set.delete cid)
533 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
534 -- [Unified table 9, rule node_disconnect]
535 let nid = NodeIdentifier $ NodeId theirAddr
536 writeChan ctrlChan NCMsg
537 { ctrlMsgSender = nid
538 , ctrlMsgSignal = Died nid DiedDisconnect
540 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
541 closeImplicitReconnections node nid
542 go ( (incomingFrom theirAddr ^= Set.empty)
543 . (incoming ^: Map.filterWithKey (const . notLost))
546 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
547 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
548 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
549 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
552 NT.ReceivedMulticast _ _ ->
553 -- If we received a multicast message, something went horribly wrong
554 -- and we just give up
555 fail "Cloud Haskell fatal error: received unexpected multicast"
557 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
558 invalidRequest cid st = do
559 -- TODO: We should treat this as a fatal error on the part of the remote
560 -- node. That is, we should report the remote node as having died, and we
561 -- should close incoming connections (this requires a Transport layer
563 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
565 go ( incomingAt cid ^= Nothing
569 state = localState node
570 endpoint = localEndPoint node
571 ctrlChan = localCtrlChan node
573--------------------------------------------------------------------------------
574-- Top-level access to the node controller --
575--------------------------------------------------------------------------------
577runNodeController :: LocalNode -> IO ()
579 runReaderT (evalStateT (unNC nodeController) initNCState)
581--------------------------------------------------------------------------------
582-- Internal data types --
583--------------------------------------------------------------------------------
585data NCState = NCState
586 { -- Mapping from remote processes to linked local processes
587 _links :: !(Map Identifier (Set ProcessId))
588 -- Mapping from remote processes to monitoring local processes
589 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
590 -- Process registry: names and where they live, mapped to the PIDs
591 , _registeredHere :: !(Map String ProcessId)
592 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
595newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
596 deriving ( Applicative
601 , MonadReader LocalNode
604initNCState :: NCState
605initNCState = NCState { _links = Map.empty
606 , _monitors = Map.empty
607 , _registeredHere = Map.empty
608 , _registeredOnNodes = Map.empty
611-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
612-- type is deliberately not exported so it cannot be caught explicitly.
613data ProcessKillException =
614 ProcessKillException !ProcessId !String
617instance Exception ProcessKillException
618instance Show ProcessKillException where
619 show (ProcessKillException pid reason) =
620 "killed-by=" ++ show pid ++ ",reason=" ++ reason
622--------------------------------------------------------------------------------
623-- Tracing/Debugging --
624--------------------------------------------------------------------------------
626-- [Issue #104 / DP-13]
628traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
629traceNotifyDied node ident reason =
630 -- TODO: sendPortDied notifications
631 liftIO $ withLocalTracer node $ \t ->
633 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
634 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
637traceEventFmtIO :: LocalNode
641traceEventFmtIO node fmt args =
642 withLocalTracer node $ \t -> traceLogFmt t fmt args
644trace :: LocalNode -> MxEvent -> IO ()
645trace node ev = withLocalTracer node $ \t -> traceEvent t ev
647withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
648withLocalTracer node act = act (localEventBus node)
650--------------------------------------------------------------------------------
651-- Core functionality --
652--------------------------------------------------------------------------------
655nodeController :: NC ()
659 msg <- liftIO $ readChan (localCtrlChan node)
661 -- [Unified: Table 7, rule nc_forward]
662 case destNid (ctrlMsgSignal msg) of
663 Just nid' | nid' /= localNodeId node ->
664 liftIO $ sendBinary node
666 (NodeIdentifier nid')
667 WithImplicitReconnect
673 NCMsg (ProcessIdentifier from) (Link them) ->
674 ncEffectMonitor from them Nothing
675 NCMsg (ProcessIdentifier from) (Monitor ref) ->
676 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
677 NCMsg (ProcessIdentifier from) (Unlink them) ->
678 ncEffectUnlink from them
679 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
680 ncEffectUnmonitor from ref
681 NCMsg _from (Died ident reason) ->
682 ncEffectDied ident reason
683 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
684 ncEffectSpawn from proc ref
685 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
686 ncEffectRegister from label atnode pid force
687 NCMsg (ProcessIdentifier from) (WhereIs label) ->
688 ncEffectWhereIs from label
689 NCMsg _ (NamedSend label msg') ->
690 ncEffectNamedSend label msg'
691 NCMsg _ (LocalSend to msg') ->
692 ncEffectLocalSend node to msg'
693 NCMsg _ (LocalPortSend to msg') ->
694 ncEffectLocalPortSend to msg'
695 NCMsg (ProcessIdentifier from) (Kill to reason) ->
696 ncEffectKill from to reason
697 NCMsg (ProcessIdentifier from) (Exit to reason) ->
698 ncEffectExit from to reason
699 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
700 ncEffectGetInfo from pid
701 NCMsg _ SigShutdown ->
703 NT.closeEndPoint (localEndPoint node)
704 `Exception.finally` throwIO ThreadKilled
705 -- ThreadKilled seems to make more sense than fail/error here
706 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
707 ncEffectGetNodeStats from nid
709 error $ "nodeController: unexpected message " ++ show unexpected
711-- [Unified: Table 10]
712ncEffectMonitor :: ProcessId -- ^ Who's watching?
713 -> Identifier -- ^ Who's being watched?
714 -> Maybe MonitorRef -- ^ 'Nothing' to link
716ncEffectMonitor from them mRef = do
719 if not (isLocal node them)
721 else isValidLocalIdentifier them
722 case (shouldLink, isLocal node (ProcessIdentifier from)) of
723 (True, _) -> -- [Unified: first rule]
725 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
726 Nothing -> modify' $ linksFor them ^: Set.insert from
727 (False, True) -> -- [Unified: second rule]
728 notifyDied from them DiedUnknownId mRef
729 (False, False) -> -- [Unified: third rule]
730 -- TODO: this is the right sender according to the Unified semantics,
731 -- but perhaps having 'them' as the sender would make more sense
732 -- (see also: notifyDied)
733 liftIO $ sendBinary node
734 (NodeIdentifier $ localNodeId node)
735 (NodeIdentifier $ processNodeId from)
736 WithImplicitReconnect
738 { ctrlMsgSender = NodeIdentifier (localNodeId node)
739 , ctrlMsgSignal = Died them DiedUnknownId
742-- [Unified: Table 11]
743ncEffectUnlink :: ProcessId -> Identifier -> NC ()
744ncEffectUnlink from them = do
746 when (isLocal node (ProcessIdentifier from)) $
748 ProcessIdentifier pid ->
749 postAsMessage from $ DidUnlinkProcess pid
750 NodeIdentifier nid ->
751 postAsMessage from $ DidUnlinkNode nid
752 SendPortIdentifier cid ->
753 postAsMessage from $ DidUnlinkPort cid
754 modify' $ linksFor them ^: Set.delete from
756-- [Unified: Table 11]
757ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
758ncEffectUnmonitor from ref = do
760 when (isLocal node (ProcessIdentifier from)) $
761 postAsMessage from $ DidUnmonitor ref
762 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
764-- [Unified: Table 12]
765ncEffectDied :: Identifier -> DiedReason -> NC ()
766ncEffectDied ident reason = do
768 traceNotifyDied node ident reason
769 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
770 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
772-- _registry :: !(Map (String,NodeId) ProcessId)
774 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
776 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
778 when (localOnly <= isLocal node (ProcessIdentifier us)) $
779 notifyDied us them reason Nothing
781 forM_ (Map.toList affectedMons) $ \(them, refs) ->
782 forM_ refs $ \(us, ref) ->
783 when (localOnly <= isLocal node (ProcessIdentifier us)) $
784 notifyDied us them reason (Just ref)
786 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
788 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
790 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
791 mapM (\(pid,nidlist) ->
792 case ident `impliesDeathOf` ProcessIdentifier pid of
794 do forM_ nidlist $ \(nid,_) ->
795 when (not $ isLocal node (NodeIdentifier nid))
796 (forwardNameDeath node nid)
798 False -> return $ Just (pid,nidlist) )
799 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
801 forwardNameDeath node nid =
802 liftIO $ sendBinary node
803 (NodeIdentifier $ localNodeId node)
804 (NodeIdentifier $ nid)
805 WithImplicitReconnect
807 { ctrlMsgSender = NodeIdentifier (localNodeId node)
808 , ctrlMsgSignal = Died ident reason
811-- [Unified: Table 13]
812ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
813ncEffectSpawn pid cProc ref = do
814 mProc <- unClosure cProc
815 -- If the closure does not exist, we spawn a process that throws an exception
816 -- This allows the remote node to find out what's happening
818 let proc = case mProc of
819 Left err -> fail $ "Error: Could not resolve closure: " ++ err
822 pid' <- liftIO $ forkProcess node proc
823 liftIO $ sendMessage node
824 (NodeIdentifier (localNodeId node))
825 (ProcessIdentifier pid)
826 WithImplicitReconnect
829-- Unified semantics does not explicitly describe how to implement 'register',
830-- but mentions it's "very similar to nsend" (Table 14)
831-- We send a response indicated if the operation is invalid
832ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
833ncEffectRegister from label atnode mPid reregistration = do
835 currentVal <- gets (^. registeredHereFor label)
838 Nothing -> -- unregister request
839 return $ isJust currentVal
840 Just thepid -> -- register request
841 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
842 return $ (isNothing currentVal /= reregistration) &&
843 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
844 if isLocal node (NodeIdentifier atnode)
845 then do when (isOk) $
846 do modify' $ registeredHereFor label ^= mPid
847 updateRemote node currentVal mPid
849 (Just p) -> liftIO $ trace node (MxRegistered p label)
850 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
851 liftIO $ sendMessage node
852 (NodeIdentifier (localNodeId node))
853 (ProcessIdentifier from)
854 WithImplicitReconnect
855 (RegisterReply label isOk)
857 case reregistration of
859 False -> flip incList
862 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
863 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
864 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
865 forward node (processNodeId newval) (Register label atnode (Just newval) False)
866 updateRemote node Nothing (Just newval) =
867 forward node (processNodeId newval) (Register label atnode (Just newval) False)
868 updateRemote node (Just oldval) Nothing =
869 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
870 updateRemote _ _ _ = return ()
871 maybeify f Nothing = unmaybeify $ f []
872 maybeify f (Just x) = unmaybeify $ f x
874 unmaybeify [] = Nothing
875 unmaybeify x = Just x
876 incList [] tag = [(tag,1)]
877 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
878 incList (x:xs) tag = x : incList xs tag
880 decList ((atag,1):xs) tag | atag == tag = xs
881 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
882 decList (x:xs) tag = x:decList xs tag
883 forward node to reg =
884 when (not $ isLocal node (NodeIdentifier to)) $
885 liftIO $ sendBinary node
886 (ProcessIdentifier from)
888 WithImplicitReconnect
890 { ctrlMsgSender = ProcessIdentifier from
891 , ctrlMsgSignal = reg
895-- Unified semantics does not explicitly describe 'whereis'
896ncEffectWhereIs :: ProcessId -> String -> NC ()
897ncEffectWhereIs from label = do
899 mPid <- gets (^. registeredHereFor label)
900 liftIO $ sendMessage node
901 (NodeIdentifier (localNodeId node))
902 (ProcessIdentifier from)
903 WithImplicitReconnect
904 (WhereIsReply label mPid)
906-- [Unified: Table 14]
907ncEffectNamedSend :: String -> Message -> NC ()
908ncEffectNamedSend label msg = do
909 mPid <- gets (^. registeredHereFor label)
910 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
911 forM_ mPid $ \pid -> postMessage pid msg
914ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
915ncEffectLocalSend node to msg =
916 liftIO $ withLocalProc node to $ \p -> do
917 enqueue (processQueue p) msg
918 trace node (MxReceived to msg)
921ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
922ncEffectLocalPortSend from msg = do
924 let pid = sendPortProcessId from
925 cid = sendPortLocalId from
926 liftIO $ withLocalProc node pid $ \proc -> do
927 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
929 -- in the unlikely event we know nothing about this channel id,
930 -- there's little to be done - perhaps some logging/tracing though...
932 Just (TypedChannel chan') -> do
933 -- If ch is Nothing, the process has given up the read end of
934 -- the channel and we simply ignore the incoming message - this
935 ch <- deRefWeak chan'
936 forM_ ch $ \chan -> deliverChan msg chan
937 where deliverChan :: forall a . Message -> TQueue a -> IO ()
938 deliverChan (UnencodedMessage _ raw) chan' =
939 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
940 deliverChan (EncodedMessage _ _) _ =
941 -- this will not happen unless someone screws with Primitives.hs
942 error "invalid local channel delivery"
945ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
946ncEffectKill from to reason = do
948 when (isLocal node (ProcessIdentifier to)) $
949 throwException to $ ProcessKillException from reason
952ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
953ncEffectExit from to reason = do
955 when (isLocal node (ProcessIdentifier to)) $
956 throwException to $ ProcessExitException from reason
959ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
960ncEffectGetInfo from pid =
961 let lpid = processLocalId pid
962 them = (ProcessIdentifier pid)
966 withMVar (localState node) $ return . (^. localProcessWithId lpid)
968 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
969 from node (ProcessInfoNone DiedUnknownId)
971 itsLinks <- gets (^. linksFor them)
972 itsMons <- gets (^. monitorsFor them)
973 registered <- gets (^. registeredHere)
975 let reg = registeredNames registered
976 dispatch (isLocal node (ProcessIdentifier from))
980 infoNode = (processNodeId pid)
981 , infoRegisteredNames = reg
982 -- we cannot populate this field
983 , infoMessageQueueLength = Nothing
984 , infoMonitors = Set.toList itsMons
985 , infoLinks = Set.toList itsLinks
987 where dispatch :: (Serializable a, Show a)
993 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
994 dispatch False dest node pInfo = do
995 liftIO $ sendMessage node
996 (NodeIdentifier (localNodeId node))
997 (ProcessIdentifier dest)
998 WithImplicitReconnect
1001 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1005ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1006ncEffectGetNodeStats from _nid = do
1008 ncState <- StateT.get
1009 nodeState <- liftIO $ readMVar (localState node)
1010 let localProcesses' = nodeState ^. localProcesses
1013 nodeStatsNode = localNodeId node
1014 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1015 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1016 , nodeStatsLinks = Map.size $ ncState ^. links
1017 , nodeStatsProcesses = Map.size localProcesses'
1019 postAsMessage from stats
1020--------------------------------------------------------------------------------
1022--------------------------------------------------------------------------------
1024notifyDied :: ProcessId -- ^ Who to notify?
1025 -> Identifier -- ^ Who died?
1026 -> DiedReason -- ^ How did they die?
1027 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1029notifyDied dest src reason mRef = do
1031 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1032 (True, Just ref, ProcessIdentifier pid) ->
1033 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1034 (True, Just ref, NodeIdentifier nid) ->
1035 postAsMessage dest $ NodeMonitorNotification ref nid reason
1036 (True, Just ref, SendPortIdentifier cid) ->
1037 postAsMessage dest $ PortMonitorNotification ref cid reason
1038 (True, Nothing, ProcessIdentifier pid) ->
1039 throwException dest $ ProcessLinkException pid reason
1040 (True, Nothing, NodeIdentifier pid) ->
1041 throwException dest $ NodeLinkException pid reason
1042 (True, Nothing, SendPortIdentifier pid) ->
1043 throwException dest $ PortLinkException pid reason
1045 -- The change in sender comes from [Unified: Table 10]
1046 liftIO $ sendBinary node
1047 (NodeIdentifier $ localNodeId node)
1048 (NodeIdentifier $ processNodeId dest)
1049 WithImplicitReconnect
1051 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1052 , ctrlMsgSignal = Died src reason
1055-- | [Unified: Table 8]
1056destNid :: ProcessSignal -> Maybe NodeId
1057destNid (Link ident) = Just $ nodeOf ident
1058destNid (Unlink ident) = Just $ nodeOf ident
1059destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1060destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1061destNid (Spawn _ _) = Nothing
1062destNid (Register _ _ _ _) = Nothing
1063destNid (WhereIs _) = Nothing
1064destNid (NamedSend _ _) = Nothing
1065-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1066-- then when a local process dies the monitoring/linking machinery will take
1067-- care of notifying remote nodes
1068destNid (Died _ _) = Nothing
1069destNid (Kill pid _) = Just $ processNodeId pid
1070destNid (Exit pid _) = Just $ processNodeId pid
1071destNid (GetInfo pid) = Just $ processNodeId pid
1072destNid (GetNodeStats nid) = Just nid
1073destNid (LocalSend pid _) = Just $ processNodeId pid
1074destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1075destNid (SigShutdown) = Nothing
1077-- | Check if a process is local to our own node
1078isLocal :: LocalNode -> Identifier -> Bool
1079isLocal nid ident = nodeOf ident == localNodeId nid
1081-- | Lookup a local closure
1082unClosure :: Typeable a => Closure a -> NC (Either String a)
1083unClosure closure = do
1084 rtable <- remoteTable <$> ask
1085 return (Static.unclosure rtable closure)
1087-- | Check if an identifier refers to a valid local object
1088isValidLocalIdentifier :: Identifier -> NC Bool
1089isValidLocalIdentifier ident = do
1091 liftIO . withMVar (localState node) $ \nSt ->
1093 NodeIdentifier nid ->
1094 return $ nid == localNodeId node
1095 ProcessIdentifier pid -> do
1096 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1097 return $ isJust mProc
1098 SendPortIdentifier cid -> do
1099 let pid = sendPortProcessId cid
1100 mProc = nSt ^. localProcessWithId (processLocalId pid)
1102 Nothing -> return False
1103 Just proc -> withMVar (processState proc) $ \pSt -> do
1104 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1107--------------------------------------------------------------------------------
1108-- Messages to local processes --
1109--------------------------------------------------------------------------------
1111postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1112postAsMessage pid = postMessage pid . createUnencodedMessage
1114postMessage :: ProcessId -> Message -> NC ()
1115postMessage pid msg = do
1117 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1119throwException :: Exception e => ProcessId -> e -> NC ()
1120throwException pid e = do
1122 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1124withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1125withLocalProc node pid p =
1126 -- By [Unified: table 6, rule missing_process] messages to dead processes
1127 -- can silently be dropped
1128 let lpid = processLocalId pid in do
1129 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1132--------------------------------------------------------------------------------
1134--------------------------------------------------------------------------------
1136links :: Accessor NCState (Map Identifier (Set ProcessId))
1137links = accessor _links (\ls st -> st { _links = ls })
1139monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1140monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1142registeredHere :: Accessor NCState (Map String ProcessId)
1143registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1145registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1146registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1148linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1149linksFor ident = links >>> DAC.mapDefault Set.empty ident
1151monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1152monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1154registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1155registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1157registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1158registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1160-- | @splitNotif ident@ splits a notifications map into those
1161-- notifications that should trigger when 'ident' fails and those links that
1164-- There is a hierarchy between identifiers: failure of a node implies failure
1165-- of all processes on that node, and failure of a process implies failure of
1166-- all typed channels to that process. In other words, if 'ident' refers to a
1167-- node, then the /should trigger/ set will include
1169-- * the notifications for the node specifically
1170-- * the notifications for processes on that node, and
1171-- * the notifications for typed channels to processes on that node.
1173-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1176-- * the notifications for that process specifically and
1177-- * the notifications for typed channels to that process.
1179-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1180splitNotif :: Identifier
1182 -> (Map Identifier a, Map Identifier a)
1183splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1185--------------------------------------------------------------------------------
1187--------------------------------------------------------------------------------
1189-- | Modify and evaluate the state
1190modify' :: MonadState s m => (s -> s) -> m ()
1191modify' 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