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 _ (UnreliableSend lpid msg') ->
692 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
693 NCMsg _ (LocalSend to msg') ->
694 ncEffectLocalSend node to msg'
695 NCMsg _ (LocalPortSend to msg') ->
696 ncEffectLocalPortSend to msg'
697 NCMsg (ProcessIdentifier from) (Kill to reason) ->
698 ncEffectKill from to reason
699 NCMsg (ProcessIdentifier from) (Exit to reason) ->
700 ncEffectExit from to reason
701 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
702 ncEffectGetInfo from pid
703 NCMsg _ SigShutdown ->
705 NT.closeEndPoint (localEndPoint node)
706 `Exception.finally` throwIO ThreadKilled
707 -- ThreadKilled seems to make more sense than fail/error here
708 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
709 ncEffectGetNodeStats from nid
711 error $ "nodeController: unexpected message " ++ show unexpected
713-- [Unified: Table 10]
714ncEffectMonitor :: ProcessId -- ^ Who's watching?
715 -> Identifier -- ^ Who's being watched?
716 -> Maybe MonitorRef -- ^ 'Nothing' to link
718ncEffectMonitor from them mRef = do
721 if not (isLocal node them)
723 else isValidLocalIdentifier them
724 case (shouldLink, isLocal node (ProcessIdentifier from)) of
725 (True, _) -> -- [Unified: first rule]
727 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
728 Nothing -> modify' $ linksFor them ^: Set.insert from
729 (False, True) -> -- [Unified: second rule]
730 notifyDied from them DiedUnknownId mRef
731 (False, False) -> -- [Unified: third rule]
732 -- TODO: this is the right sender according to the Unified semantics,
733 -- but perhaps having 'them' as the sender would make more sense
734 -- (see also: notifyDied)
735 liftIO $ sendBinary node
736 (NodeIdentifier $ localNodeId node)
737 (NodeIdentifier $ processNodeId from)
738 WithImplicitReconnect
740 { ctrlMsgSender = NodeIdentifier (localNodeId node)
741 , ctrlMsgSignal = Died them DiedUnknownId
744-- [Unified: Table 11]
745ncEffectUnlink :: ProcessId -> Identifier -> NC ()
746ncEffectUnlink from them = do
748 when (isLocal node (ProcessIdentifier from)) $
750 ProcessIdentifier pid ->
751 postAsMessage from $ DidUnlinkProcess pid
752 NodeIdentifier nid ->
753 postAsMessage from $ DidUnlinkNode nid
754 SendPortIdentifier cid ->
755 postAsMessage from $ DidUnlinkPort cid
756 modify' $ linksFor them ^: Set.delete from
758-- [Unified: Table 11]
759ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
760ncEffectUnmonitor from ref = do
762 when (isLocal node (ProcessIdentifier from)) $
763 postAsMessage from $ DidUnmonitor ref
764 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
766-- [Unified: Table 12]
767ncEffectDied :: Identifier -> DiedReason -> NC ()
768ncEffectDied ident reason = do
770 traceNotifyDied node ident reason
771 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
772 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
774-- _registry :: !(Map (String,NodeId) ProcessId)
776 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
778 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
780 when (localOnly <= isLocal node (ProcessIdentifier us)) $
781 notifyDied us them reason Nothing
783 forM_ (Map.toList affectedMons) $ \(them, refs) ->
784 forM_ refs $ \(us, ref) ->
785 when (localOnly <= isLocal node (ProcessIdentifier us)) $
786 notifyDied us them reason (Just ref)
788 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
790 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
792 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
793 mapM (\(pid,nidlist) ->
794 case ident `impliesDeathOf` ProcessIdentifier pid of
796 do forM_ nidlist $ \(nid,_) ->
797 when (not $ isLocal node (NodeIdentifier nid))
798 (forwardNameDeath node nid)
800 False -> return $ Just (pid,nidlist) )
801 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
803 forwardNameDeath node nid =
804 liftIO $ sendBinary node
805 (NodeIdentifier $ localNodeId node)
806 (NodeIdentifier $ nid)
807 WithImplicitReconnect
809 { ctrlMsgSender = NodeIdentifier (localNodeId node)
810 , ctrlMsgSignal = Died ident reason
813-- [Unified: Table 13]
814ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
815ncEffectSpawn pid cProc ref = do
816 mProc <- unClosure cProc
817 -- If the closure does not exist, we spawn a process that throws an exception
818 -- This allows the remote node to find out what's happening
820 let proc = case mProc of
821 Left err -> fail $ "Error: Could not resolve closure: " ++ err
824 pid' <- liftIO $ forkProcess node proc
825 liftIO $ sendMessage node
826 (NodeIdentifier (localNodeId node))
827 (ProcessIdentifier pid)
828 WithImplicitReconnect
831-- Unified semantics does not explicitly describe how to implement 'register',
832-- but mentions it's "very similar to nsend" (Table 14)
833-- We send a response indicated if the operation is invalid
834ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
835ncEffectRegister from label atnode mPid reregistration = do
837 currentVal <- gets (^. registeredHereFor label)
840 Nothing -> -- unregister request
841 return $ isJust currentVal
842 Just thepid -> -- register request
843 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
844 return $ (isNothing currentVal /= reregistration) &&
845 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
846 if isLocal node (NodeIdentifier atnode)
847 then do when (isOk) $
848 do modify' $ registeredHereFor label ^= mPid
849 updateRemote node currentVal mPid
851 (Just p) -> liftIO $ trace node (MxRegistered p label)
852 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
853 liftIO $ sendMessage node
854 (NodeIdentifier (localNodeId node))
855 (ProcessIdentifier from)
856 WithImplicitReconnect
857 (RegisterReply label isOk)
859 case reregistration of
861 False -> flip incList
864 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
865 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
866 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
867 forward node (processNodeId newval) (Register label atnode (Just newval) False)
868 updateRemote node Nothing (Just newval) =
869 forward node (processNodeId newval) (Register label atnode (Just newval) False)
870 updateRemote node (Just oldval) Nothing =
871 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
872 updateRemote _ _ _ = return ()
873 maybeify f Nothing = unmaybeify $ f []
874 maybeify f (Just x) = unmaybeify $ f x
876 unmaybeify [] = Nothing
877 unmaybeify x = Just x
878 incList [] tag = [(tag,1)]
879 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
880 incList (x:xs) tag = x : incList xs tag
882 decList ((atag,1):xs) tag | atag == tag = xs
883 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
884 decList (x:xs) tag = x:decList xs tag
885 forward node to reg =
886 when (not $ isLocal node (NodeIdentifier to)) $
887 liftIO $ sendBinary node
888 (ProcessIdentifier from)
890 WithImplicitReconnect
892 { ctrlMsgSender = ProcessIdentifier from
893 , ctrlMsgSignal = reg
897-- Unified semantics does not explicitly describe 'whereis'
898ncEffectWhereIs :: ProcessId -> String -> NC ()
899ncEffectWhereIs from label = do
901 mPid <- gets (^. registeredHereFor label)
902 liftIO $ sendMessage node
903 (NodeIdentifier (localNodeId node))
904 (ProcessIdentifier from)
905 WithImplicitReconnect
906 (WhereIsReply label mPid)
908-- [Unified: Table 14]
909ncEffectNamedSend :: String -> Message -> NC ()
910ncEffectNamedSend label msg = do
911 mPid <- gets (^. registeredHereFor label)
912 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
913 forM_ mPid $ \pid -> postMessage pid msg
916ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
917ncEffectLocalSend node to msg =
918 liftIO $ withLocalProc node to $ \p -> do
919 enqueue (processQueue p) msg
920 trace node (MxReceived to msg)
923ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
924ncEffectLocalPortSend from msg = do
926 let pid = sendPortProcessId from
927 cid = sendPortLocalId from
928 liftIO $ withLocalProc node pid $ \proc -> do
929 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
931 -- in the unlikely event we know nothing about this channel id,
932 -- there's little to be done - perhaps some logging/tracing though...
934 Just (TypedChannel chan') -> do
935 -- If ch is Nothing, the process has given up the read end of
936 -- the channel and we simply ignore the incoming message - this
937 ch <- deRefWeak chan'
938 forM_ ch $ \chan -> deliverChan msg chan
939 where deliverChan :: forall a . Message -> TQueue a -> IO ()
940 deliverChan (UnencodedMessage _ raw) chan' =
941 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
942 deliverChan (EncodedMessage _ _) _ =
943 -- this will not happen unless someone screws with Primitives.hs
944 error "invalid local channel delivery"
947ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
948ncEffectKill from to reason = do
950 when (isLocal node (ProcessIdentifier to)) $
951 throwException to $ ProcessKillException from reason
954ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
955ncEffectExit from to reason = do
957 when (isLocal node (ProcessIdentifier to)) $
958 throwException to $ ProcessExitException from reason
961ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
962ncEffectGetInfo from pid =
963 let lpid = processLocalId pid
964 them = (ProcessIdentifier pid)
968 withMVar (localState node) $ return . (^. localProcessWithId lpid)
970 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
971 from node (ProcessInfoNone DiedUnknownId)
973 itsLinks <- gets (^. linksFor them)
974 itsMons <- gets (^. monitorsFor them)
975 registered <- gets (^. registeredHere)
977 let reg = registeredNames registered
978 dispatch (isLocal node (ProcessIdentifier from))
982 infoNode = (processNodeId pid)
983 , infoRegisteredNames = reg
984 -- we cannot populate this field
985 , infoMessageQueueLength = Nothing
986 , infoMonitors = Set.toList itsMons
987 , infoLinks = Set.toList itsLinks
989 where dispatch :: (Serializable a, Show a)
995 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
996 dispatch False dest node pInfo = do
997 liftIO $ sendMessage node
998 (NodeIdentifier (localNodeId node))
999 (ProcessIdentifier dest)
1000 WithImplicitReconnect
1003 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1007ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1008ncEffectGetNodeStats from _nid = do
1010 ncState <- StateT.get
1011 nodeState <- liftIO $ readMVar (localState node)
1012 let localProcesses' = nodeState ^. localProcesses
1015 nodeStatsNode = localNodeId node
1016 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1017 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1018 , nodeStatsLinks = Map.size $ ncState ^. links
1019 , nodeStatsProcesses = Map.size localProcesses'
1021 postAsMessage from stats
1022--------------------------------------------------------------------------------
1024--------------------------------------------------------------------------------
1026notifyDied :: ProcessId -- ^ Who to notify?
1027 -> Identifier -- ^ Who died?
1028 -> DiedReason -- ^ How did they die?
1029 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1031notifyDied dest src reason mRef = do
1033 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1034 (True, Just ref, ProcessIdentifier pid) ->
1035 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1036 (True, Just ref, NodeIdentifier nid) ->
1037 postAsMessage dest $ NodeMonitorNotification ref nid reason
1038 (True, Just ref, SendPortIdentifier cid) ->
1039 postAsMessage dest $ PortMonitorNotification ref cid reason
1040 (True, Nothing, ProcessIdentifier pid) ->
1041 throwException dest $ ProcessLinkException pid reason
1042 (True, Nothing, NodeIdentifier pid) ->
1043 throwException dest $ NodeLinkException pid reason
1044 (True, Nothing, SendPortIdentifier pid) ->
1045 throwException dest $ PortLinkException pid reason
1047 -- The change in sender comes from [Unified: Table 10]
1048 liftIO $ sendBinary node
1049 (NodeIdentifier $ localNodeId node)
1050 (NodeIdentifier $ processNodeId dest)
1051 WithImplicitReconnect
1053 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1054 , ctrlMsgSignal = Died src reason
1057-- | [Unified: Table 8]
1058destNid :: ProcessSignal -> Maybe NodeId
1059destNid (Link ident) = Just $ nodeOf ident
1060destNid (Unlink ident) = Just $ nodeOf ident
1061destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1062destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1063destNid (Spawn _ _) = Nothing
1064destNid (Register _ _ _ _) = Nothing
1065destNid (WhereIs _) = Nothing
1066destNid (NamedSend _ _) = Nothing
1067destNid (UnreliableSend _ _) = Nothing
1068-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1069-- then when a local process dies the monitoring/linking machinery will take
1070-- care of notifying remote nodes
1071destNid (Died _ _) = Nothing
1072destNid (Kill pid _) = Just $ processNodeId pid
1073destNid (Exit pid _) = Just $ processNodeId pid
1074destNid (GetInfo pid) = Just $ processNodeId pid
1075destNid (GetNodeStats nid) = Just nid
1076destNid (LocalSend pid _) = Just $ processNodeId pid
1077destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1078destNid (SigShutdown) = Nothing
1080-- | Check if a process is local to our own node
1081isLocal :: LocalNode -> Identifier -> Bool
1082isLocal nid ident = nodeOf ident == localNodeId nid
1084-- | Lookup a local closure
1085unClosure :: Typeable a => Closure a -> NC (Either String a)
1086unClosure closure = do
1087 rtable <- remoteTable <$> ask
1088 return (Static.unclosure rtable closure)
1090-- | Check if an identifier refers to a valid local object
1091isValidLocalIdentifier :: Identifier -> NC Bool
1092isValidLocalIdentifier ident = do
1094 liftIO . withMVar (localState node) $ \nSt ->
1096 NodeIdentifier nid ->
1097 return $ nid == localNodeId node
1098 ProcessIdentifier pid -> do
1099 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1100 return $ isJust mProc
1101 SendPortIdentifier cid -> do
1102 let pid = sendPortProcessId cid
1103 mProc = nSt ^. localProcessWithId (processLocalId pid)
1105 Nothing -> return False
1106 Just proc -> withMVar (processState proc) $ \pSt -> do
1107 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1110--------------------------------------------------------------------------------
1111-- Messages to local processes --
1112--------------------------------------------------------------------------------
1114postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1115postAsMessage pid = postMessage pid . createUnencodedMessage
1117postMessage :: ProcessId -> Message -> NC ()
1118postMessage pid msg = do
1120 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1122throwException :: Exception e => ProcessId -> e -> NC ()
1123throwException pid e = do
1125 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1127withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1128withLocalProc node pid p =
1129 -- By [Unified: table 6, rule missing_process] messages to dead processes
1130 -- can silently be dropped
1131 let lpid = processLocalId pid in do
1132 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1135--------------------------------------------------------------------------------
1137--------------------------------------------------------------------------------
1139links :: Accessor NCState (Map Identifier (Set ProcessId))
1140links = accessor _links (\ls st -> st { _links = ls })
1142monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1143monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1145registeredHere :: Accessor NCState (Map String ProcessId)
1146registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1148registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1149registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1151linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1152linksFor ident = links >>> DAC.mapDefault Set.empty ident
1154monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1155monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1157registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1158registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1160registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1161registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1163-- | @splitNotif ident@ splits a notifications map into those
1164-- notifications that should trigger when 'ident' fails and those links that
1167-- There is a hierarchy between identifiers: failure of a node implies failure
1168-- of all processes on that node, and failure of a process implies failure of
1169-- all typed channels to that process. In other words, if 'ident' refers to a
1170-- node, then the /should trigger/ set will include
1172-- * the notifications for the node specifically
1173-- * the notifications for processes on that node, and
1174-- * the notifications for typed channels to processes on that node.
1176-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1179-- * the notifications for that process specifically and
1180-- * the notifications for typed channels to that process.
1182-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1183splitNotif :: Identifier
1185 -> (Map Identifier a, Map Identifier a)
1186splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1188--------------------------------------------------------------------------------
1190--------------------------------------------------------------------------------
1192-- | Modify and evaluate the state
1193modify' :: MonadState s m => (s -> s) -> m ()
1194modify' 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