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)
68import qualified Control.Exception as Exception (Handler(..), catches, finally)
69import Control.Concurrent (forkIO, myThreadId)
70import Control.Distributed.Process.Internal.StrictMVar
80import Control.Concurrent.Chan (newChan, writeChan, readChan)
81import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
82import Control.Concurrent.STM
85import Control.Distributed.Process.Internal.CQueue
91import qualified Network.Transport as NT
104 , Reliability(ReliableOrdered)
106import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
107import System.Random (randomIO)
108import Control.Distributed.Static (RemoteTable, Closure)
109import qualified Control.Distributed.Static as Static
113import Control.Distributed.Process.Internal.Types
121 , LocalProcessState(..)
133 , ProcessMonitorNotification(..)
134 , NodeMonitorNotification(..)
135 , PortMonitorNotification(..)
136 , ProcessExitException(..)
137 , ProcessLinkException(..)
138 , NodeLinkException(..)
139 , PortLinkException(..)
141 , DidUnlinkProcess(..)
151 , ProcessInfoNone(..)
158 , createUnencodedMessage
160 , firstNonReservedProcessId
161 , ImplicitReconnect(WithImplicitReconnect)
163import Control.Distributed.Process.Management.Internal.Agent
166import qualified Control.Distributed.Process.Management.Internal.Table as Table
168 , startTableCoordinator
170import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
173import Control.Distributed.Process.Management.Internal.Trace.Tracer
176import Control.Distributed.Process.Management.Internal.Trace.Types
182import Control.Distributed.Process.Management.Internal.Types
185import Control.Distributed.Process.Serializable (Serializable)
186import Control.Distributed.Process.Internal.Messaging
189 , closeImplicitReconnections
192import Control.Distributed.Process.Internal.Primitives
201import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
202import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
203import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
204import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
210--------------------------------------------------------------------------------
212--------------------------------------------------------------------------------
214initRemoteTable :: RemoteTable
215initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
217-- | Initialize a new local node.
218newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
219newLocalNode transport rtable = do
220 mEndPoint <- NT.newEndPoint transport
222 Left ex -> throwIO ex
224 localNode <- createBareLocalNode endPoint rtable
225 startServiceProcesses localNode
228-- | Create a new local node (without any service processes running)
229createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
230createBareLocalNode endPoint rtable = do
232 state <- newMVar LocalNodeState
233 { _localProcesses = Map.empty
234 , _localPidCounter = firstNonReservedProcessId
235 , _localPidUnique = unq
236 , _localConnections = Map.empty
239 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
240 , localEndPoint = endPoint
242 , localCtrlChan = ctrlChan
243 , localEventBus = MxEventBusInitialising
244 , remoteTable = rtable
246 tracedNode <- startMxAgent node
248 -- Once the NC terminates, the endpoint isn't much use,
249 void $ forkIO $ Exception.finally (runNodeController tracedNode)
250 (NT.closeEndPoint (localEndPoint node))
252 -- whilst a closed/failing endpoint will terminate the NC
253 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
259 writeChan (localCtrlChan node) NCMsg
260 { ctrlMsgSender = NodeIdentifier (localNodeId node)
261 , ctrlMsgSignal = SigShutdown
264startMxAgent :: LocalNode -> IO LocalNode
265startMxAgent node = do
266 -- see note [tracer/forkProcess races]
267 let fork = forkProcess node
268 mv <- MVar.newEmptyMVar
269 pid <- fork $ mxAgentController fork mv
270 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
271 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
273startDefaultTracer :: LocalNode -> IO ()
274startDefaultTracer node' = do
275 let t = localEventBus node'
277 MxEventBus _ (Tracer pid _) _ _ -> do
278 runProcess node' $ register "trace.controller" pid
279 pid' <- forkProcess node' defaultTracer
280 enableTrace (localEventBus node') pid'
281 runProcess node' $ register "tracer.initial" pid'
284-- TODO: we need a better mechanism for defining and registering services
286-- | Start and register the service processes on a node
287startServiceProcesses :: LocalNode -> IO ()
288startServiceProcesses node = do
289 -- tracing /spawns/ relies on the tracer being enabled, but we start
290 -- the default tracer first, even though it might @nsend@ to the logger
291 -- before /that/ process has started - this is a totally harmless race
292 -- however, so we deliberably ignore it
293 startDefaultTracer node
294 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
295 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
296 logger <- forkProcess node loop
297 runProcess node $ register "logger" logger
299 fork = forkProcess node
303 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
304 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
306 , match $ \((time, string) :: (String, String)) -> do
307 -- this is a 'trace' message from the local node tracer
308 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
310 , match $ \(ch :: SendPort ()) -> -- a shutdown request
314-- | Force-close a local node
316-- TODO: for now we just close the associated endpoint
317closeLocalNode :: LocalNode -> IO ()
319 -- TODO: close all our processes, surely!?
320 NT.closeEndPoint (localEndPoint node)
322-- | Run a process on a local node and wait for it to finish
323runProcess :: LocalNode -> Process () -> IO ()
324runProcess node proc = do
327 void $ forkProcess node $ do
328 catch (proc `finally` liftIO (putMVar done ()))
329 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
332-- | Spawn a new process on a local node
333forkProcess :: LocalNode -> Process () -> IO ProcessId
334forkProcess node proc = modifyMVar (localState node) startProcess
336 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
338 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
339 , lpidUnique = st ^. localPidUnique
341 let pid = ProcessId { processNodeId = localNodeId node
342 , processLocalId = lpid
344 pst <- newMVar LocalProcessState { _monitorCounter = 0
346 , _channelCounter = 0
347 , _typedChannels = Map.empty
350 weakQueue <- mkWeakCQueue queue (return ())
351 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
352 let lproc = LocalProcess { processQueue = queue
353 , processWeakQ = weakQueue
356 , processThread = tid
360 reason <- Exception.catches
361 (runLocalProcess lproc proc >> return DiedNormal)
362 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
363 mMsg <- unwrapMessage msg :: IO (Maybe String)
365 Nothing -> return $ DiedException $ show ex
366 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
368 (return . DiedException . (show :: SomeException -> String)))]
370 -- [Unified: Table 4, rules termination and exiting]
371 modifyMVar_ (localState node) (cleanupProcess pid)
372 writeChan (localCtrlChan node) NCMsg
373 { ctrlMsgSender = ProcessIdentifier pid
374 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
378 -- see note [tracer/forkProcess races]
379 trace node (MxSpawned pid)
381 if lpidCounter lpid == maxBound
383 -- TODO: this doesn't look right at all - how do we know
384 -- that newUnique represents a process id that is available!?
385 newUnique <- randomIO
386 return ( (localProcessWithId lpid ^= Just lproc)
387 . (localPidCounter ^= firstNonReservedProcessId)
388 . (localPidUnique ^= newUnique)
393 return ( (localProcessWithId lpid ^= Just lproc)
394 . (localPidCounter ^: (+ 1))
399 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
400 cleanupProcess pid st = do
401 let pid' = ProcessIdentifier pid
402 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
403 mapM_ (NT.close . fst) (Map.elems affected)
404 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
405 . (localConnections ^= unaffected)
408-- note [tracer/forkProcess races]
410-- Our startTracing function uses forkProcess to start the trace controller
411-- process, and of course forkProcess attempts to call traceEvent once the
412-- process has started. This is harmless, as the localEventBus is not updated
413-- until /after/ the initial forkProcess completes, so the first call to
414-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
417--------------------------------------------------------------------------------
418-- Handle incoming messages --
419--------------------------------------------------------------------------------
421type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
425 | ToProc ProcessId (Weak (CQueue Message))
426 | ToChan TypedChannel
429data ConnectionState = ConnectionState {
430 _incoming :: !(Map NT.ConnectionId IncomingConnection)
431 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
434initConnectionState :: ConnectionState
435initConnectionState = ConnectionState {
436 _incoming = Map.empty
437 , _incomingFrom = Map.empty
440incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
441incoming = accessor _incoming (\conns st -> st { _incoming = conns })
443incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
444incomingAt cid = incoming >>> DAC.mapMaybe cid
446incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
447incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
449 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
451handleIncomingMessages :: LocalNode -> IO ()
452handleIncomingMessages node = go initConnectionState
454 go :: ConnectionState -> IO ()
456 event <- NT.receive endpoint
458 NT.ConnectionOpened cid rel theirAddr ->
459 if rel == NT.ReliableOrdered
461 trace node (MxConnected cid theirAddr)
463 (incomingAt cid ^= Just (theirAddr, Uninit))
464 . (incomingFrom theirAddr ^: Set.insert cid)
467 else invalidRequest cid st
468 NT.Received cid payload ->
469 case st ^. incomingAt cid of
470 Just (_, ToProc pid weakQueue) -> do
471 mQueue <- deRefWeak weakQueue
472 forM_ mQueue $ \queue -> do
473 -- TODO: if we find that the queue is Nothing, should we remove
474 -- it from the NC state? (and same for channels, below)
475 let msg = payloadToMessage payload
476 enqueue queue msg -- 'enqueue' is strict
477 trace node (MxReceived pid msg)
479 Just (_, ToChan (TypedChannel chan')) -> do
480 mChan <- deRefWeak chan'
481 -- If mChan is Nothing, the process has given up the read end of
482 -- the channel and we simply ignore the incoming message
483 forM_ mChan $ \chan -> atomically $
484 -- We make sure the message is fully decoded when it is enqueued
485 writeTQueue chan $! decode (BSL.fromChunks payload)
487 Just (_, ToNode) -> do
488 let ctrlMsg = decode . BSL.fromChunks $ payload
489 writeChan ctrlChan $! ctrlMsg
491 Just (src, Uninit) ->
492 case decode (BSL.fromChunks payload) of
493 ProcessIdentifier pid -> do
494 let lpid = processLocalId pid
495 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
498 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
500 invalidRequest cid st
501 SendPortIdentifier chId -> do
502 let lcid = sendPortLocalId chId
503 lpid = processLocalId (sendPortProcessId chId)
504 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
507 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
510 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
512 invalidRequest cid st
514 invalidRequest cid st
515 NodeIdentifier nid ->
516 if nid == localNodeId node
517 then go (incomingAt cid ^= Just (src, ToNode) $ st)
518 else invalidRequest cid st
520 invalidRequest cid st
521 NT.ConnectionClosed cid ->
522 case st ^. incomingAt cid of
524 invalidRequest cid st
526 trace node (MxDisconnected cid src)
527 go ( (incomingAt cid ^= Nothing)
528 . (incomingFrom src ^: Set.delete cid)
531 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
532 -- [Unified table 9, rule node_disconnect]
533 let nid = NodeIdentifier $ NodeId theirAddr
534 writeChan ctrlChan NCMsg
535 { ctrlMsgSender = nid
536 , ctrlMsgSignal = Died nid DiedDisconnect
538 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
539 closeImplicitReconnections node nid
540 go ( (incomingFrom theirAddr ^= Set.empty)
541 . (incoming ^: Map.filterWithKey (const . notLost))
544 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
545 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
546 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
547 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
550 NT.ReceivedMulticast _ _ ->
551 -- If we received a multicast message, something went horribly wrong
552 -- and we just give up
553 fail "Cloud Haskell fatal error: received unexpected multicast"
555 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
556 invalidRequest cid st = do
557 -- TODO: We should treat this as a fatal error on the part of the remote
558 -- node. That is, we should report the remote node as having died, and we
559 -- should close incoming connections (this requires a Transport layer
561 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
563 go ( incomingAt cid ^= Nothing
567 state = localState node
568 endpoint = localEndPoint node
569 ctrlChan = localCtrlChan node
571--------------------------------------------------------------------------------
572-- Top-level access to the node controller --
573--------------------------------------------------------------------------------
575runNodeController :: LocalNode -> IO ()
577 runReaderT (evalStateT (unNC nodeController) initNCState)
579--------------------------------------------------------------------------------
580-- Internal data types --
581--------------------------------------------------------------------------------
583data NCState = NCState
584 { -- Mapping from remote processes to linked local processes
585 _links :: !(Map Identifier (Set ProcessId))
586 -- Mapping from remote processes to monitoring local processes
587 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
588 -- Process registry: names and where they live, mapped to the PIDs
589 , _registeredHere :: !(Map String ProcessId)
590 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
593newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
594 deriving ( Applicative
599 , MonadReader LocalNode
602initNCState :: NCState
603initNCState = NCState { _links = Map.empty
604 , _monitors = Map.empty
605 , _registeredHere = Map.empty
606 , _registeredOnNodes = Map.empty
609-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
610-- type is deliberately not exported so it cannot be caught explicitly.
611data ProcessKillException =
612 ProcessKillException !ProcessId !String
615instance Exception ProcessKillException
616instance Show ProcessKillException where
617 show (ProcessKillException pid reason) =
618 "killed-by=" ++ show pid ++ ",reason=" ++ reason
620--------------------------------------------------------------------------------
621-- Tracing/Debugging --
622--------------------------------------------------------------------------------
624-- [Issue #104 / DP-13]
626traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
627traceNotifyDied node ident reason =
628 -- TODO: sendPortDied notifications
629 liftIO $ withLocalTracer node $ \t ->
631 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
632 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
635traceEventFmtIO :: LocalNode
639traceEventFmtIO node fmt args =
640 withLocalTracer node $ \t -> traceLogFmt t fmt args
642trace :: LocalNode -> MxEvent -> IO ()
643trace node ev = withLocalTracer node $ \t -> traceEvent t ev
645withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
646withLocalTracer node act = act (localEventBus node)
648--------------------------------------------------------------------------------
649-- Core functionality --
650--------------------------------------------------------------------------------
653nodeController :: NC ()
657 msg <- liftIO $ readChan (localCtrlChan node)
659 -- [Unified: Table 7, rule nc_forward]
660 case destNid (ctrlMsgSignal msg) of
661 Just nid' | nid' /= localNodeId node ->
662 liftIO $ sendBinary node
664 (NodeIdentifier nid')
665 WithImplicitReconnect
671 NCMsg (ProcessIdentifier from) (Link them) ->
672 ncEffectMonitor from them Nothing
673 NCMsg (ProcessIdentifier from) (Monitor ref) ->
674 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
675 NCMsg (ProcessIdentifier from) (Unlink them) ->
676 ncEffectUnlink from them
677 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
678 ncEffectUnmonitor from ref
679 NCMsg _from (Died ident reason) ->
680 ncEffectDied ident reason
681 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
682 ncEffectSpawn from proc ref
683 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
684 ncEffectRegister from label atnode pid force
685 NCMsg (ProcessIdentifier from) (WhereIs label) ->
686 ncEffectWhereIs from label
687 NCMsg _ (NamedSend label msg') ->
688 ncEffectNamedSend label msg'
689 NCMsg _ (LocalSend to msg') ->
690 ncEffectLocalSend node to msg'
691 NCMsg _ (LocalPortSend to msg') ->
692 ncEffectLocalPortSend to msg'
693 NCMsg (ProcessIdentifier from) (Kill to reason) ->
694 ncEffectKill from to reason
695 NCMsg (ProcessIdentifier from) (Exit to reason) ->
696 ncEffectExit from to reason
697 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
698 ncEffectGetInfo from pid
699 NCMsg _ SigShutdown ->
701 NT.closeEndPoint (localEndPoint node)
702 `Exception.finally` throwIO ThreadKilled
703 -- ThreadKilled seems to make more sense than fail/error here
704 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
705 ncEffectGetNodeStats from nid
707 error $ "nodeController: unexpected message " ++ show unexpected
709-- [Unified: Table 10]
710ncEffectMonitor :: ProcessId -- ^ Who's watching?
711 -> Identifier -- ^ Who's being watched?
712 -> Maybe MonitorRef -- ^ 'Nothing' to link
714ncEffectMonitor from them mRef = do
717 if not (isLocal node them)
719 else isValidLocalIdentifier them
720 case (shouldLink, isLocal node (ProcessIdentifier from)) of
721 (True, _) -> -- [Unified: first rule]
723 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
724 Nothing -> modify' $ linksFor them ^: Set.insert from
725 (False, True) -> -- [Unified: second rule]
726 notifyDied from them DiedUnknownId mRef
727 (False, False) -> -- [Unified: third rule]
728 -- TODO: this is the right sender according to the Unified semantics,
729 -- but perhaps having 'them' as the sender would make more sense
730 -- (see also: notifyDied)
731 liftIO $ sendBinary node
732 (NodeIdentifier $ localNodeId node)
733 (NodeIdentifier $ processNodeId from)
734 WithImplicitReconnect
736 { ctrlMsgSender = NodeIdentifier (localNodeId node)
737 , ctrlMsgSignal = Died them DiedUnknownId
740-- [Unified: Table 11]
741ncEffectUnlink :: ProcessId -> Identifier -> NC ()
742ncEffectUnlink from them = do
744 when (isLocal node (ProcessIdentifier from)) $
746 ProcessIdentifier pid ->
747 postAsMessage from $ DidUnlinkProcess pid
748 NodeIdentifier nid ->
749 postAsMessage from $ DidUnlinkNode nid
750 SendPortIdentifier cid ->
751 postAsMessage from $ DidUnlinkPort cid
752 modify' $ linksFor them ^: Set.delete from
754-- [Unified: Table 11]
755ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
756ncEffectUnmonitor from ref = do
758 when (isLocal node (ProcessIdentifier from)) $
759 postAsMessage from $ DidUnmonitor ref
760 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
762-- [Unified: Table 12]
763ncEffectDied :: Identifier -> DiedReason -> NC ()
764ncEffectDied ident reason = do
766 traceNotifyDied node ident reason
767 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
768 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
770-- _registry :: !(Map (String,NodeId) ProcessId)
772 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
774 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
776 when (localOnly <= isLocal node (ProcessIdentifier us)) $
777 notifyDied us them reason Nothing
779 forM_ (Map.toList affectedMons) $ \(them, refs) ->
780 forM_ refs $ \(us, ref) ->
781 when (localOnly <= isLocal node (ProcessIdentifier us)) $
782 notifyDied us them reason (Just ref)
784 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
786 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
788 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
789 mapM (\(pid,nidlist) ->
790 case ident `impliesDeathOf` ProcessIdentifier pid of
792 do forM_ nidlist $ \(nid,_) ->
793 when (not $ isLocal node (NodeIdentifier nid))
794 (forwardNameDeath node nid)
796 False -> return $ Just (pid,nidlist) )
797 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
799 forwardNameDeath node nid =
800 liftIO $ sendBinary node
801 (NodeIdentifier $ localNodeId node)
802 (NodeIdentifier $ nid)
803 WithImplicitReconnect
805 { ctrlMsgSender = NodeIdentifier (localNodeId node)
806 , ctrlMsgSignal = Died ident reason
809-- [Unified: Table 13]
810ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
811ncEffectSpawn pid cProc ref = do
812 mProc <- unClosure cProc
813 -- If the closure does not exist, we spawn a process that throws an exception
814 -- This allows the remote node to find out what's happening
816 let proc = case mProc of
817 Left err -> fail $ "Error: Could not resolve closure: " ++ err
820 pid' <- liftIO $ forkProcess node proc
821 liftIO $ sendMessage node
822 (NodeIdentifier (localNodeId node))
823 (ProcessIdentifier pid)
824 WithImplicitReconnect
827-- Unified semantics does not explicitly describe how to implement 'register',
828-- but mentions it's "very similar to nsend" (Table 14)
829-- We send a response indicated if the operation is invalid
830ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
831ncEffectRegister from label atnode mPid reregistration = do
833 currentVal <- gets (^. registeredHereFor label)
836 Nothing -> -- unregister request
837 return $ isJust currentVal
838 Just thepid -> -- register request
839 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
840 return $ (isNothing currentVal /= reregistration) &&
841 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
842 if isLocal node (NodeIdentifier atnode)
843 then do when (isOk) $
844 do modify' $ registeredHereFor label ^= mPid
845 updateRemote node currentVal mPid
847 (Just p) -> liftIO $ trace node (MxRegistered p label)
848 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
849 liftIO $ sendMessage node
850 (NodeIdentifier (localNodeId node))
851 (ProcessIdentifier from)
852 WithImplicitReconnect
853 (RegisterReply label isOk)
855 case reregistration of
857 False -> flip incList
860 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
861 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
862 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
863 forward node (processNodeId newval) (Register label atnode (Just newval) False)
864 updateRemote node Nothing (Just newval) =
865 forward node (processNodeId newval) (Register label atnode (Just newval) False)
866 updateRemote node (Just oldval) Nothing =
867 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
868 updateRemote _ _ _ = return ()
869 maybeify f Nothing = unmaybeify $ f []
870 maybeify f (Just x) = unmaybeify $ f x
872 unmaybeify [] = Nothing
873 unmaybeify x = Just x
874 incList [] tag = [(tag,1)]
875 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
876 incList (x:xs) tag = x : incList xs tag
878 decList ((atag,1):xs) tag | atag == tag = xs
879 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
880 decList (x:xs) tag = x:decList xs tag
881 forward node to reg =
882 when (not $ isLocal node (NodeIdentifier to)) $
883 liftIO $ sendBinary node
884 (ProcessIdentifier from)
886 WithImplicitReconnect
888 { ctrlMsgSender = ProcessIdentifier from
889 , ctrlMsgSignal = reg
893-- Unified semantics does not explicitly describe 'whereis'
894ncEffectWhereIs :: ProcessId -> String -> NC ()
895ncEffectWhereIs from label = do
897 mPid <- gets (^. registeredHereFor label)
898 liftIO $ sendMessage node
899 (NodeIdentifier (localNodeId node))
900 (ProcessIdentifier from)
901 WithImplicitReconnect
902 (WhereIsReply label mPid)
904-- [Unified: Table 14]
905ncEffectNamedSend :: String -> Message -> NC ()
906ncEffectNamedSend label msg = do
907 mPid <- gets (^. registeredHereFor label)
908 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
909 forM_ mPid $ \pid -> postMessage pid msg
912ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
913ncEffectLocalSend node to msg =
914 liftIO $ withLocalProc node to $ \p -> do
915 enqueue (processQueue p) msg
916 trace node (MxReceived to msg)
919ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
920ncEffectLocalPortSend from msg = do
922 let pid = sendPortProcessId from
923 cid = sendPortLocalId from
924 liftIO $ withLocalProc node pid $ \proc -> do
925 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
927 -- in the unlikely event we know nothing about this channel id,
928 -- there's little to be done - perhaps some logging/tracing though...
930 Just (TypedChannel chan') -> do
931 -- If ch is Nothing, the process has given up the read end of
932 -- the channel and we simply ignore the incoming message - this
933 ch <- deRefWeak chan'
934 forM_ ch $ \chan -> deliverChan msg chan
935 where deliverChan :: forall a . Message -> TQueue a -> IO ()
936 deliverChan (UnencodedMessage _ raw) chan' =
937 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
938 deliverChan (EncodedMessage _ _) _ =
939 -- this will not happen unless someone screws with Primitives.hs
940 error "invalid local channel delivery"
943ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
944ncEffectKill from to reason = do
946 when (isLocal node (ProcessIdentifier to)) $
947 throwException to $ ProcessKillException from reason
950ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
951ncEffectExit from to reason = do
953 when (isLocal node (ProcessIdentifier to)) $
954 throwException to $ ProcessExitException from reason
957ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
958ncEffectGetInfo from pid =
959 let lpid = processLocalId pid
960 them = (ProcessIdentifier pid)
964 withMVar (localState node) $ return . (^. localProcessWithId lpid)
966 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
967 from node (ProcessInfoNone DiedUnknownId)
969 itsLinks <- gets (^. linksFor them)
970 itsMons <- gets (^. monitorsFor them)
971 registered <- gets (^. registeredHere)
973 let reg = registeredNames registered
974 dispatch (isLocal node (ProcessIdentifier from))
978 infoNode = (processNodeId pid)
979 , infoRegisteredNames = reg
980 -- we cannot populate this field
981 , infoMessageQueueLength = Nothing
982 , infoMonitors = Set.toList itsMons
983 , infoLinks = Set.toList itsLinks
985 where dispatch :: (Serializable a, Show a)
991 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
992 dispatch False dest node pInfo = do
993 liftIO $ sendMessage node
994 (NodeIdentifier (localNodeId node))
995 (ProcessIdentifier dest)
996 WithImplicitReconnect
999 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1003ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1004ncEffectGetNodeStats from _nid = do
1006 ncState <- StateT.get
1007 nodeState <- liftIO $ readMVar (localState node)
1008 let localProcesses' = nodeState ^. localProcesses
1011 nodeStatsNode = localNodeId node
1012 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1013 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1014 , nodeStatsLinks = Map.size $ ncState ^. links
1015 , nodeStatsProcesses = Map.size localProcesses'
1017 postAsMessage from stats
1018--------------------------------------------------------------------------------
1020--------------------------------------------------------------------------------
1022notifyDied :: ProcessId -- ^ Who to notify?
1023 -> Identifier -- ^ Who died?
1024 -> DiedReason -- ^ How did they die?
1025 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1027notifyDied dest src reason mRef = do
1029 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1030 (True, Just ref, ProcessIdentifier pid) ->
1031 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1032 (True, Just ref, NodeIdentifier nid) ->
1033 postAsMessage dest $ NodeMonitorNotification ref nid reason
1034 (True, Just ref, SendPortIdentifier cid) ->
1035 postAsMessage dest $ PortMonitorNotification ref cid reason
1036 (True, Nothing, ProcessIdentifier pid) ->
1037 throwException dest $ ProcessLinkException pid reason
1038 (True, Nothing, NodeIdentifier pid) ->
1039 throwException dest $ NodeLinkException pid reason
1040 (True, Nothing, SendPortIdentifier pid) ->
1041 throwException dest $ PortLinkException pid reason
1043 -- The change in sender comes from [Unified: Table 10]
1044 liftIO $ sendBinary node
1045 (NodeIdentifier $ localNodeId node)
1046 (NodeIdentifier $ processNodeId dest)
1047 WithImplicitReconnect
1049 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1050 , ctrlMsgSignal = Died src reason
1053-- | [Unified: Table 8]
1054destNid :: ProcessSignal -> Maybe NodeId
1055destNid (Link ident) = Just $ nodeOf ident
1056destNid (Unlink ident) = Just $ nodeOf ident
1057destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1058destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1059destNid (Spawn _ _) = Nothing
1060destNid (Register _ _ _ _) = Nothing
1061destNid (WhereIs _) = Nothing
1062destNid (NamedSend _ _) = Nothing
1063-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1064-- then when a local process dies the monitoring/linking machinery will take
1065-- care of notifying remote nodes
1066destNid (Died _ _) = Nothing
1067destNid (Kill pid _) = Just $ processNodeId pid
1068destNid (Exit pid _) = Just $ processNodeId pid
1069destNid (GetInfo pid) = Just $ processNodeId pid
1070destNid (GetNodeStats nid) = Just nid
1071destNid (LocalSend pid _) = Just $ processNodeId pid
1072destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1073destNid (SigShutdown) = Nothing
1075-- | Check if a process is local to our own node
1076isLocal :: LocalNode -> Identifier -> Bool
1077isLocal nid ident = nodeOf ident == localNodeId nid
1079-- | Lookup a local closure
1080unClosure :: Typeable a => Closure a -> NC (Either String a)
1081unClosure closure = do
1082 rtable <- remoteTable <$> ask
1083 return (Static.unclosure rtable closure)
1085-- | Check if an identifier refers to a valid local object
1086isValidLocalIdentifier :: Identifier -> NC Bool
1087isValidLocalIdentifier ident = do
1089 liftIO . withMVar (localState node) $ \nSt ->
1091 NodeIdentifier nid ->
1092 return $ nid == localNodeId node
1093 ProcessIdentifier pid -> do
1094 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1095 return $ isJust mProc
1096 SendPortIdentifier cid -> do
1097 let pid = sendPortProcessId cid
1098 mProc = nSt ^. localProcessWithId (processLocalId pid)
1100 Nothing -> return False
1101 Just proc -> withMVar (processState proc) $ \pSt -> do
1102 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1105--------------------------------------------------------------------------------
1106-- Messages to local processes --
1107--------------------------------------------------------------------------------
1109postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1110postAsMessage pid = postMessage pid . createUnencodedMessage
1112postMessage :: ProcessId -> Message -> NC ()
1113postMessage pid msg = do
1115 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1117throwException :: Exception e => ProcessId -> e -> NC ()
1118throwException pid e = do
1120 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1122withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1123withLocalProc node pid p =
1124 -- By [Unified: table 6, rule missing_process] messages to dead processes
1125 -- can silently be dropped
1126 let lpid = processLocalId pid in do
1127 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1130--------------------------------------------------------------------------------
1132--------------------------------------------------------------------------------
1134links :: Accessor NCState (Map Identifier (Set ProcessId))
1135links = accessor _links (\ls st -> st { _links = ls })
1137monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1138monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1140registeredHere :: Accessor NCState (Map String ProcessId)
1141registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1143registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1144registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1146linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1147linksFor ident = links >>> DAC.mapDefault Set.empty ident
1149monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1150monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1152registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1153registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1155registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1156registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1158-- | @splitNotif ident@ splits a notifications map into those
1159-- notifications that should trigger when 'ident' fails and those links that
1162-- There is a hierarchy between identifiers: failure of a node implies failure
1163-- of all processes on that node, and failure of a process implies failure of
1164-- all typed channels to that process. In other words, if 'ident' refers to a
1165-- node, then the /should trigger/ set will include
1167-- * the notifications for the node specifically
1168-- * the notifications for processes on that node, and
1169-- * the notifications for typed channels to processes on that node.
1171-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1174-- * the notifications for that process specifically and
1175-- * the notifications for typed channels to that process.
1177-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1178splitNotif :: Identifier
1180 -> (Map Identifier a, Map Identifier a)
1181splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1183--------------------------------------------------------------------------------
1185--------------------------------------------------------------------------------
1187-- | Modify and evaluate the state
1188modify' :: MonadState s m => (s -> s) -> m ()
1189modify' 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