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, killThread, myThreadId)
71import 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
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
121 , ValidLocalNodeState(..)
122 , withValidLocalState
123 , modifyValidLocalState_
125 , LocalProcessState(..)
137 , ProcessMonitorNotification(..)
138 , NodeMonitorNotification(..)
139 , PortMonitorNotification(..)
140 , ProcessExitException(..)
141 , ProcessLinkException(..)
142 , NodeLinkException(..)
143 , PortLinkException(..)
145 , DidUnlinkProcess(..)
155 , ProcessInfoNone(..)
163 , createUnencodedMessage
165 , firstNonReservedProcessId
166 , ImplicitReconnect(WithImplicitReconnect,NoImplicitReconnect)
168import Control.Distributed.Process.Management.Internal.Agent
171import qualified Control.Distributed.Process.Management.Internal.Table as Table
173 , startTableCoordinator
175import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
178import Control.Distributed.Process.Management.Internal.Trace.Tracer
181import Control.Distributed.Process.Management.Internal.Trace.Types
187import Control.Distributed.Process.Management.Internal.Types
190import Control.Distributed.Process.Serializable (Serializable)
191import Control.Distributed.Process.Internal.Messaging
195 , closeImplicitReconnections
198import Control.Distributed.Process.Internal.Primitives
207import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
208import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
209import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
210import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
216--------------------------------------------------------------------------------
218--------------------------------------------------------------------------------
220initRemoteTable :: RemoteTable
221initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
223-- | Initialize a new local node.
224newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
225newLocalNode transport rtable = do
226 mEndPoint <- NT.newEndPoint transport
228 Left ex -> throwIO ex
230 localNode <- createBareLocalNode endPoint rtable
231 startServiceProcesses localNode
234-- | Create a new local node (without any service processes running)
235createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
236createBareLocalNode endPoint rtable = do
238 state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
239 { _localProcesses = Map.empty
240 , _localPidCounter = firstNonReservedProcessId
241 , _localPidUnique = unq
242 , _localConnections = Map.empty
245 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
246 , localEndPoint = endPoint
248 , localCtrlChan = ctrlChan
249 , localEventBus = MxEventBusInitialising
250 , remoteTable = rtable
252 tracedNode <- startMxAgent node
254 -- Once the NC terminates, the endpoint isn't much use,
255 void $ forkIO $ Exception.finally (runNodeController tracedNode)
256 (NT.closeEndPoint (localEndPoint node))
258 -- whilst a closed/failing endpoint will terminate the NC
259 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
265 writeChan (localCtrlChan node) NCMsg
266 { ctrlMsgSender = NodeIdentifier (localNodeId node)
267 , ctrlMsgSignal = SigShutdown
270startMxAgent :: LocalNode -> IO LocalNode
271startMxAgent node = do
272 -- see note [tracer/forkProcess races]
273 let fork = forkProcess node
274 mv <- MVar.newEmptyMVar
275 pid <- fork $ mxAgentController fork mv
276 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
277 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
279startDefaultTracer :: LocalNode -> IO ()
280startDefaultTracer node' = do
281 let t = localEventBus node'
283 MxEventBus _ (Tracer pid _) _ _ -> do
284 runProcess node' $ register "trace.controller" pid
285 pid' <- forkProcess node' defaultTracer
286 enableTrace (localEventBus node') pid'
287 runProcess node' $ register "tracer.initial" pid'
290-- TODO: we need a better mechanism for defining and registering services
292-- | Start and register the service processes on a node
293startServiceProcesses :: LocalNode -> IO ()
294startServiceProcesses node = do
295 -- tracing /spawns/ relies on the tracer being enabled, but we start
296 -- the default tracer first, even though it might @nsend@ to the logger
297 -- before /that/ process has started - this is a totally harmless race
298 -- however, so we deliberably ignore it
299 startDefaultTracer node
300 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
301 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
302 logger <- forkProcess node loop
304 register "logger" logger
305 -- The trace.logger is used for tracing to the console to avoid feedback
306 -- loops during tracing if the user reregisters the "logger" with a custom
307 -- process which uses 'send' or other primitives which are traced.
308 register "trace.logger" logger
310 fork = forkProcess node
314 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
315 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
317 , match $ \((time, string) :: (String, String)) -> do
318 -- this is a 'trace' message from the local node tracer
319 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
321 , match $ \(ch :: SendPort ()) -> -- a shutdown request
325-- | Force-close a local node, killing all processes on that node.
326closeLocalNode :: LocalNode -> IO ()
327closeLocalNode node = do
328 modifyMVar_ (localState node) $ \st -> case st of
329 LocalNodeValid vst -> do
330 forM_ (vst ^. localProcesses) $ \lproc ->
331 -- Semantics of 'throwTo' guarantee that target thread will get
332 -- delivered an exception. Therefore, target thread will be killed
333 -- eventually and that's as good as we can do. No need to wait for
334 -- thread to actually finish dying.
335 killThread (processThread lproc)
336 return LocalNodeClosed
337 LocalNodeClosed -> return LocalNodeClosed
338 -- This call will have the effect of shutting down the NC as well (see
339 -- 'createBareLocalNode').
340 NT.closeEndPoint (localEndPoint node)
342-- | Run a process on a local node and wait for it to finish
343runProcess :: LocalNode -> Process () -> IO ()
344runProcess node proc = do
347 void $ forkProcess node $ do
348 catch (proc `finally` liftIO (putMVar done ()))
349 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
352-- | Spawn a new process on a local node
353forkProcess :: LocalNode -> Process () -> IO ProcessId
354forkProcess node proc =
355 modifyMVarMasked (localState node) startProcess
357 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
358 startProcess (LocalNodeValid vst) = do
359 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
360 , lpidUnique = vst ^. localPidUnique
362 let pid = ProcessId { processNodeId = localNodeId node
363 , processLocalId = lpid
365 pst <- newMVar LocalProcessState { _monitorCounter = 0
367 , _channelCounter = 0
368 , _typedChannels = Map.empty
371 weakQueue <- mkWeakCQueue queue (return ())
372 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
373 let lproc = LocalProcess { processQueue = queue
374 , processWeakQ = weakQueue
377 , processThread = tid
380 tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
381 reason <- Exception.catches
382 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
383 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
384 mMsg <- unwrapMessage msg :: IO (Maybe String)
386 Nothing -> return $ DiedException $ show ex
387 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
389 (return . DiedException . (show :: SomeException -> String)))]
391 -- [Unified: Table 4, rules termination and exiting]
392 modifyValidLocalState_ node (cleanupProcess pid)
393 writeChan (localCtrlChan node) NCMsg
394 { ctrlMsgSender = ProcessIdentifier pid
395 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
399 -- see note [tracer/forkProcess races]
400 trace node (MxSpawned pid)
402 if lpidCounter lpid == maxBound
404 -- TODO: this doesn't look right at all - how do we know
405 -- that newUnique represents a process id that is available!?
406 newUnique <- randomIO
407 return ( LocalNodeValid
408 $ (localProcessWithId lpid ^= Just lproc)
409 . (localPidCounter ^= firstNonReservedProcessId)
410 . (localPidUnique ^= newUnique)
415 return ( LocalNodeValid
416 $ (localProcessWithId lpid ^= Just lproc)
417 . (localPidCounter ^: (+ 1))
421 startProcess LocalNodeClosed = throwIO $ userError $ "Node closed " ++ show (localNodeId node)
423 cleanupProcess :: ProcessId -> ValidLocalNodeState -> IO ValidLocalNodeState
424 cleanupProcess pid vst = do
425 let pid' = ProcessIdentifier pid
426 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
427 mapM_ (NT.close . fst) (Map.elems affected)
428 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
429 . (localConnections ^= unaffected)
432-- note [tracer/forkProcess races]
434-- Our startTracing function uses forkProcess to start the trace controller
435-- process, and of course forkProcess attempts to call traceEvent once the
436-- process has started. This is harmless, as the localEventBus is not updated
437-- until /after/ the initial forkProcess completes, so the first call to
438-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
441--------------------------------------------------------------------------------
442-- Handle incoming messages --
443--------------------------------------------------------------------------------
445type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
449 | ToProc ProcessId (Weak (CQueue Message))
450 | ToChan TypedChannel
453data ConnectionState = ConnectionState {
454 _incoming :: !(Map NT.ConnectionId IncomingConnection)
455 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
458initConnectionState :: ConnectionState
459initConnectionState = ConnectionState {
460 _incoming = Map.empty
461 , _incomingFrom = Map.empty
464incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
465incoming = accessor _incoming (\conns st -> st { _incoming = conns })
467incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
468incomingAt cid = incoming >>> DAC.mapMaybe cid
470incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
471incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
473 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
475handleIncomingMessages :: LocalNode -> IO ()
476handleIncomingMessages node = go initConnectionState
478 go :: ConnectionState -> IO ()
480 event <- NT.receive endpoint
482 NT.ConnectionOpened cid rel theirAddr ->
483 if rel == NT.ReliableOrdered
485 trace node (MxConnected cid theirAddr)
487 (incomingAt cid ^= Just (theirAddr, Uninit))
488 . (incomingFrom theirAddr ^: Set.insert cid)
491 else invalidRequest cid st
492 NT.Received cid payload ->
493 case st ^. incomingAt cid of
494 Just (_, ToProc pid weakQueue) -> do
495 mQueue <- deRefWeak weakQueue
496 forM_ mQueue $ \queue -> do
497 -- TODO: if we find that the queue is Nothing, should we remove
498 -- it from the NC state? (and same for channels, below)
499 let msg = payloadToMessage payload
500 enqueue queue msg -- 'enqueue' is strict
501 trace node (MxReceived pid msg)
503 Just (_, ToChan (TypedChannel chan')) -> do
504 mChan <- deRefWeak chan'
505 -- If mChan is Nothing, the process has given up the read end of
506 -- the channel and we simply ignore the incoming message
507 forM_ mChan $ \chan -> atomically $
508 -- We make sure the message is fully decoded when it is enqueued
509 writeTQueue chan $! decode (BSL.fromChunks payload)
511 Just (_, ToNode) -> do
512 let ctrlMsg = decode . BSL.fromChunks $ payload
513 writeChan ctrlChan $! ctrlMsg
515 Just (src, Uninit) ->
516 case decode (BSL.fromChunks payload) of
517 ProcessIdentifier pid -> do
518 let lpid = processLocalId pid
519 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
522 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
524 invalidRequest cid st
525 SendPortIdentifier chId -> do
526 let lcid = sendPortLocalId chId
527 lpid = processLocalId (sendPortProcessId chId)
528 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
531 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
534 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
536 invalidRequest cid st
538 invalidRequest cid st
539 NodeIdentifier nid ->
540 if nid == localNodeId node
541 then go (incomingAt cid ^= Just (src, ToNode) $ st)
542 else invalidRequest cid st
544 invalidRequest cid st
545 NT.ConnectionClosed cid ->
546 case st ^. incomingAt cid of
548 invalidRequest cid st
550 trace node (MxDisconnected cid src)
551 go ( (incomingAt cid ^= Nothing)
552 . (incomingFrom src ^: Set.delete cid)
555 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
556 -- [Unified table 9, rule node_disconnect]
557 let nid = NodeIdentifier $ NodeId theirAddr
558 writeChan ctrlChan NCMsg
559 { ctrlMsgSender = nid
560 , ctrlMsgSignal = Died nid DiedDisconnect
562 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
563 closeImplicitReconnections node nid
564 go ( (incomingFrom theirAddr ^= Set.empty)
565 . (incoming ^: Map.filterWithKey (const . notLost))
568 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
569 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
570 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
571 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
574 NT.ReceivedMulticast _ _ ->
575 -- If we received a multicast message, something went horribly wrong
576 -- and we just give up
577 fail "Cloud Haskell fatal error: received unexpected multicast"
579 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
580 invalidRequest cid st = do
581 -- TODO: We should treat this as a fatal error on the part of the remote
582 -- node. That is, we should report the remote node as having died, and we
583 -- should close incoming connections (this requires a Transport layer
585 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
587 go ( incomingAt cid ^= Nothing
591 state = localState node
592 endpoint = localEndPoint node
593 ctrlChan = localCtrlChan node
595--------------------------------------------------------------------------------
596-- Top-level access to the node controller --
597--------------------------------------------------------------------------------
599runNodeController :: LocalNode -> IO ()
601 runReaderT (evalStateT (unNC nodeController) initNCState)
603--------------------------------------------------------------------------------
604-- Internal data types --
605--------------------------------------------------------------------------------
607data NCState = NCState
608 { -- Mapping from remote processes to linked local processes
609 _links :: !(Map Identifier (Set ProcessId))
610 -- Mapping from remote processes to monitoring local processes
611 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
612 -- Process registry: names and where they live, mapped to the PIDs
613 , _registeredHere :: !(Map String ProcessId)
614 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
617newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
618 deriving ( Applicative
623 , MonadReader LocalNode
626initNCState :: NCState
627initNCState = NCState { _links = Map.empty
628 , _monitors = Map.empty
629 , _registeredHere = Map.empty
630 , _registeredOnNodes = Map.empty
633-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
634-- type is deliberately not exported so it cannot be caught explicitly.
635data ProcessKillException =
636 ProcessKillException !ProcessId !String
639instance Exception ProcessKillException
640instance Show ProcessKillException where
641 show (ProcessKillException pid reason) =
642 "killed-by=" ++ show pid ++ ",reason=" ++ reason
644--------------------------------------------------------------------------------
645-- Tracing/Debugging --
646--------------------------------------------------------------------------------
648-- [Issue #104 / DP-13]
650traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
651traceNotifyDied node ident reason =
652 -- TODO: sendPortDied notifications
653 liftIO $ withLocalTracer node $ \t ->
655 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
656 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
659traceEventFmtIO :: LocalNode
663traceEventFmtIO node fmt args =
664 withLocalTracer node $ \t -> traceLogFmt t fmt args
666trace :: LocalNode -> MxEvent -> IO ()
667trace node ev = withLocalTracer node $ \t -> traceEvent t ev
669withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
670withLocalTracer node act = act (localEventBus node)
672--------------------------------------------------------------------------------
673-- Core functionality --
674--------------------------------------------------------------------------------
677nodeController :: NC ()
681 msg <- liftIO $ readChan (localCtrlChan node)
683 -- [Unified: Table 7, rule nc_forward]
684 case destNid (ctrlMsgSignal msg) of
685 Just nid' | nid' /= localNodeId node ->
686 liftIO $ sendBinary node
688 (NodeIdentifier nid')
689 WithImplicitReconnect
695 NCMsg (ProcessIdentifier from) (Link them) ->
696 ncEffectMonitor from them Nothing
697 NCMsg (ProcessIdentifier from) (Monitor ref) ->
698 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
699 NCMsg (ProcessIdentifier from) (Unlink them) ->
700 ncEffectUnlink from them
701 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
702 ncEffectUnmonitor from ref
703 NCMsg _from (Died ident reason) ->
704 ncEffectDied ident reason
705 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
706 ncEffectSpawn from proc ref
707 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
708 ncEffectRegister from label atnode pid force
709 NCMsg (ProcessIdentifier from) (WhereIs label) ->
710 ncEffectWhereIs from label
711 NCMsg (ProcessIdentifier from) (NamedSend label msg') ->
712 ncEffectNamedSend from label msg'
713 NCMsg _ (UnreliableSend lpid msg') ->
714 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
715 NCMsg _ (LocalSend to msg') ->
716 ncEffectLocalSend node to msg'
717 NCMsg _ (LocalPortSend to msg') ->
718 ncEffectLocalPortSend to msg'
719 NCMsg (ProcessIdentifier from) (Kill to reason) ->
720 ncEffectKill from to reason
721 NCMsg (ProcessIdentifier from) (Exit to reason) ->
722 ncEffectExit from to reason
723 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
724 ncEffectGetInfo from pid
725 NCMsg _ SigShutdown ->
727 NT.closeEndPoint (localEndPoint node)
728 `Exception.finally` throwIO ThreadKilled
729 -- ThreadKilled seems to make more sense than fail/error here
730 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
731 ncEffectGetNodeStats from nid
733 error $ "nodeController: unexpected message " ++ show unexpected
735-- [Unified: Table 10]
736ncEffectMonitor :: ProcessId -- ^ Who's watching?
737 -> Identifier -- ^ Who's being watched?
738 -> Maybe MonitorRef -- ^ 'Nothing' to link
740ncEffectMonitor from them mRef = do
743 if not (isLocal node them)
745 else isValidLocalIdentifier them
746 case (shouldLink, isLocal node (ProcessIdentifier from)) of
747 (True, _) -> -- [Unified: first rule]
749 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
750 Nothing -> modify' $ linksFor them ^: Set.insert from
751 (False, True) -> -- [Unified: second rule]
752 notifyDied from them DiedUnknownId mRef
753 (False, False) -> -- [Unified: third rule]
754 -- TODO: this is the right sender according to the Unified semantics,
755 -- but perhaps having 'them' as the sender would make more sense
756 -- (see also: notifyDied)
757 liftIO $ sendBinary node
758 (NodeIdentifier $ localNodeId node)
759 (NodeIdentifier $ processNodeId from)
760 WithImplicitReconnect
762 { ctrlMsgSender = NodeIdentifier (localNodeId node)
763 , ctrlMsgSignal = Died them DiedUnknownId
766-- [Unified: Table 11]
767ncEffectUnlink :: ProcessId -> Identifier -> NC ()
768ncEffectUnlink from them = do
770 when (isLocal node (ProcessIdentifier from)) $
772 ProcessIdentifier pid ->
773 postAsMessage from $ DidUnlinkProcess pid
774 NodeIdentifier nid ->
775 postAsMessage from $ DidUnlinkNode nid
776 SendPortIdentifier cid ->
777 postAsMessage from $ DidUnlinkPort cid
778 modify' $ linksFor them ^: Set.delete from
780-- [Unified: Table 11]
781ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
782ncEffectUnmonitor from ref = do
784 when (isLocal node (ProcessIdentifier from)) $
785 postAsMessage from $ DidUnmonitor ref
786 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
788-- [Unified: Table 12]
789ncEffectDied :: Identifier -> DiedReason -> NC ()
790ncEffectDied ident reason = do
792 traceNotifyDied node ident reason
793 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
794 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
796-- _registry :: !(Map (String,NodeId) ProcessId)
798 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
800 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
802 when (localOnly <= isLocal node (ProcessIdentifier us)) $
803 notifyDied us them reason Nothing
805 forM_ (Map.toList affectedMons) $ \(them, refs) ->
806 forM_ refs $ \(us, ref) ->
807 when (localOnly <= isLocal node (ProcessIdentifier us)) $
808 notifyDied us them reason (Just ref)
810 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
812 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
814 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
815 mapM (\(pid,nidlist) ->
816 case ident `impliesDeathOf` ProcessIdentifier pid of
818 do forM_ nidlist $ \(nid,_) ->
819 when (not $ isLocal node (NodeIdentifier nid))
820 (forwardNameDeath node nid)
822 False -> return $ Just (pid,nidlist) )
823 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
825 forwardNameDeath node nid =
826 liftIO $ sendBinary node
827 (NodeIdentifier $ localNodeId node)
828 (NodeIdentifier $ nid)
829 WithImplicitReconnect
831 { ctrlMsgSender = NodeIdentifier (localNodeId node)
832 , ctrlMsgSignal = Died ident reason
835-- [Unified: Table 13]
836ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
837ncEffectSpawn pid cProc ref = do
838 mProc <- unClosure cProc
839 -- If the closure does not exist, we spawn a process that throws an exception
840 -- This allows the remote node to find out what's happening
842 let proc = case mProc of
843 Left err -> fail $ "Error: Could not resolve closure: " ++ err
846 pid' <- liftIO $ forkProcess node proc
847 liftIO $ sendMessage node
848 (NodeIdentifier (localNodeId node))
849 (ProcessIdentifier pid)
850 WithImplicitReconnect
853-- Unified semantics does not explicitly describe how to implement 'register',
854-- but mentions it's "very similar to nsend" (Table 14)
855-- We send a response indicated if the operation is invalid
856ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
857ncEffectRegister from label atnode mPid reregistration = do
859 currentVal <- gets (^. registeredHereFor label)
862 Nothing -> -- unregister request
863 return $ isJust currentVal
864 Just thepid -> -- register request
865 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
866 return $ (isNothing currentVal /= reregistration) &&
867 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
868 if isLocal node (NodeIdentifier atnode)
869 then do when (isOk) $
870 do modify' $ registeredHereFor label ^= mPid
871 updateRemote node currentVal mPid
873 (Just p) -> liftIO $ trace node (MxRegistered p label)
874 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
875 liftIO $ sendMessage node
876 (NodeIdentifier (localNodeId node))
877 (ProcessIdentifier from)
878 WithImplicitReconnect
879 (RegisterReply label isOk)
881 case reregistration of
883 False -> flip incList
886 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
887 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
888 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
889 forward node (processNodeId newval) (Register label atnode (Just newval) False)
890 updateRemote node Nothing (Just newval) =
891 forward node (processNodeId newval) (Register label atnode (Just newval) False)
892 updateRemote node (Just oldval) Nothing =
893 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
894 updateRemote _ _ _ = return ()
895 maybeify f Nothing = unmaybeify $ f []
896 maybeify f (Just x) = unmaybeify $ f x
898 unmaybeify [] = Nothing
899 unmaybeify x = Just x
900 incList [] tag = [(tag,1)]
901 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
902 incList (x:xs) tag = x : incList xs tag
904 decList ((atag,1):xs) tag | atag == tag = xs
905 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
906 decList (x:xs) tag = x:decList xs tag
907 forward node to reg =
908 when (not $ isLocal node (NodeIdentifier to)) $
909 liftIO $ sendBinary node
910 (ProcessIdentifier from)
912 WithImplicitReconnect
914 { ctrlMsgSender = ProcessIdentifier from
915 , ctrlMsgSignal = reg
919-- Unified semantics does not explicitly describe 'whereis'
920ncEffectWhereIs :: ProcessId -> String -> NC ()
921ncEffectWhereIs from label = do
923 mPid <- gets (^. registeredHereFor label)
924 liftIO $ sendMessage node
925 (NodeIdentifier (localNodeId node))
926 (ProcessIdentifier from)
927 WithImplicitReconnect
928 (WhereIsReply label mPid)
930-- [Unified: Table 14]
931ncEffectNamedSend :: ProcessId -> String -> Message -> NC ()
932ncEffectNamedSend from label msg = do
933 mPid <- gets (^. registeredHereFor label)
935 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
937 liftIO $ sendPayload node
938 (ProcessIdentifier from)
939 (ProcessIdentifier pid)
941 (messageToPayload msg)
944ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
945ncEffectLocalSend node to msg =
946 liftIO $ withLocalProc node to $ \p -> do
947 enqueue (processQueue p) msg
948 trace node (MxReceived to msg)
951ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
952ncEffectLocalPortSend from msg = do
954 let pid = sendPortProcessId from
955 cid = sendPortLocalId from
956 liftIO $ withLocalProc node pid $ \proc -> do
957 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
959 -- in the unlikely event we know nothing about this channel id,
960 -- there's little to be done - perhaps some logging/tracing though...
962 Just (TypedChannel chan') -> do
963 -- If ch is Nothing, the process has given up the read end of
964 -- the channel and we simply ignore the incoming message - this
965 ch <- deRefWeak chan'
966 forM_ ch $ \chan -> deliverChan msg chan
967 where deliverChan :: forall a . Message -> TQueue a -> IO ()
968 deliverChan (UnencodedMessage _ raw) chan' =
969 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
970 deliverChan (EncodedMessage _ _) _ =
971 -- this will not happen unless someone screws with Primitives.hs
972 error "invalid local channel delivery"
975ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
976ncEffectKill from to reason = do
978 when (isLocal node (ProcessIdentifier to)) $
979 throwException to $ ProcessKillException from reason
982ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
983ncEffectExit from to reason = do
985 when (isLocal node (ProcessIdentifier to)) $
986 throwException to $ ProcessExitException from reason
989ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
990ncEffectGetInfo from pid =
991 let lpid = processLocalId pid
992 them = (ProcessIdentifier pid)
995 mProc <- liftIO $ withValidLocalState node
996 $ return . (^. localProcessWithId lpid)
998 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
999 from node (ProcessInfoNone DiedUnknownId)
1001 itsLinks <- gets (^. linksFor them)
1002 itsMons <- gets (^. monitorsFor them)
1003 registered <- gets (^. registeredHere)
1004 size <- liftIO $ queueSize $ processQueue $ proc
1006 let reg = registeredNames registered
1007 dispatch (isLocal node (ProcessIdentifier from))
1011 infoNode = (processNodeId pid)
1012 , infoRegisteredNames = reg
1013 , infoMessageQueueLength = size
1014 , infoMonitors = Set.toList itsMons
1015 , infoLinks = Set.toList itsLinks
1017 where dispatch :: (Serializable a, Show a)
1023 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1024 dispatch False dest node pInfo = do
1025 liftIO $ sendMessage node
1026 (NodeIdentifier (localNodeId node))
1027 (ProcessIdentifier dest)
1028 WithImplicitReconnect
1031 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1035ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1036ncEffectGetNodeStats from _nid = do
1038 ncState <- StateT.get
1039 nodeState <- liftIO $ withValidLocalState node return
1042 nodeStatsNode = localNodeId node
1043 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1044 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1045 , nodeStatsLinks = Map.size $ ncState ^. links
1046 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1048 postAsMessage from stats
1050--------------------------------------------------------------------------------
1052--------------------------------------------------------------------------------
1054notifyDied :: ProcessId -- ^ Who to notify?
1055 -> Identifier -- ^ Who died?
1056 -> DiedReason -- ^ How did they die?
1057 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1059notifyDied dest src reason mRef = do
1061 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1062 (True, Just ref, ProcessIdentifier pid) ->
1063 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1064 (True, Just ref, NodeIdentifier nid) ->
1065 postAsMessage dest $ NodeMonitorNotification ref nid reason
1066 (True, Just ref, SendPortIdentifier cid) ->
1067 postAsMessage dest $ PortMonitorNotification ref cid reason
1068 (True, Nothing, ProcessIdentifier pid) ->
1069 throwException dest $ ProcessLinkException pid reason
1070 (True, Nothing, NodeIdentifier pid) ->
1071 throwException dest $ NodeLinkException pid reason
1072 (True, Nothing, SendPortIdentifier pid) ->
1073 throwException dest $ PortLinkException pid reason
1075 -- The change in sender comes from [Unified: Table 10]
1076 liftIO $ sendBinary node
1077 (NodeIdentifier $ localNodeId node)
1078 (NodeIdentifier $ processNodeId dest)
1079 WithImplicitReconnect
1081 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1082 , ctrlMsgSignal = Died src reason
1085-- | [Unified: Table 8]
1086destNid :: ProcessSignal -> Maybe NodeId
1087destNid (Link ident) = Just $ nodeOf ident
1088destNid (Unlink ident) = Just $ nodeOf ident
1089destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1090destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1091destNid (Spawn _ _) = Nothing
1092destNid (Register _ _ _ _) = Nothing
1093destNid (WhereIs _) = Nothing
1094destNid (NamedSend _ _) = Nothing
1095destNid (UnreliableSend _ _) = Nothing
1096-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1097-- then when a local process dies the monitoring/linking machinery will take
1098-- care of notifying remote nodes
1099destNid (Died _ _) = Nothing
1100destNid (Kill pid _) = Just $ processNodeId pid
1101destNid (Exit pid _) = Just $ processNodeId pid
1102destNid (GetInfo pid) = Just $ processNodeId pid
1103destNid (GetNodeStats nid) = Just nid
1104destNid (LocalSend pid _) = Just $ processNodeId pid
1105destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1106destNid (SigShutdown) = Nothing
1108-- | Check if a process is local to our own node
1109isLocal :: LocalNode -> Identifier -> Bool
1110isLocal nid ident = nodeOf ident == localNodeId nid
1112-- | Lookup a local closure
1113unClosure :: Typeable a => Closure a -> NC (Either String a)
1114unClosure closure = do
1115 rtable <- remoteTable <$> ask
1116 return (Static.unclosure rtable closure)
1118-- | Check if an identifier refers to a valid local object
1119isValidLocalIdentifier :: Identifier -> NC Bool
1120isValidLocalIdentifier ident = do
1122 liftIO . withValidLocalState node $ \nSt ->
1124 NodeIdentifier nid ->
1125 return $ nid == localNodeId node
1126 ProcessIdentifier pid -> do
1127 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1128 return $ isJust mProc
1129 SendPortIdentifier cid -> do
1130 let pid = sendPortProcessId cid
1131 mProc = nSt ^. localProcessWithId (processLocalId pid)
1133 Nothing -> return False
1134 Just proc -> withMVar (processState proc) $ \pSt -> do
1135 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1138--------------------------------------------------------------------------------
1139-- Messages to local processes --
1140--------------------------------------------------------------------------------
1142postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1143postAsMessage pid = postMessage pid . createUnencodedMessage
1145postMessage :: ProcessId -> Message -> NC ()
1146postMessage pid msg = do
1148 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1150throwException :: Exception e => ProcessId -> e -> NC ()
1151throwException pid e = do
1153 -- throwTo blocks until the exception is received by the target thread.
1154 -- We fork a helper thread to avoid blocking the node controller.
1155 liftIO $ withLocalProc node pid $ \p -> void $ forkIO $ throwTo (processThread p) e
1157withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1158withLocalProc node pid p =
1159 -- By [Unified: table 6, rule missing_process] messages to dead processes
1160 -- can silently be dropped
1161 let lpid = processLocalId pid in do
1162 withValidLocalState node $ \vst ->
1163 forM_ (vst ^. localProcessWithId lpid) p
1165--------------------------------------------------------------------------------
1167--------------------------------------------------------------------------------
1169links :: Accessor NCState (Map Identifier (Set ProcessId))
1170links = accessor _links (\ls st -> st { _links = ls })
1172monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1173monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1175registeredHere :: Accessor NCState (Map String ProcessId)
1176registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1178registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1179registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1181linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1182linksFor ident = links >>> DAC.mapDefault Set.empty ident
1184monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1185monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1187registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1188registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1190registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1191registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1193-- | @splitNotif ident@ splits a notifications map into those
1194-- notifications that should trigger when 'ident' fails and those links that
1197-- There is a hierarchy between identifiers: failure of a node implies failure
1198-- of all processes on that node, and failure of a process implies failure of
1199-- all typed channels to that process. In other words, if 'ident' refers to a
1200-- node, then the /should trigger/ set will include
1202-- * the notifications for the node specifically
1203-- * the notifications for processes on that node, and
1204-- * the notifications for typed channels to processes on that node.
1206-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1209-- * the notifications for that process specifically and
1210-- * the notifications for typed channels to that process.
1212-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1213splitNotif :: Identifier
1215 -> (Map Identifier a, Map Identifier a)
1216splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1218--------------------------------------------------------------------------------
1220--------------------------------------------------------------------------------
1222-- | Modify and evaluate the state
1223modify' :: MonadState s m => (s -> s) -> m ()
1224modify' 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