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
43import qualified Data.Set as Set
50import Data.Foldable (forM_)
51import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
52import Data.Typeable (Typeable)
53import Control.Category ((>>>))
54import Control.Applicative ((<$>))
55import Control.Monad (void, when)
56import Control.Monad.IO.Class (MonadIO, liftIO)
57import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
58import qualified Control.Monad.State.Strict as StateT (get, put)
59import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
60import Control.Exception
62 , AsyncException(ThreadKilled)
67import qualified Control.Exception as Exception (Handler(..), catches, finally)
68import Control.Concurrent (forkIO, myThreadId)
69import Control.Distributed.Process.Internal.StrictMVar
78import Control.Concurrent.Chan (newChan, writeChan, readChan)
79import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
80import Control.Concurrent.STM
83import Control.Distributed.Process.Internal.CQueue
89import qualified Network.Transport as NT
103 , Reliability(ReliableOrdered)
105import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
106import System.Random (randomIO)
107import Control.Distributed.Static (RemoteTable, Closure)
108import qualified Control.Distributed.Static as Static
112import Control.Distributed.Process.Internal.Types
120 , LocalProcessState(..)
131 , ProcessMonitorNotification(..)
132 , NodeMonitorNotification(..)
133 , PortMonitorNotification(..)
134 , ProcessExitException(..)
135 , ProcessLinkException(..)
136 , NodeLinkException(..)
137 , PortLinkException(..)
139 , DidUnlinkProcess(..)
149 , ProcessInfoNone(..)
155 , createUnencodedMessage
157 , firstNonReservedProcessId
158 , ImplicitReconnect(WithImplicitReconnect)
160import Control.Distributed.Process.Management.Agent
163import qualified Control.Distributed.Process.Management.Table as Table
165 , startTableCoordinator
167import qualified Control.Distributed.Process.Management.Trace.Remote as Trace
170import Control.Distributed.Process.Management.Trace.Tracer
173import Control.Distributed.Process.Management.Trace.Types
179import Control.Distributed.Process.Management.Types
182import Control.Distributed.Process.Serializable (Serializable)
183import Control.Distributed.Process.Internal.Messaging
186 , closeImplicitReconnections
189import Control.Distributed.Process.Internal.Primitives
198import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
199import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
200import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
201import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
207--------------------------------------------------------------------------------
209--------------------------------------------------------------------------------
211initRemoteTable :: RemoteTable
212initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
214-- | Initialize a new local node.
215newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
216newLocalNode transport rtable = do
217 mEndPoint <- NT.newEndPoint transport
219 Left ex -> throwIO ex
221 localNode <- createBareLocalNode endPoint rtable
222 startServiceProcesses localNode
225-- | Create a new local node (without any service processes running)
226createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
227createBareLocalNode endPoint rtable = do
229 state <- newMVar LocalNodeState
230 { _localProcesses = Map.empty
231 , _localPidCounter = firstNonReservedProcessId
232 , _localPidUnique = unq
233 , _localConnections = Map.empty
236 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
237 , localEndPoint = endPoint
239 , localCtrlChan = ctrlChan
240 , localEventBus = MxEventBusInitialising
241 , remoteTable = rtable
243 tracedNode <- startMxAgent node
245 -- Once the NC terminates, the endpoint isn't much use,
246 void $ forkIO $ Exception.finally (runNodeController tracedNode)
247 (NT.closeEndPoint (localEndPoint node))
249 -- whilst a closed/failing endpoint will terminate the NC
250 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
256 writeChan (localCtrlChan node) NCMsg
257 { ctrlMsgSender = NodeIdentifier (localNodeId node)
258 , ctrlMsgSignal = SigShutdown
261startMxAgent :: LocalNode -> IO LocalNode
262startMxAgent node = do
263 -- see note [tracer/forkProcess races]
264 let fork = forkProcess node
265 mv <- MVar.newEmptyMVar
266 pid <- fork $ mxAgentController fork mv
267 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
268 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
270startDefaultTracer :: LocalNode -> IO ()
271startDefaultTracer node' = do
272 let t = localEventBus node'
274 MxEventBus _ (Tracer pid _) _ _ -> do
275 runProcess node' $ register "trace.controller" pid
276 pid' <- forkProcess node' defaultTracer
277 enableTrace (localEventBus node') pid'
278 runProcess node' $ register "tracer.initial" pid'
281-- TODO: we need a better mechanism for defining and registering services
283-- | Start and register the service processes on a node
284startServiceProcesses :: LocalNode -> IO ()
285startServiceProcesses node = do
286 -- tracing /spawns/ relies on the tracer being enabled, but we start
287 -- the default tracer first, even though it might @nsend@ to the logger
288 -- before /that/ process has started - this is a totally harmless race
289 -- however, so we deliberably ignore it
290 startDefaultTracer node
291 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
292 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
293 logger <- forkProcess node loop
294 runProcess node $ register "logger" logger
296 fork = forkProcess node
300 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
301 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
303 , match $ \((time, string) :: (String, String)) -> do
304 -- this is a 'trace' message from the local node tracer
305 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
307 , match $ \(ch :: SendPort ()) -> -- a shutdown request
311-- | Force-close a local node
313-- TODO: for now we just close the associated endpoint
314closeLocalNode :: LocalNode -> IO ()
316 -- TODO: close all our processes, surely!?
317 NT.closeEndPoint (localEndPoint node)
319-- | Run a process on a local node and wait for it to finish
320runProcess :: LocalNode -> Process () -> IO ()
321runProcess node proc = do
324 void $ forkProcess node $ do
325 catch (proc `finally` liftIO (putMVar done ()))
326 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
329-- | Spawn a new process on a local node
330forkProcess :: LocalNode -> Process () -> IO ProcessId
331forkProcess node proc = modifyMVar (localState node) startProcess
333 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
335 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
336 , lpidUnique = st ^. localPidUnique
338 let pid = ProcessId { processNodeId = localNodeId node
339 , processLocalId = lpid
341 pst <- newMVar LocalProcessState { _monitorCounter = 0
343 , _channelCounter = 0
344 , _typedChannels = Map.empty
347 weakQueue <- mkWeakCQueue queue (return ())
348 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
349 let lproc = LocalProcess { processQueue = queue
350 , processWeakQ = weakQueue
353 , processThread = tid
357 reason <- Exception.catches
358 (runLocalProcess lproc proc >> return DiedNormal)
359 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
360 mMsg <- unwrapMessage msg :: IO (Maybe String)
362 Nothing -> return $ DiedException $ show ex
363 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
365 (return . DiedException . (show :: SomeException -> String)))]
367 -- [Unified: Table 4, rules termination and exiting]
368 modifyMVar_ (localState node) (cleanupProcess pid)
369 writeChan (localCtrlChan node) NCMsg
370 { ctrlMsgSender = ProcessIdentifier pid
371 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
375 -- see note [tracer/forkProcess races]
376 trace node (MxSpawned pid)
378 if lpidCounter lpid == maxBound
380 -- TODO: this doesn't look right at all - how do we know
381 -- that newUnique represents a process id that is available!?
382 newUnique <- randomIO
383 return ( (localProcessWithId lpid ^= Just lproc)
384 . (localPidCounter ^= firstNonReservedProcessId)
385 . (localPidUnique ^= newUnique)
390 return ( (localProcessWithId lpid ^= Just lproc)
391 . (localPidCounter ^: (+ 1))
396 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
397 cleanupProcess pid st = do
398 let pid' = ProcessIdentifier pid
399 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
400 mapM_ (NT.close . fst) (Map.elems affected)
401 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
402 . (localConnections ^= unaffected)
405-- note [tracer/forkProcess races]
407-- Our startTracing function uses forkProcess to start the trace controller
408-- process, and of course forkProcess attempts to call traceEvent once the
409-- process has started. This is harmless, as the localEventBus is not updated
410-- until /after/ the initial forkProcess completes, so the first call to
411-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
414--------------------------------------------------------------------------------
415-- Handle incoming messages --
416--------------------------------------------------------------------------------
418type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
422 | ToProc ProcessId (Weak (CQueue Message))
423 | ToChan TypedChannel
426data ConnectionState = ConnectionState {
427 _incoming :: !(Map NT.ConnectionId IncomingConnection)
428 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
431initConnectionState :: ConnectionState
432initConnectionState = ConnectionState {
433 _incoming = Map.empty
434 , _incomingFrom = Map.empty
437incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
438incoming = accessor _incoming (\conns st -> st { _incoming = conns })
440incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
441incomingAt cid = incoming >>> DAC.mapMaybe cid
443incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
444incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
446 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
448handleIncomingMessages :: LocalNode -> IO ()
449handleIncomingMessages node = go initConnectionState
451 go :: ConnectionState -> IO ()
453 event <- NT.receive endpoint
455 NT.ConnectionOpened cid rel theirAddr ->
456 if rel == NT.ReliableOrdered
458 trace node (MxConnected cid theirAddr)
460 (incomingAt cid ^= Just (theirAddr, Uninit))
461 . (incomingFrom theirAddr ^: Set.insert cid)
464 else invalidRequest cid st
465 NT.Received cid payload ->
466 case st ^. incomingAt cid of
467 Just (_, ToProc pid weakQueue) -> do
468 mQueue <- deRefWeak weakQueue
469 forM_ mQueue $ \queue -> do
470 -- TODO: if we find that the queue is Nothing, should we remove
471 -- it from the NC state? (and same for channels, below)
472 let msg = payloadToMessage payload
473 enqueue queue msg -- 'enqueue' is strict
474 trace node (MxReceived pid msg)
476 Just (_, ToChan (TypedChannel chan')) -> do
477 mChan <- deRefWeak chan'
478 -- If mChan is Nothing, the process has given up the read end of
479 -- the channel and we simply ignore the incoming message
480 forM_ mChan $ \chan -> atomically $
481 -- We make sure the message is fully decoded when it is enqueued
482 writeTQueue chan $! decode (BSL.fromChunks payload)
484 Just (_, ToNode) -> do
485 let ctrlMsg = decode . BSL.fromChunks $ payload
486 writeChan ctrlChan $! ctrlMsg
488 Just (src, Uninit) ->
489 case decode (BSL.fromChunks payload) of
490 ProcessIdentifier pid -> do
491 let lpid = processLocalId pid
492 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
495 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
497 invalidRequest cid st
498 SendPortIdentifier chId -> do
499 let lcid = sendPortLocalId chId
500 lpid = processLocalId (sendPortProcessId chId)
501 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
504 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
507 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
509 invalidRequest cid st
511 invalidRequest cid st
512 NodeIdentifier nid ->
513 if nid == localNodeId node
514 then go (incomingAt cid ^= Just (src, ToNode) $ st)
515 else invalidRequest cid st
517 invalidRequest cid st
518 NT.ConnectionClosed cid ->
519 case st ^. incomingAt cid of
521 invalidRequest cid st
523 trace node (MxDisconnected cid src)
524 go ( (incomingAt cid ^= Nothing)
525 . (incomingFrom src ^: Set.delete cid)
528 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
529 -- [Unified table 9, rule node_disconnect]
530 let nid = NodeIdentifier $ NodeId theirAddr
531 writeChan ctrlChan NCMsg
532 { ctrlMsgSender = nid
533 , ctrlMsgSignal = Died nid DiedDisconnect
535 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
536 closeImplicitReconnections node nid
537 go ( (incomingFrom theirAddr ^= Set.empty)
538 . (incoming ^: Map.filterWithKey (const . notLost))
541 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
542 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
543 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
544 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
547 NT.ReceivedMulticast _ _ ->
548 -- If we received a multicast message, something went horribly wrong
549 -- and we just give up
550 fail "Cloud Haskell fatal error: received unexpected multicast"
552 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
553 invalidRequest cid st = do
554 -- TODO: We should treat this as a fatal error on the part of the remote
555 -- node. That is, we should report the remote node as having died, and we
556 -- should close incoming connections (this requires a Transport layer
558 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
560 go ( incomingAt cid ^= Nothing
564 state = localState node
565 endpoint = localEndPoint node
566 ctrlChan = localCtrlChan node
568--------------------------------------------------------------------------------
569-- Top-level access to the node controller --
570--------------------------------------------------------------------------------
572runNodeController :: LocalNode -> IO ()
574 runReaderT (evalStateT (unNC nodeController) initNCState)
576--------------------------------------------------------------------------------
577-- Internal data types --
578--------------------------------------------------------------------------------
580data NCState = NCState
581 { -- Mapping from remote processes to linked local processes
582 _links :: !(Map Identifier (Set ProcessId))
583 -- Mapping from remote processes to monitoring local processes
584 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
585 -- Process registry: names and where they live, mapped to the PIDs
586 , _registeredHere :: !(Map String ProcessId)
587 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
590newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
591 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
593initNCState :: NCState
594initNCState = NCState { _links = Map.empty
595 , _monitors = Map.empty
596 , _registeredHere = Map.empty
597 , _registeredOnNodes = Map.empty
600-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
601-- type is deliberately not exported so it cannot be caught explicitly.
602data ProcessKillException =
603 ProcessKillException !ProcessId !String
606instance Exception ProcessKillException
607instance Show ProcessKillException where
608 show (ProcessKillException pid reason) =
609 "killed-by=" ++ show pid ++ ",reason=" ++ reason
611--------------------------------------------------------------------------------
612-- Tracing/Debugging --
613--------------------------------------------------------------------------------
615-- [Issue #104 / DP-13]
617traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
618traceNotifyDied node ident reason =
619 -- TODO: sendPortDied notifications
620 liftIO $ withLocalTracer node $ \t ->
622 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
623 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
626traceEventFmtIO :: LocalNode
630traceEventFmtIO node fmt args =
631 withLocalTracer node $ \t -> traceLogFmt t fmt args
633trace :: LocalNode -> MxEvent -> IO ()
634trace node ev = withLocalTracer node $ \t -> traceEvent t ev
636withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
637withLocalTracer node act = act (localEventBus node)
639--------------------------------------------------------------------------------
640-- Core functionality --
641--------------------------------------------------------------------------------
644nodeController :: NC ()
648 msg <- liftIO $ readChan (localCtrlChan node)
650 -- [Unified: Table 7, rule nc_forward]
651 case destNid (ctrlMsgSignal msg) of
652 Just nid' | nid' /= localNodeId node ->
653 liftIO $ sendBinary node
655 (NodeIdentifier nid')
656 WithImplicitReconnect
662 NCMsg (ProcessIdentifier from) (Link them) ->
663 ncEffectMonitor from them Nothing
664 NCMsg (ProcessIdentifier from) (Monitor ref) ->
665 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
666 NCMsg (ProcessIdentifier from) (Unlink them) ->
667 ncEffectUnlink from them
668 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
669 ncEffectUnmonitor from ref
670 NCMsg _from (Died ident reason) ->
671 ncEffectDied ident reason
672 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
673 ncEffectSpawn from proc ref
674 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
675 ncEffectRegister from label atnode pid force
676 NCMsg (ProcessIdentifier from) (WhereIs label) ->
677 ncEffectWhereIs from label
678 NCMsg _ (NamedSend label msg') ->
679 ncEffectNamedSend label msg'
680 NCMsg _ (LocalSend to msg') ->
681 ncEffectLocalSend node to msg'
682 NCMsg _ (LocalPortSend to msg') ->
683 ncEffectLocalPortSend to msg'
684 NCMsg (ProcessIdentifier from) (Kill to reason) ->
685 ncEffectKill from to reason
686 NCMsg (ProcessIdentifier from) (Exit to reason) ->
687 ncEffectExit from to reason
688 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
689 ncEffectGetInfo from pid
690 NCMsg _ SigShutdown ->
692 NT.closeEndPoint (localEndPoint node)
693 `Exception.finally` throwIO ThreadKilled
694 -- ThreadKilled seems to make more sense than fail/error here
696 error $ "nodeController: unexpected message " ++ show unexpected
698-- [Unified: Table 10]
699ncEffectMonitor :: ProcessId -- ^ Who's watching?
700 -> Identifier -- ^ Who's being watched?
701 -> Maybe MonitorRef -- ^ 'Nothing' to link
703ncEffectMonitor from them mRef = do
706 if not (isLocal node them)
708 else isValidLocalIdentifier them
709 case (shouldLink, isLocal node (ProcessIdentifier from)) of
710 (True, _) -> -- [Unified: first rule]
712 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
713 Nothing -> modify' $ linksFor them ^: Set.insert from
714 (False, True) -> -- [Unified: second rule]
715 notifyDied from them DiedUnknownId mRef
716 (False, False) -> -- [Unified: third rule]
717 -- TODO: this is the right sender according to the Unified semantics,
718 -- but perhaps having 'them' as the sender would make more sense
719 -- (see also: notifyDied)
720 liftIO $ sendBinary node
721 (NodeIdentifier $ localNodeId node)
722 (NodeIdentifier $ processNodeId from)
723 WithImplicitReconnect
725 { ctrlMsgSender = NodeIdentifier (localNodeId node)
726 , ctrlMsgSignal = Died them DiedUnknownId
729-- [Unified: Table 11]
730ncEffectUnlink :: ProcessId -> Identifier -> NC ()
731ncEffectUnlink from them = do
733 when (isLocal node (ProcessIdentifier from)) $
735 ProcessIdentifier pid ->
736 postAsMessage from $ DidUnlinkProcess pid
737 NodeIdentifier nid ->
738 postAsMessage from $ DidUnlinkNode nid
739 SendPortIdentifier cid ->
740 postAsMessage from $ DidUnlinkPort cid
741 modify' $ linksFor them ^: Set.delete from
743-- [Unified: Table 11]
744ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
745ncEffectUnmonitor from ref = do
747 when (isLocal node (ProcessIdentifier from)) $
748 postAsMessage from $ DidUnmonitor ref
749 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
751-- [Unified: Table 12]
752ncEffectDied :: Identifier -> DiedReason -> NC ()
753ncEffectDied ident reason = do
755 traceNotifyDied node ident reason
756 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
757 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
759-- _registry :: !(Map (String,NodeId) ProcessId)
761 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
763 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
765 when (localOnly <= isLocal node (ProcessIdentifier us)) $
766 notifyDied us them reason Nothing
768 forM_ (Map.toList affectedMons) $ \(them, refs) ->
769 forM_ refs $ \(us, ref) ->
770 when (localOnly <= isLocal node (ProcessIdentifier us)) $
771 notifyDied us them reason (Just ref)
773 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
775 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
777 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
778 mapM (\(pid,nidlist) ->
779 case ident `impliesDeathOf` ProcessIdentifier pid of
781 do forM_ nidlist $ \(nid,_) ->
782 when (not $ isLocal node (NodeIdentifier nid))
783 (forwardNameDeath node nid)
785 False -> return $ Just (pid,nidlist) )
786 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
788 forwardNameDeath node nid =
789 liftIO $ sendBinary node
790 (NodeIdentifier $ localNodeId node)
791 (NodeIdentifier $ nid)
792 WithImplicitReconnect
794 { ctrlMsgSender = NodeIdentifier (localNodeId node)
795 , ctrlMsgSignal = Died ident reason
798-- [Unified: Table 13]
799ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
800ncEffectSpawn pid cProc ref = do
801 mProc <- unClosure cProc
802 -- If the closure does not exist, we spawn a process that throws an exception
803 -- This allows the remote node to find out what's happening
805 let proc = case mProc of
806 Left err -> fail $ "Error: Could not resolve closure: " ++ err
809 pid' <- liftIO $ forkProcess node proc
810 liftIO $ sendMessage node
811 (NodeIdentifier (localNodeId node))
812 (ProcessIdentifier pid)
813 WithImplicitReconnect
816-- Unified semantics does not explicitly describe how to implement 'register',
817-- but mentions it's "very similar to nsend" (Table 14)
818-- We send a response indicated if the operation is invalid
819ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
820ncEffectRegister from label atnode mPid reregistration = do
822 currentVal <- gets (^. registeredHereFor label)
825 Nothing -> -- unregister request
826 return $ isJust currentVal
827 Just thepid -> -- register request
828 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
829 return $ (isNothing currentVal /= reregistration) &&
830 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
831 if isLocal node (NodeIdentifier atnode)
832 then do when (isOk) $
833 do modify' $ registeredHereFor label ^= mPid
834 updateRemote node currentVal mPid
836 (Just p) -> liftIO $ trace node (MxRegistered p label)
837 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
838 liftIO $ sendMessage node
839 (NodeIdentifier (localNodeId node))
840 (ProcessIdentifier from)
841 WithImplicitReconnect
842 (RegisterReply label isOk)
844 case reregistration of
846 False -> flip incList
849 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
850 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
851 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
852 forward node (processNodeId newval) (Register label atnode (Just newval) False)
853 updateRemote node Nothing (Just newval) =
854 forward node (processNodeId newval) (Register label atnode (Just newval) False)
855 updateRemote node (Just oldval) Nothing =
856 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
857 updateRemote _ _ _ = return ()
858 maybeify f Nothing = unmaybeify $ f []
859 maybeify f (Just x) = unmaybeify $ f x
861 unmaybeify [] = Nothing
862 unmaybeify x = Just x
863 incList [] tag = [(tag,1)]
864 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
865 incList (x:xs) tag = x : incList xs tag
867 decList ((atag,1):xs) tag | atag == tag = xs
868 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
869 decList (x:xs) tag = x:decList xs tag
870 forward node to reg =
871 when (not $ isLocal node (NodeIdentifier to)) $
872 liftIO $ sendBinary node
873 (ProcessIdentifier from)
875 WithImplicitReconnect
877 { ctrlMsgSender = ProcessIdentifier from
878 , ctrlMsgSignal = reg
882-- Unified semantics does not explicitly describe 'whereis'
883ncEffectWhereIs :: ProcessId -> String -> NC ()
884ncEffectWhereIs from label = do
886 mPid <- gets (^. registeredHereFor label)
887 liftIO $ sendMessage node
888 (NodeIdentifier (localNodeId node))
889 (ProcessIdentifier from)
890 WithImplicitReconnect
891 (WhereIsReply label mPid)
893-- [Unified: Table 14]
894ncEffectNamedSend :: String -> Message -> NC ()
895ncEffectNamedSend label msg = do
896 mPid <- gets (^. registeredHereFor label)
897 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
898 forM_ mPid $ \pid -> postMessage pid msg
901ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
902ncEffectLocalSend node to msg =
903 liftIO $ withLocalProc node to $ \p -> do
904 enqueue (processQueue p) msg
905 trace node (MxReceived to msg)
908ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
909ncEffectLocalPortSend from msg = do
911 let pid = sendPortProcessId from
912 cid = sendPortLocalId from
913 liftIO $ withLocalProc node pid $ \proc -> do
914 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
916 -- in the unlikely event we know nothing about this channel id,
917 -- there's little to be done - perhaps some logging/tracing though...
919 Just (TypedChannel chan') -> do
920 -- If ch is Nothing, the process has given up the read end of
921 -- the channel and we simply ignore the incoming message - this
922 ch <- deRefWeak chan'
923 forM_ ch $ \chan -> deliverChan msg chan
924 where deliverChan :: forall a . Message -> TQueue a -> IO ()
925 deliverChan (UnencodedMessage _ raw) chan' =
926 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
927 deliverChan (EncodedMessage _ _) _ =
928 -- this will not happen unless someone screws with Primitives.hs
929 error "invalid local channel delivery"
932ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
933ncEffectKill from to reason = do
935 when (isLocal node (ProcessIdentifier to)) $
936 throwException to $ ProcessKillException from reason
939ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
940ncEffectExit from to reason = do
942 when (isLocal node (ProcessIdentifier to)) $
943 throwException to $ ProcessExitException from reason
946ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
947ncEffectGetInfo from pid =
948 let lpid = processLocalId pid
949 them = (ProcessIdentifier pid)
953 withMVar (localState node) $ return . (^. localProcessWithId lpid)
955 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
956 from node (ProcessInfoNone DiedUnknownId)
958 itsLinks <- gets (^. linksFor them)
959 itsMons <- gets (^. monitorsFor them)
960 registered <- gets (^. registeredHere)
962 let reg = registeredNames registered
963 dispatch (isLocal node (ProcessIdentifier from))
967 infoNode = (processNodeId pid)
968 , infoRegisteredNames = reg
969 -- we cannot populate this field
970 , infoMessageQueueLength = Nothing
971 , infoMonitors = Set.toList itsMons
972 , infoLinks = Set.toList itsLinks
974 where dispatch :: (Serializable a, Show a)
980 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
981 dispatch False dest node pInfo = do
982 liftIO $ sendMessage node
983 (NodeIdentifier (localNodeId node))
984 (ProcessIdentifier dest)
985 WithImplicitReconnect
988 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
992--------------------------------------------------------------------------------
994--------------------------------------------------------------------------------
996notifyDied :: ProcessId -- ^ Who to notify?
997 -> Identifier -- ^ Who died?
998 -> DiedReason -- ^ How did they die?
999 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1001notifyDied dest src reason mRef = do
1003 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1004 (True, Just ref, ProcessIdentifier pid) ->
1005 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1006 (True, Just ref, NodeIdentifier nid) ->
1007 postAsMessage dest $ NodeMonitorNotification ref nid reason
1008 (True, Just ref, SendPortIdentifier cid) ->
1009 postAsMessage dest $ PortMonitorNotification ref cid reason
1010 (True, Nothing, ProcessIdentifier pid) ->
1011 throwException dest $ ProcessLinkException pid reason
1012 (True, Nothing, NodeIdentifier pid) ->
1013 throwException dest $ NodeLinkException pid reason
1014 (True, Nothing, SendPortIdentifier pid) ->
1015 throwException dest $ PortLinkException pid reason
1017 -- The change in sender comes from [Unified: Table 10]
1018 liftIO $ sendBinary node
1019 (NodeIdentifier $ localNodeId node)
1020 (NodeIdentifier $ processNodeId dest)
1021 WithImplicitReconnect
1023 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1024 , ctrlMsgSignal = Died src reason
1027-- | [Unified: Table 8]
1028destNid :: ProcessSignal -> Maybe NodeId
1029destNid (Link ident) = Just $ nodeOf ident
1030destNid (Unlink ident) = Just $ nodeOf ident
1031destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1032destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1033destNid (Spawn _ _) = Nothing
1034destNid (Register _ _ _ _) = Nothing
1035destNid (WhereIs _) = Nothing
1036destNid (NamedSend _ _) = Nothing
1037-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1038-- then when a local process dies the monitoring/linking machinery will take
1039-- care of notifying remote nodes
1040destNid (Died _ _) = Nothing
1041destNid (Kill pid _) = Just $ processNodeId pid
1042destNid (Exit pid _) = Just $ processNodeId pid
1043destNid (GetInfo pid) = Just $ processNodeId pid
1044destNid (LocalSend pid _) = Just $ processNodeId pid
1045destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1046destNid (SigShutdown) = Nothing
1048-- | Check if a process is local to our own node
1049isLocal :: LocalNode -> Identifier -> Bool
1050isLocal nid ident = nodeOf ident == localNodeId nid
1052-- | Lookup a local closure
1053unClosure :: Typeable a => Closure a -> NC (Either String a)
1054unClosure closure = do
1055 rtable <- remoteTable <$> ask
1056 return (Static.unclosure rtable closure)
1058-- | Check if an identifier refers to a valid local object
1059isValidLocalIdentifier :: Identifier -> NC Bool
1060isValidLocalIdentifier ident = do
1062 liftIO . withMVar (localState node) $ \nSt ->
1064 NodeIdentifier nid ->
1065 return $ nid == localNodeId node
1066 ProcessIdentifier pid -> do
1067 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1068 return $ isJust mProc
1069 SendPortIdentifier cid -> do
1070 let pid = sendPortProcessId cid
1071 mProc = nSt ^. localProcessWithId (processLocalId pid)
1073 Nothing -> return False
1074 Just proc -> withMVar (processState proc) $ \pSt -> do
1075 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1078--------------------------------------------------------------------------------
1079-- Messages to local processes --
1080--------------------------------------------------------------------------------
1082postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1083postAsMessage pid = postMessage pid . createUnencodedMessage
1085postMessage :: ProcessId -> Message -> NC ()
1086postMessage pid msg = do
1088 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1090throwException :: Exception e => ProcessId -> e -> NC ()
1091throwException pid e = do
1093 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1095withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1096withLocalProc node pid p =
1097 -- By [Unified: table 6, rule missing_process] messages to dead processes
1098 -- can silently be dropped
1099 let lpid = processLocalId pid in do
1100 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1103--------------------------------------------------------------------------------
1105--------------------------------------------------------------------------------
1107links :: Accessor NCState (Map Identifier (Set ProcessId))
1108links = accessor _links (\ls st -> st { _links = ls })
1110monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1111monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1113registeredHere :: Accessor NCState (Map String ProcessId)
1114registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1116registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1117registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1119linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1120linksFor ident = links >>> DAC.mapDefault Set.empty ident
1122monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1123monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1125registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1126registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1128registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1129registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1131-- | @splitNotif ident@ splits a notifications map into those
1132-- notifications that should trigger when 'ident' fails and those links that
1135-- There is a hierarchy between identifiers: failure of a node implies failure
1136-- of all processes on that node, and failure of a process implies failure of
1137-- all typed channels to that process. In other words, if 'ident' refers to a
1138-- node, then the /should trigger/ set will include
1140-- * the notifications for the node specifically
1141-- * the notifications for processes on that node, and
1142-- * the notifications for typed channels to processes on that node.
1144-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1147-- * the notifications for that process specifically and
1148-- * the notifications for typed channels to that process.
1150-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1151splitNotif :: Identifier
1153 -> (Map Identifier a, Map Identifier a)
1154splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1156--------------------------------------------------------------------------------
1158--------------------------------------------------------------------------------
1160-- | Modify and evaluate the state
1161modify' :: MonadState s m => (s -> s) -> m ()
1162modify' 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