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 = modifyMVar (localState node) startProcess
337 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
339 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
340 , lpidUnique = st ^. localPidUnique
342 let pid = ProcessId { processNodeId = localNodeId node
343 , processLocalId = lpid
345 pst <- newMVar LocalProcessState { _monitorCounter = 0
347 , _channelCounter = 0
348 , _typedChannels = Map.empty
351 weakQueue <- mkWeakCQueue queue (return ())
352 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
353 let lproc = LocalProcess { processQueue = queue
354 , processWeakQ = weakQueue
357 , processThread = tid
360 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
361 reason <- Exception.catches
362 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
363 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
364 mMsg <- unwrapMessage msg :: IO (Maybe String)
366 Nothing -> return $ DiedException $ show ex
367 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
369 (return . DiedException . (show :: SomeException -> String)))]
371 -- [Unified: Table 4, rules termination and exiting]
372 modifyMVar_ (localState node) (cleanupProcess pid)
373 writeChan (localCtrlChan node) NCMsg
374 { ctrlMsgSender = ProcessIdentifier pid
375 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
379 -- see note [tracer/forkProcess races]
380 trace node (MxSpawned pid)
382 if lpidCounter lpid == maxBound
384 -- TODO: this doesn't look right at all - how do we know
385 -- that newUnique represents a process id that is available!?
386 newUnique <- randomIO
387 return ( (localProcessWithId lpid ^= Just lproc)
388 . (localPidCounter ^= firstNonReservedProcessId)
389 . (localPidUnique ^= newUnique)
394 return ( (localProcessWithId lpid ^= Just lproc)
395 . (localPidCounter ^: (+ 1))
400 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
401 cleanupProcess pid st = do
402 let pid' = ProcessIdentifier pid
403 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
404 mapM_ (NT.close . fst) (Map.elems affected)
405 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
406 . (localConnections ^= unaffected)
409-- note [tracer/forkProcess races]
411-- Our startTracing function uses forkProcess to start the trace controller
412-- process, and of course forkProcess attempts to call traceEvent once the
413-- process has started. This is harmless, as the localEventBus is not updated
414-- until /after/ the initial forkProcess completes, so the first call to
415-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
418--------------------------------------------------------------------------------
419-- Handle incoming messages --
420--------------------------------------------------------------------------------
422type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
426 | ToProc ProcessId (Weak (CQueue Message))
427 | ToChan TypedChannel
430data ConnectionState = ConnectionState {
431 _incoming :: !(Map NT.ConnectionId IncomingConnection)
432 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
435initConnectionState :: ConnectionState
436initConnectionState = ConnectionState {
437 _incoming = Map.empty
438 , _incomingFrom = Map.empty
441incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
442incoming = accessor _incoming (\conns st -> st { _incoming = conns })
444incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
445incomingAt cid = incoming >>> DAC.mapMaybe cid
447incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
448incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
450 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
452handleIncomingMessages :: LocalNode -> IO ()
453handleIncomingMessages node = go initConnectionState
455 go :: ConnectionState -> IO ()
457 event <- NT.receive endpoint
459 NT.ConnectionOpened cid rel theirAddr ->
460 if rel == NT.ReliableOrdered
462 trace node (MxConnected cid theirAddr)
464 (incomingAt cid ^= Just (theirAddr, Uninit))
465 . (incomingFrom theirAddr ^: Set.insert cid)
468 else invalidRequest cid st
469 NT.Received cid payload ->
470 case st ^. incomingAt cid of
471 Just (_, ToProc pid weakQueue) -> do
472 mQueue <- deRefWeak weakQueue
473 forM_ mQueue $ \queue -> do
474 -- TODO: if we find that the queue is Nothing, should we remove
475 -- it from the NC state? (and same for channels, below)
476 let msg = payloadToMessage payload
477 enqueue queue msg -- 'enqueue' is strict
478 trace node (MxReceived pid msg)
480 Just (_, ToChan (TypedChannel chan')) -> do
481 mChan <- deRefWeak chan'
482 -- If mChan is Nothing, the process has given up the read end of
483 -- the channel and we simply ignore the incoming message
484 forM_ mChan $ \chan -> atomically $
485 -- We make sure the message is fully decoded when it is enqueued
486 writeTQueue chan $! decode (BSL.fromChunks payload)
488 Just (_, ToNode) -> do
489 let ctrlMsg = decode . BSL.fromChunks $ payload
490 writeChan ctrlChan $! ctrlMsg
492 Just (src, Uninit) ->
493 case decode (BSL.fromChunks payload) of
494 ProcessIdentifier pid -> do
495 let lpid = processLocalId pid
496 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
499 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
501 invalidRequest cid st
502 SendPortIdentifier chId -> do
503 let lcid = sendPortLocalId chId
504 lpid = processLocalId (sendPortProcessId chId)
505 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
508 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
511 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
513 invalidRequest cid st
515 invalidRequest cid st
516 NodeIdentifier nid ->
517 if nid == localNodeId node
518 then go (incomingAt cid ^= Just (src, ToNode) $ st)
519 else invalidRequest cid st
521 invalidRequest cid st
522 NT.ConnectionClosed cid ->
523 case st ^. incomingAt cid of
525 invalidRequest cid st
527 trace node (MxDisconnected cid src)
528 go ( (incomingAt cid ^= Nothing)
529 . (incomingFrom src ^: Set.delete cid)
532 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
533 -- [Unified table 9, rule node_disconnect]
534 let nid = NodeIdentifier $ NodeId theirAddr
535 writeChan ctrlChan NCMsg
536 { ctrlMsgSender = nid
537 , ctrlMsgSignal = Died nid DiedDisconnect
539 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
540 closeImplicitReconnections node nid
541 go ( (incomingFrom theirAddr ^= Set.empty)
542 . (incoming ^: Map.filterWithKey (const . notLost))
545 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
546 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
547 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
548 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
551 NT.ReceivedMulticast _ _ ->
552 -- If we received a multicast message, something went horribly wrong
553 -- and we just give up
554 fail "Cloud Haskell fatal error: received unexpected multicast"
556 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
557 invalidRequest cid st = do
558 -- TODO: We should treat this as a fatal error on the part of the remote
559 -- node. That is, we should report the remote node as having died, and we
560 -- should close incoming connections (this requires a Transport layer
562 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
564 go ( incomingAt cid ^= Nothing
568 state = localState node
569 endpoint = localEndPoint node
570 ctrlChan = localCtrlChan node
572--------------------------------------------------------------------------------
573-- Top-level access to the node controller --
574--------------------------------------------------------------------------------
576runNodeController :: LocalNode -> IO ()
578 runReaderT (evalStateT (unNC nodeController) initNCState)
580--------------------------------------------------------------------------------
581-- Internal data types --
582--------------------------------------------------------------------------------
584data NCState = NCState
585 { -- Mapping from remote processes to linked local processes
586 _links :: !(Map Identifier (Set ProcessId))
587 -- Mapping from remote processes to monitoring local processes
588 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
589 -- Process registry: names and where they live, mapped to the PIDs
590 , _registeredHere :: !(Map String ProcessId)
591 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
594newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
595 deriving ( Applicative
600 , MonadReader LocalNode
603initNCState :: NCState
604initNCState = NCState { _links = Map.empty
605 , _monitors = Map.empty
606 , _registeredHere = Map.empty
607 , _registeredOnNodes = Map.empty
610-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
611-- type is deliberately not exported so it cannot be caught explicitly.
612data ProcessKillException =
613 ProcessKillException !ProcessId !String
616instance Exception ProcessKillException
617instance Show ProcessKillException where
618 show (ProcessKillException pid reason) =
619 "killed-by=" ++ show pid ++ ",reason=" ++ reason
621--------------------------------------------------------------------------------
622-- Tracing/Debugging --
623--------------------------------------------------------------------------------
625-- [Issue #104 / DP-13]
627traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
628traceNotifyDied node ident reason =
629 -- TODO: sendPortDied notifications
630 liftIO $ withLocalTracer node $ \t ->
632 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
633 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
636traceEventFmtIO :: LocalNode
640traceEventFmtIO node fmt args =
641 withLocalTracer node $ \t -> traceLogFmt t fmt args
643trace :: LocalNode -> MxEvent -> IO ()
644trace node ev = withLocalTracer node $ \t -> traceEvent t ev
646withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
647withLocalTracer node act = act (localEventBus node)
649--------------------------------------------------------------------------------
650-- Core functionality --
651--------------------------------------------------------------------------------
654nodeController :: NC ()
658 msg <- liftIO $ readChan (localCtrlChan node)
660 -- [Unified: Table 7, rule nc_forward]
661 case destNid (ctrlMsgSignal msg) of
662 Just nid' | nid' /= localNodeId node ->
663 liftIO $ sendBinary node
665 (NodeIdentifier nid')
666 WithImplicitReconnect
672 NCMsg (ProcessIdentifier from) (Link them) ->
673 ncEffectMonitor from them Nothing
674 NCMsg (ProcessIdentifier from) (Monitor ref) ->
675 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
676 NCMsg (ProcessIdentifier from) (Unlink them) ->
677 ncEffectUnlink from them
678 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
679 ncEffectUnmonitor from ref
680 NCMsg _from (Died ident reason) ->
681 ncEffectDied ident reason
682 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
683 ncEffectSpawn from proc ref
684 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
685 ncEffectRegister from label atnode pid force
686 NCMsg (ProcessIdentifier from) (WhereIs label) ->
687 ncEffectWhereIs from label
688 NCMsg _ (NamedSend label msg') ->
689 ncEffectNamedSend label msg'
690 NCMsg _ (LocalSend to msg') ->
691 ncEffectLocalSend node to msg'
692 NCMsg _ (LocalPortSend to msg') ->
693 ncEffectLocalPortSend to msg'
694 NCMsg (ProcessIdentifier from) (Kill to reason) ->
695 ncEffectKill from to reason
696 NCMsg (ProcessIdentifier from) (Exit to reason) ->
697 ncEffectExit from to reason
698 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
699 ncEffectGetInfo from pid
700 NCMsg _ SigShutdown ->
702 NT.closeEndPoint (localEndPoint node)
703 `Exception.finally` throwIO ThreadKilled
704 -- ThreadKilled seems to make more sense than fail/error here
705 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
706 ncEffectGetNodeStats from nid
708 error $ "nodeController: unexpected message " ++ show unexpected
710-- [Unified: Table 10]
711ncEffectMonitor :: ProcessId -- ^ Who's watching?
712 -> Identifier -- ^ Who's being watched?
713 -> Maybe MonitorRef -- ^ 'Nothing' to link
715ncEffectMonitor from them mRef = do
718 if not (isLocal node them)
720 else isValidLocalIdentifier them
721 case (shouldLink, isLocal node (ProcessIdentifier from)) of
722 (True, _) -> -- [Unified: first rule]
724 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
725 Nothing -> modify' $ linksFor them ^: Set.insert from
726 (False, True) -> -- [Unified: second rule]
727 notifyDied from them DiedUnknownId mRef
728 (False, False) -> -- [Unified: third rule]
729 -- TODO: this is the right sender according to the Unified semantics,
730 -- but perhaps having 'them' as the sender would make more sense
731 -- (see also: notifyDied)
732 liftIO $ sendBinary node
733 (NodeIdentifier $ localNodeId node)
734 (NodeIdentifier $ processNodeId from)
735 WithImplicitReconnect
737 { ctrlMsgSender = NodeIdentifier (localNodeId node)
738 , ctrlMsgSignal = Died them DiedUnknownId
741-- [Unified: Table 11]
742ncEffectUnlink :: ProcessId -> Identifier -> NC ()
743ncEffectUnlink from them = do
745 when (isLocal node (ProcessIdentifier from)) $
747 ProcessIdentifier pid ->
748 postAsMessage from $ DidUnlinkProcess pid
749 NodeIdentifier nid ->
750 postAsMessage from $ DidUnlinkNode nid
751 SendPortIdentifier cid ->
752 postAsMessage from $ DidUnlinkPort cid
753 modify' $ linksFor them ^: Set.delete from
755-- [Unified: Table 11]
756ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
757ncEffectUnmonitor from ref = do
759 when (isLocal node (ProcessIdentifier from)) $
760 postAsMessage from $ DidUnmonitor ref
761 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
763-- [Unified: Table 12]
764ncEffectDied :: Identifier -> DiedReason -> NC ()
765ncEffectDied ident reason = do
767 traceNotifyDied node ident reason
768 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
769 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
771-- _registry :: !(Map (String,NodeId) ProcessId)
773 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
775 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
777 when (localOnly <= isLocal node (ProcessIdentifier us)) $
778 notifyDied us them reason Nothing
780 forM_ (Map.toList affectedMons) $ \(them, refs) ->
781 forM_ refs $ \(us, ref) ->
782 when (localOnly <= isLocal node (ProcessIdentifier us)) $
783 notifyDied us them reason (Just ref)
785 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
787 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
789 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
790 mapM (\(pid,nidlist) ->
791 case ident `impliesDeathOf` ProcessIdentifier pid of
793 do forM_ nidlist $ \(nid,_) ->
794 when (not $ isLocal node (NodeIdentifier nid))
795 (forwardNameDeath node nid)
797 False -> return $ Just (pid,nidlist) )
798 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
800 forwardNameDeath node nid =
801 liftIO $ sendBinary node
802 (NodeIdentifier $ localNodeId node)
803 (NodeIdentifier $ nid)
804 WithImplicitReconnect
806 { ctrlMsgSender = NodeIdentifier (localNodeId node)
807 , ctrlMsgSignal = Died ident reason
810-- [Unified: Table 13]
811ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
812ncEffectSpawn pid cProc ref = do
813 mProc <- unClosure cProc
814 -- If the closure does not exist, we spawn a process that throws an exception
815 -- This allows the remote node to find out what's happening
817 let proc = case mProc of
818 Left err -> fail $ "Error: Could not resolve closure: " ++ err
821 pid' <- liftIO $ forkProcess node proc
822 liftIO $ sendMessage node
823 (NodeIdentifier (localNodeId node))
824 (ProcessIdentifier pid)
825 WithImplicitReconnect
828-- Unified semantics does not explicitly describe how to implement 'register',
829-- but mentions it's "very similar to nsend" (Table 14)
830-- We send a response indicated if the operation is invalid
831ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
832ncEffectRegister from label atnode mPid reregistration = do
834 currentVal <- gets (^. registeredHereFor label)
837 Nothing -> -- unregister request
838 return $ isJust currentVal
839 Just thepid -> -- register request
840 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
841 return $ (isNothing currentVal /= reregistration) &&
842 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
843 if isLocal node (NodeIdentifier atnode)
844 then do when (isOk) $
845 do modify' $ registeredHereFor label ^= mPid
846 updateRemote node currentVal mPid
848 (Just p) -> liftIO $ trace node (MxRegistered p label)
849 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
850 liftIO $ sendMessage node
851 (NodeIdentifier (localNodeId node))
852 (ProcessIdentifier from)
853 WithImplicitReconnect
854 (RegisterReply label isOk)
856 case reregistration of
858 False -> flip incList
861 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
862 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
863 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
864 forward node (processNodeId newval) (Register label atnode (Just newval) False)
865 updateRemote node Nothing (Just newval) =
866 forward node (processNodeId newval) (Register label atnode (Just newval) False)
867 updateRemote node (Just oldval) Nothing =
868 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
869 updateRemote _ _ _ = return ()
870 maybeify f Nothing = unmaybeify $ f []
871 maybeify f (Just x) = unmaybeify $ f x
873 unmaybeify [] = Nothing
874 unmaybeify x = Just x
875 incList [] tag = [(tag,1)]
876 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
877 incList (x:xs) tag = x : incList xs tag
879 decList ((atag,1):xs) tag | atag == tag = xs
880 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
881 decList (x:xs) tag = x:decList xs tag
882 forward node to reg =
883 when (not $ isLocal node (NodeIdentifier to)) $
884 liftIO $ sendBinary node
885 (ProcessIdentifier from)
887 WithImplicitReconnect
889 { ctrlMsgSender = ProcessIdentifier from
890 , ctrlMsgSignal = reg
894-- Unified semantics does not explicitly describe 'whereis'
895ncEffectWhereIs :: ProcessId -> String -> NC ()
896ncEffectWhereIs from label = do
898 mPid <- gets (^. registeredHereFor label)
899 liftIO $ sendMessage node
900 (NodeIdentifier (localNodeId node))
901 (ProcessIdentifier from)
902 WithImplicitReconnect
903 (WhereIsReply label mPid)
905-- [Unified: Table 14]
906ncEffectNamedSend :: String -> Message -> NC ()
907ncEffectNamedSend label msg = do
908 mPid <- gets (^. registeredHereFor label)
909 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
910 forM_ mPid $ \pid -> postMessage pid msg
913ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
914ncEffectLocalSend node to msg =
915 liftIO $ withLocalProc node to $ \p -> do
916 enqueue (processQueue p) msg
917 trace node (MxReceived to msg)
920ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
921ncEffectLocalPortSend from msg = do
923 let pid = sendPortProcessId from
924 cid = sendPortLocalId from
925 liftIO $ withLocalProc node pid $ \proc -> do
926 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
928 -- in the unlikely event we know nothing about this channel id,
929 -- there's little to be done - perhaps some logging/tracing though...
931 Just (TypedChannel chan') -> do
932 -- If ch is Nothing, the process has given up the read end of
933 -- the channel and we simply ignore the incoming message - this
934 ch <- deRefWeak chan'
935 forM_ ch $ \chan -> deliverChan msg chan
936 where deliverChan :: forall a . Message -> TQueue a -> IO ()
937 deliverChan (UnencodedMessage _ raw) chan' =
938 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
939 deliverChan (EncodedMessage _ _) _ =
940 -- this will not happen unless someone screws with Primitives.hs
941 error "invalid local channel delivery"
944ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
945ncEffectKill from to reason = do
947 when (isLocal node (ProcessIdentifier to)) $
948 throwException to $ ProcessKillException from reason
951ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
952ncEffectExit from to reason = do
954 when (isLocal node (ProcessIdentifier to)) $
955 throwException to $ ProcessExitException from reason
958ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
959ncEffectGetInfo from pid =
960 let lpid = processLocalId pid
961 them = (ProcessIdentifier pid)
965 withMVar (localState node) $ return . (^. localProcessWithId lpid)
967 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
968 from node (ProcessInfoNone DiedUnknownId)
970 itsLinks <- gets (^. linksFor them)
971 itsMons <- gets (^. monitorsFor them)
972 registered <- gets (^. registeredHere)
974 let reg = registeredNames registered
975 dispatch (isLocal node (ProcessIdentifier from))
979 infoNode = (processNodeId pid)
980 , infoRegisteredNames = reg
981 -- we cannot populate this field
982 , infoMessageQueueLength = Nothing
983 , infoMonitors = Set.toList itsMons
984 , infoLinks = Set.toList itsLinks
986 where dispatch :: (Serializable a, Show a)
992 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
993 dispatch False dest node pInfo = do
994 liftIO $ sendMessage node
995 (NodeIdentifier (localNodeId node))
996 (ProcessIdentifier dest)
997 WithImplicitReconnect
1000 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1004ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1005ncEffectGetNodeStats from _nid = do
1007 ncState <- StateT.get
1008 nodeState <- liftIO $ readMVar (localState node)
1009 let localProcesses' = nodeState ^. localProcesses
1012 nodeStatsNode = localNodeId node
1013 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1014 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1015 , nodeStatsLinks = Map.size $ ncState ^. links
1016 , nodeStatsProcesses = Map.size localProcesses'
1018 postAsMessage from stats
1019--------------------------------------------------------------------------------
1021--------------------------------------------------------------------------------
1023notifyDied :: ProcessId -- ^ Who to notify?
1024 -> Identifier -- ^ Who died?
1025 -> DiedReason -- ^ How did they die?
1026 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1028notifyDied dest src reason mRef = do
1030 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1031 (True, Just ref, ProcessIdentifier pid) ->
1032 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1033 (True, Just ref, NodeIdentifier nid) ->
1034 postAsMessage dest $ NodeMonitorNotification ref nid reason
1035 (True, Just ref, SendPortIdentifier cid) ->
1036 postAsMessage dest $ PortMonitorNotification ref cid reason
1037 (True, Nothing, ProcessIdentifier pid) ->
1038 throwException dest $ ProcessLinkException pid reason
1039 (True, Nothing, NodeIdentifier pid) ->
1040 throwException dest $ NodeLinkException pid reason
1041 (True, Nothing, SendPortIdentifier pid) ->
1042 throwException dest $ PortLinkException pid reason
1044 -- The change in sender comes from [Unified: Table 10]
1045 liftIO $ sendBinary node
1046 (NodeIdentifier $ localNodeId node)
1047 (NodeIdentifier $ processNodeId dest)
1048 WithImplicitReconnect
1050 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1051 , ctrlMsgSignal = Died src reason
1054-- | [Unified: Table 8]
1055destNid :: ProcessSignal -> Maybe NodeId
1056destNid (Link ident) = Just $ nodeOf ident
1057destNid (Unlink ident) = Just $ nodeOf ident
1058destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1059destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1060destNid (Spawn _ _) = Nothing
1061destNid (Register _ _ _ _) = Nothing
1062destNid (WhereIs _) = Nothing
1063destNid (NamedSend _ _) = Nothing
1064-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1065-- then when a local process dies the monitoring/linking machinery will take
1066-- care of notifying remote nodes
1067destNid (Died _ _) = Nothing
1068destNid (Kill pid _) = Just $ processNodeId pid
1069destNid (Exit pid _) = Just $ processNodeId pid
1070destNid (GetInfo pid) = Just $ processNodeId pid
1071destNid (GetNodeStats nid) = Just nid
1072destNid (LocalSend pid _) = Just $ processNodeId pid
1073destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1074destNid (SigShutdown) = Nothing
1076-- | Check if a process is local to our own node
1077isLocal :: LocalNode -> Identifier -> Bool
1078isLocal nid ident = nodeOf ident == localNodeId nid
1080-- | Lookup a local closure
1081unClosure :: Typeable a => Closure a -> NC (Either String a)
1082unClosure closure = do
1083 rtable <- remoteTable <$> ask
1084 return (Static.unclosure rtable closure)
1086-- | Check if an identifier refers to a valid local object
1087isValidLocalIdentifier :: Identifier -> NC Bool
1088isValidLocalIdentifier ident = do
1090 liftIO . withMVar (localState node) $ \nSt ->
1092 NodeIdentifier nid ->
1093 return $ nid == localNodeId node
1094 ProcessIdentifier pid -> do
1095 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1096 return $ isJust mProc
1097 SendPortIdentifier cid -> do
1098 let pid = sendPortProcessId cid
1099 mProc = nSt ^. localProcessWithId (processLocalId pid)
1101 Nothing -> return False
1102 Just proc -> withMVar (processState proc) $ \pSt -> do
1103 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1106--------------------------------------------------------------------------------
1107-- Messages to local processes --
1108--------------------------------------------------------------------------------
1110postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1111postAsMessage pid = postMessage pid . createUnencodedMessage
1113postMessage :: ProcessId -> Message -> NC ()
1114postMessage pid msg = do
1116 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1118throwException :: Exception e => ProcessId -> e -> NC ()
1119throwException pid e = do
1121 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1123withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1124withLocalProc node pid p =
1125 -- By [Unified: table 6, rule missing_process] messages to dead processes
1126 -- can silently be dropped
1127 let lpid = processLocalId pid in do
1128 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1131--------------------------------------------------------------------------------
1133--------------------------------------------------------------------------------
1135links :: Accessor NCState (Map Identifier (Set ProcessId))
1136links = accessor _links (\ls st -> st { _links = ls })
1138monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1139monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1141registeredHere :: Accessor NCState (Map String ProcessId)
1142registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1144registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1145registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1147linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1148linksFor ident = links >>> DAC.mapDefault Set.empty ident
1150monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1151monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1153registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1154registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1156registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1157registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1159-- | @splitNotif ident@ splits a notifications map into those
1160-- notifications that should trigger when 'ident' fails and those links that
1163-- There is a hierarchy between identifiers: failure of a node implies failure
1164-- of all processes on that node, and failure of a process implies failure of
1165-- all typed channels to that process. In other words, if 'ident' refers to a
1166-- node, then the /should trigger/ set will include
1168-- * the notifications for the node specifically
1169-- * the notifications for processes on that node, and
1170-- * the notifications for typed channels to processes on that node.
1172-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1175-- * the notifications for that process specifically and
1176-- * the notifications for typed channels to that process.
1178-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1179splitNotif :: Identifier
1181 -> (Map Identifier a, Map Identifier a)
1182splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1184--------------------------------------------------------------------------------
1186--------------------------------------------------------------------------------
1188-- | Modify and evaluate the state
1189modify' :: MonadState s m => (s -> s) -> m ()
1190modify' 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