1{-# LANGUAGE RecordWildCards #-}
5module Control.Distributed.Process.Node
15-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
17#if ! MIN_VERSION_base(4,6,0)
18import Prelude hiding (catch)
21import System.IO (fixIO, hPutStrLn, stderr)
22import System.Mem.Weak (Weak, deRefWeak)
23import qualified Data.ByteString.Lazy as BSL (fromChunks)
24import Data.Binary (decode)
26import qualified Data.Map as Map
37import qualified Data.Set as Set
44import Data.Foldable (forM_)
45import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
46import Data.Typeable (Typeable)
47import Control.Category ((>>>))
48import Control.Applicative ((<$>))
49import Control.Monad (void, when)
50import Control.Monad.IO.Class (MonadIO, liftIO)
51import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
52import qualified Control.Monad.State.Strict as StateT (get, put)
53import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
54import Control.Exception
56 , AsyncException(ThreadKilled)
61import qualified Control.Exception as Exception (Handler(..), catches, finally)
62import Control.Concurrent (forkIO, myThreadId)
63import Control.Distributed.Process.Internal.StrictMVar
72import Control.Concurrent.Chan (newChan, writeChan, readChan)
73import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
74import Control.Concurrent.STM
77import Control.Distributed.Process.Internal.CQueue
83import qualified Network.Transport as NT
97 , Reliability(ReliableOrdered)
99import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
100import System.Random (randomIO)
101import Control.Distributed.Static (RemoteTable, Closure)
102import qualified Control.Distributed.Static as Static
106import Control.Distributed.Process.Internal.Types
114 , LocalProcessState(..)
125 , ProcessMonitorNotification(..)
126 , NodeMonitorNotification(..)
127 , PortMonitorNotification(..)
128 , ProcessExitException(..)
129 , ProcessLinkException(..)
130 , NodeLinkException(..)
131 , PortLinkException(..)
133 , DidUnlinkProcess(..)
143 , ProcessInfoNone(..)
149 , createUnencodedMessage
151 , firstNonReservedProcessId
152 , ImplicitReconnect(WithImplicitReconnect)
154import Control.Distributed.Process.Management.Agent
157import qualified Control.Distributed.Process.Management.Table as Table
159 , startTableCoordinator
161import qualified Control.Distributed.Process.Management.Trace.Remote as Trace
164import Control.Distributed.Process.Management.Trace.Tracer
167import Control.Distributed.Process.Management.Trace.Types
173import Control.Distributed.Process.Management.Types
176import Control.Distributed.Process.Serializable (Serializable)
177import Control.Distributed.Process.Internal.Messaging
180 , closeImplicitReconnections
183import Control.Distributed.Process.Internal.Primitives
192import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
193import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
194import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
195import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
201--------------------------------------------------------------------------------
203--------------------------------------------------------------------------------
205initRemoteTable :: RemoteTable
206initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
208-- | Initialize a new local node.
209newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
210newLocalNode transport rtable = do
211 mEndPoint <- NT.newEndPoint transport
213 Left ex -> throwIO ex
215 localNode <- createBareLocalNode endPoint rtable
216 startServiceProcesses localNode
219-- | Create a new local node (without any service processes running)
220createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
221createBareLocalNode endPoint rtable = do
223 state <- newMVar LocalNodeState
224 { _localProcesses = Map.empty
225 , _localPidCounter = firstNonReservedProcessId
226 , _localPidUnique = unq
227 , _localConnections = Map.empty
230 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
231 , localEndPoint = endPoint
233 , localCtrlChan = ctrlChan
234 , localEventBus = MxEventBusInitialising
235 , remoteTable = rtable
237 tracedNode <- startMxAgent node
239 -- Once the NC terminates, the endpoint isn't much use,
240 void $ forkIO $ Exception.finally (runNodeController tracedNode)
241 (NT.closeEndPoint (localEndPoint node))
243 -- whilst a closed/failing endpoint will terminate the NC
244 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
250 writeChan (localCtrlChan node) NCMsg
251 { ctrlMsgSender = NodeIdentifier (localNodeId node)
252 , ctrlMsgSignal = SigShutdown
255startMxAgent :: LocalNode -> IO LocalNode
256startMxAgent node = do
257 -- see note [tracer/forkProcess races]
258 let fork = forkProcess node
259 mv <- MVar.newEmptyMVar
260 pid <- fork $ mxAgentController fork mv
261 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
262 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
264startDefaultTracer :: LocalNode -> IO ()
265startDefaultTracer node' = do
266 let t = localEventBus node'
268 MxEventBus _ (Tracer pid _) _ _ -> do
269 runProcess node' $ register "trace.controller" pid
270 pid' <- forkProcess node' defaultTracer
271 enableTrace (localEventBus node') pid'
272 runProcess node' $ register "tracer.initial" pid'
275-- TODO: we need a better mechanism for defining and registering services
277-- | Start and register the service processes on a node
278startServiceProcesses :: LocalNode -> IO ()
279startServiceProcesses node = do
280 -- tracing /spawns/ relies on the tracer being enabled, but we start
281 -- the default tracer first, even though it might @nsend@ to the logger
282 -- before /that/ process has started - this is a totally harmless race
283 -- however, so we deliberably ignore it
284 startDefaultTracer node
285 tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
286 runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
287 logger <- forkProcess node loop
288 runProcess node $ register "logger" logger
290 fork = forkProcess node
294 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
295 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
297 , match $ \((time, string) :: (String, String)) -> do
298 -- this is a 'trace' message from the local node tracer
299 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
301 , match $ \(ch :: SendPort ()) -> -- a shutdown request
305-- | Force-close a local node
307-- TODO: for now we just close the associated endpoint
308closeLocalNode :: LocalNode -> IO ()
310 -- TODO: close all our processes, surely!?
311 NT.closeEndPoint (localEndPoint node)
313-- | Run a process on a local node and wait for it to finish
314runProcess :: LocalNode -> Process () -> IO ()
315runProcess node proc = do
318 void $ forkProcess node $ do
319 catch (proc `finally` liftIO (putMVar done ()))
320 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
323-- | Spawn a new process on a local node
324forkProcess :: LocalNode -> Process () -> IO ProcessId
325forkProcess node proc = modifyMVar (localState node) startProcess
327 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
329 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
330 , lpidUnique = st ^. localPidUnique
332 let pid = ProcessId { processNodeId = localNodeId node
333 , processLocalId = lpid
335 pst <- newMVar LocalProcessState { _monitorCounter = 0
337 , _channelCounter = 0
338 , _typedChannels = Map.empty
341 weakQueue <- mkWeakCQueue queue (return ())
342 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
343 let lproc = LocalProcess { processQueue = queue
344 , processWeakQ = weakQueue
347 , processThread = tid
351 reason <- Exception.catches
352 (runLocalProcess lproc proc >> return DiedNormal)
353 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
354 mMsg <- unwrapMessage msg :: IO (Maybe String)
356 Nothing -> return $ DiedException $ show ex
357 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
359 (return . DiedException . (show :: SomeException -> String)))]
361 -- [Unified: Table 4, rules termination and exiting]
362 modifyMVar_ (localState node) (cleanupProcess pid)
363 writeChan (localCtrlChan node) NCMsg
364 { ctrlMsgSender = ProcessIdentifier pid
365 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
369 -- see note [tracer/forkProcess races]
370 trace node (MxSpawned pid)
372 if lpidCounter lpid == maxBound
374 -- TODO: this doesn't look right at all - how do we know
375 -- that newUnique represents a process id that is available!?
376 newUnique <- randomIO
377 return ( (localProcessWithId lpid ^= Just lproc)
378 . (localPidCounter ^= firstNonReservedProcessId)
379 . (localPidUnique ^= newUnique)
384 return ( (localProcessWithId lpid ^= Just lproc)
385 . (localPidCounter ^: (+ 1))
390 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
391 cleanupProcess pid st = do
392 let pid' = ProcessIdentifier pid
393 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
394 mapM_ (NT.close . fst) (Map.elems affected)
395 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
396 . (localConnections ^= unaffected)
399-- note [tracer/forkProcess races]
401-- Our startTracing function uses forkProcess to start the trace controller
402-- process, and of course forkProcess attempts to call traceEvent once the
403-- process has started. This is harmless, as the localEventBus is not updated
404-- until /after/ the initial forkProcess completes, so the first call to
405-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
408--------------------------------------------------------------------------------
409-- Handle incoming messages --
410--------------------------------------------------------------------------------
412type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
416 | ToProc ProcessId (Weak (CQueue Message))
417 | ToChan TypedChannel
420data ConnectionState = ConnectionState {
421 _incoming :: !(Map NT.ConnectionId IncomingConnection)
422 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
425initConnectionState :: ConnectionState
426initConnectionState = ConnectionState {
427 _incoming = Map.empty
428 , _incomingFrom = Map.empty
431incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
432incoming = accessor _incoming (\conns st -> st { _incoming = conns })
434incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
435incomingAt cid = incoming >>> DAC.mapMaybe cid
437incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
438incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
440 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
442handleIncomingMessages :: LocalNode -> IO ()
443handleIncomingMessages node = go initConnectionState
445 go :: ConnectionState -> IO ()
447 event <- NT.receive endpoint
449 NT.ConnectionOpened cid rel theirAddr ->
450 if rel == NT.ReliableOrdered
452 trace node (MxConnected cid theirAddr)
454 (incomingAt cid ^= Just (theirAddr, Uninit))
455 . (incomingFrom theirAddr ^: Set.insert cid)
458 else invalidRequest cid st
459 NT.Received cid payload ->
460 case st ^. incomingAt cid of
461 Just (_, ToProc pid weakQueue) -> do
462 mQueue <- deRefWeak weakQueue
463 forM_ mQueue $ \queue -> do
464 -- TODO: if we find that the queue is Nothing, should we remove
465 -- it from the NC state? (and same for channels, below)
466 let msg = payloadToMessage payload
467 enqueue queue msg -- 'enqueue' is strict
468 trace node (MxReceived pid msg)
470 Just (_, ToChan (TypedChannel chan')) -> do
471 mChan <- deRefWeak chan'
472 -- If mChan is Nothing, the process has given up the read end of
473 -- the channel and we simply ignore the incoming message
474 forM_ mChan $ \chan -> atomically $
475 -- We make sure the message is fully decoded when it is enqueued
476 writeTQueue chan $! decode (BSL.fromChunks payload)
478 Just (_, ToNode) -> do
479 let ctrlMsg = decode . BSL.fromChunks $ payload
480 writeChan ctrlChan $! ctrlMsg
482 Just (src, Uninit) ->
483 case decode (BSL.fromChunks payload) of
484 ProcessIdentifier pid -> do
485 let lpid = processLocalId pid
486 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
489 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
491 invalidRequest cid st
492 SendPortIdentifier chId -> do
493 let lcid = sendPortLocalId chId
494 lpid = processLocalId (sendPortProcessId chId)
495 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
498 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
501 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
503 invalidRequest cid st
505 invalidRequest cid st
506 NodeIdentifier nid ->
507 if nid == localNodeId node
508 then go (incomingAt cid ^= Just (src, ToNode) $ st)
509 else invalidRequest cid st
511 invalidRequest cid st
512 NT.ConnectionClosed cid ->
513 case st ^. incomingAt cid of
515 invalidRequest cid st
517 trace node (MxDisconnected cid src)
518 go ( (incomingAt cid ^= Nothing)
519 . (incomingFrom src ^: Set.delete cid)
522 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
523 -- [Unified table 9, rule node_disconnect]
524 let nid = NodeIdentifier $ NodeId theirAddr
525 writeChan ctrlChan NCMsg
526 { ctrlMsgSender = nid
527 , ctrlMsgSignal = Died nid DiedDisconnect
529 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
530 closeImplicitReconnections node nid
531 go ( (incomingFrom theirAddr ^= Set.empty)
532 . (incoming ^: Map.filterWithKey (const . notLost))
535 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
536 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
537 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
538 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
541 NT.ReceivedMulticast _ _ ->
542 -- If we received a multicast message, something went horribly wrong
543 -- and we just give up
544 fail "Cloud Haskell fatal error: received unexpected multicast"
546 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
547 invalidRequest cid st = do
548 -- TODO: We should treat this as a fatal error on the part of the remote
549 -- node. That is, we should report the remote node as having died, and we
550 -- should close incoming connections (this requires a Transport layer
552 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
554 go ( incomingAt cid ^= Nothing
558 state = localState node
559 endpoint = localEndPoint node
560 ctrlChan = localCtrlChan node
562--------------------------------------------------------------------------------
563-- Top-level access to the node controller --
564--------------------------------------------------------------------------------
566runNodeController :: LocalNode -> IO ()
568 runReaderT (evalStateT (unNC nodeController) initNCState)
570--------------------------------------------------------------------------------
571-- Internal data types --
572--------------------------------------------------------------------------------
574data NCState = NCState
575 { -- Mapping from remote processes to linked local processes
576 _links :: !(Map Identifier (Set ProcessId))
577 -- Mapping from remote processes to monitoring local processes
578 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
579 -- Process registry: names and where they live, mapped to the PIDs
580 , _registeredHere :: !(Map String ProcessId)
581 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
584newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
585 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
587initNCState :: NCState
588initNCState = NCState { _links = Map.empty
589 , _monitors = Map.empty
590 , _registeredHere = Map.empty
591 , _registeredOnNodes = Map.empty
594-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
595-- type is deliberately not exported so it cannot be caught explicitly.
596data ProcessKillException =
597 ProcessKillException !ProcessId !String
600instance Exception ProcessKillException
601instance Show ProcessKillException where
602 show (ProcessKillException pid reason) =
603 "killed-by=" ++ show pid ++ ",reason=" ++ reason
605--------------------------------------------------------------------------------
606-- Tracing/Debugging --
607--------------------------------------------------------------------------------
609-- [Issue #104 / DP-13]
611traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
612traceNotifyDied node ident reason =
613 -- TODO: sendPortDied notifications
614 liftIO $ withLocalTracer node $ \t ->
616 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
617 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
620traceEventFmtIO :: LocalNode
624traceEventFmtIO node fmt args =
625 withLocalTracer node $ \t -> traceLogFmt t fmt args
627trace :: LocalNode -> MxEvent -> IO ()
628trace node ev = withLocalTracer node $ \t -> traceEvent t ev
630withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
631withLocalTracer node act = act (localEventBus node)
633--------------------------------------------------------------------------------
634-- Core functionality --
635--------------------------------------------------------------------------------
638nodeController :: NC ()
642 msg <- liftIO $ readChan (localCtrlChan node)
644 -- [Unified: Table 7, rule nc_forward]
645 case destNid (ctrlMsgSignal msg) of
646 Just nid' | nid' /= localNodeId node ->
647 liftIO $ sendBinary node
649 (NodeIdentifier nid')
650 WithImplicitReconnect
656 NCMsg (ProcessIdentifier from) (Link them) ->
657 ncEffectMonitor from them Nothing
658 NCMsg (ProcessIdentifier from) (Monitor ref) ->
659 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
660 NCMsg (ProcessIdentifier from) (Unlink them) ->
661 ncEffectUnlink from them
662 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
663 ncEffectUnmonitor from ref
664 NCMsg _from (Died ident reason) ->
665 ncEffectDied ident reason
666 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
667 ncEffectSpawn from proc ref
668 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
669 ncEffectRegister from label atnode pid force
670 NCMsg (ProcessIdentifier from) (WhereIs label) ->
671 ncEffectWhereIs from label
672 NCMsg _ (NamedSend label msg') ->
673 ncEffectNamedSend label msg'
674 NCMsg _ (LocalSend to msg') ->
675 ncEffectLocalSend node to msg'
676 NCMsg _ (LocalPortSend to msg') ->
677 ncEffectLocalPortSend to msg'
678 NCMsg (ProcessIdentifier from) (Kill to reason) ->
679 ncEffectKill from to reason
680 NCMsg (ProcessIdentifier from) (Exit to reason) ->
681 ncEffectExit from to reason
682 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
683 ncEffectGetInfo from pid
684 NCMsg _ SigShutdown ->
686 NT.closeEndPoint (localEndPoint node)
687 `Exception.finally` throwIO ThreadKilled
688 -- ThreadKilled seems to make more sense than fail/error here
690 error $ "nodeController: unexpected message " ++ show unexpected
692-- [Unified: Table 10]
693ncEffectMonitor :: ProcessId -- ^ Who's watching?
694 -> Identifier -- ^ Who's being watched?
695 -> Maybe MonitorRef -- ^ 'Nothing' to link
697ncEffectMonitor from them mRef = do
700 if not (isLocal node them)
702 else isValidLocalIdentifier them
703 case (shouldLink, isLocal node (ProcessIdentifier from)) of
704 (True, _) -> -- [Unified: first rule]
706 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
707 Nothing -> modify' $ linksFor them ^: Set.insert from
708 (False, True) -> -- [Unified: second rule]
709 notifyDied from them DiedUnknownId mRef
710 (False, False) -> -- [Unified: third rule]
711 -- TODO: this is the right sender according to the Unified semantics,
712 -- but perhaps having 'them' as the sender would make more sense
713 -- (see also: notifyDied)
714 liftIO $ sendBinary node
715 (NodeIdentifier $ localNodeId node)
716 (NodeIdentifier $ processNodeId from)
717 WithImplicitReconnect
719 { ctrlMsgSender = NodeIdentifier (localNodeId node)
720 , ctrlMsgSignal = Died them DiedUnknownId
723-- [Unified: Table 11]
724ncEffectUnlink :: ProcessId -> Identifier -> NC ()
725ncEffectUnlink from them = do
727 when (isLocal node (ProcessIdentifier from)) $
729 ProcessIdentifier pid ->
730 postAsMessage from $ DidUnlinkProcess pid
731 NodeIdentifier nid ->
732 postAsMessage from $ DidUnlinkNode nid
733 SendPortIdentifier cid ->
734 postAsMessage from $ DidUnlinkPort cid
735 modify' $ linksFor them ^: Set.delete from
737-- [Unified: Table 11]
738ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
739ncEffectUnmonitor from ref = do
741 when (isLocal node (ProcessIdentifier from)) $
742 postAsMessage from $ DidUnmonitor ref
743 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
745-- [Unified: Table 12]
746ncEffectDied :: Identifier -> DiedReason -> NC ()
747ncEffectDied ident reason = do
749 traceNotifyDied node ident reason
750 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
751 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
753-- _registry :: !(Map (String,NodeId) ProcessId)
755 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
757 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
759 when (localOnly <= isLocal node (ProcessIdentifier us)) $
760 notifyDied us them reason Nothing
762 forM_ (Map.toList affectedMons) $ \(them, refs) ->
763 forM_ refs $ \(us, ref) ->
764 when (localOnly <= isLocal node (ProcessIdentifier us)) $
765 notifyDied us them reason (Just ref)
767 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
769 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
771 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
772 mapM (\(pid,nidlist) ->
773 case ident `impliesDeathOf` ProcessIdentifier pid of
775 do forM_ nidlist $ \(nid,_) ->
776 when (not $ isLocal node (NodeIdentifier nid))
777 (forwardNameDeath node nid)
779 False -> return $ Just (pid,nidlist) )
780 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
782 forwardNameDeath node nid =
783 liftIO $ sendBinary node
784 (NodeIdentifier $ localNodeId node)
785 (NodeIdentifier $ nid)
786 WithImplicitReconnect
788 { ctrlMsgSender = NodeIdentifier (localNodeId node)
789 , ctrlMsgSignal = Died ident reason
792-- [Unified: Table 13]
793ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
794ncEffectSpawn pid cProc ref = do
795 mProc <- unClosure cProc
796 -- If the closure does not exist, we spawn a process that throws an exception
797 -- This allows the remote node to find out what's happening
799 let proc = case mProc of
800 Left err -> fail $ "Error: Could not resolve closure: " ++ err
803 pid' <- liftIO $ forkProcess node proc
804 liftIO $ sendMessage node
805 (NodeIdentifier (localNodeId node))
806 (ProcessIdentifier pid)
807 WithImplicitReconnect
810-- Unified semantics does not explicitly describe how to implement 'register',
811-- but mentions it's "very similar to nsend" (Table 14)
812-- We send a response indicated if the operation is invalid
813ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
814ncEffectRegister from label atnode mPid reregistration = do
816 currentVal <- gets (^. registeredHereFor label)
819 Nothing -> -- unregister request
820 return $ isJust currentVal
821 Just thepid -> -- register request
822 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
823 return $ (isNothing currentVal /= reregistration) &&
824 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
825 if isLocal node (NodeIdentifier atnode)
826 then do when (isOk) $
827 do modify' $ registeredHereFor label ^= mPid
828 updateRemote node currentVal mPid
830 (Just p) -> liftIO $ trace node (MxRegistered p label)
831 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
832 liftIO $ sendMessage node
833 (NodeIdentifier (localNodeId node))
834 (ProcessIdentifier from)
835 WithImplicitReconnect
836 (RegisterReply label isOk)
838 case reregistration of
840 False -> flip incList
843 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
844 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
845 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
846 forward node (processNodeId newval) (Register label atnode (Just newval) False)
847 updateRemote node Nothing (Just newval) =
848 forward node (processNodeId newval) (Register label atnode (Just newval) False)
849 updateRemote node (Just oldval) Nothing =
850 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
851 updateRemote _ _ _ = return ()
852 maybeify f Nothing = unmaybeify $ f []
853 maybeify f (Just x) = unmaybeify $ f x
855 unmaybeify [] = Nothing
856 unmaybeify x = Just x
857 incList [] tag = [(tag,1)]
858 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
859 incList (x:xs) tag = x : incList xs tag
861 decList ((atag,1):xs) tag | atag == tag = xs
862 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
863 decList (x:xs) tag = x:decList xs tag
864 forward node to reg =
865 when (not $ isLocal node (NodeIdentifier to)) $
866 liftIO $ sendBinary node
867 (ProcessIdentifier from)
869 WithImplicitReconnect
871 { ctrlMsgSender = ProcessIdentifier from
872 , ctrlMsgSignal = reg
876-- Unified semantics does not explicitly describe 'whereis'
877ncEffectWhereIs :: ProcessId -> String -> NC ()
878ncEffectWhereIs from label = do
880 mPid <- gets (^. registeredHereFor label)
881 liftIO $ sendMessage node
882 (NodeIdentifier (localNodeId node))
883 (ProcessIdentifier from)
884 WithImplicitReconnect
885 (WhereIsReply label mPid)
887-- [Unified: Table 14]
888ncEffectNamedSend :: String -> Message -> NC ()
889ncEffectNamedSend label msg = do
890 mPid <- gets (^. registeredHereFor label)
891 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
892 forM_ mPid $ \pid -> postMessage pid msg
895ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
896ncEffectLocalSend node to msg =
897 liftIO $ withLocalProc node to $ \p -> do
898 enqueue (processQueue p) msg
899 trace node (MxReceived to msg)
902ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
903ncEffectLocalPortSend from msg = do
905 let pid = sendPortProcessId from
906 cid = sendPortLocalId from
907 liftIO $ withLocalProc node pid $ \proc -> do
908 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
910 -- in the unlikely event we know nothing about this channel id,
911 -- there's little to be done - perhaps some logging/tracing though...
913 Just (TypedChannel chan') -> do
914 -- If ch is Nothing, the process has given up the read end of
915 -- the channel and we simply ignore the incoming message - this
916 ch <- deRefWeak chan'
917 forM_ ch $ \chan -> deliverChan msg chan
918 where deliverChan :: forall a . Message -> TQueue a -> IO ()
919 deliverChan (UnencodedMessage _ raw) chan' =
920 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
921 deliverChan (EncodedMessage _ _) _ =
922 -- this will not happen unless someone screws with Primitives.hs
923 error "invalid local channel delivery"
926ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
927ncEffectKill from to reason = do
929 when (isLocal node (ProcessIdentifier to)) $
930 throwException to $ ProcessKillException from reason
933ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
934ncEffectExit from to reason = do
936 when (isLocal node (ProcessIdentifier to)) $
937 throwException to $ ProcessExitException from reason
940ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
941ncEffectGetInfo from pid =
942 let lpid = processLocalId pid
943 them = (ProcessIdentifier pid)
947 withMVar (localState node) $ return . (^. localProcessWithId lpid)
949 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
950 from node (ProcessInfoNone DiedUnknownId)
952 itsLinks <- gets (^. linksFor them)
953 itsMons <- gets (^. monitorsFor them)
954 registered <- gets (^. registeredHere)
956 let reg = registeredNames registered
957 dispatch (isLocal node (ProcessIdentifier from))
961 infoNode = (processNodeId pid)
962 , infoRegisteredNames = reg
963 -- we cannot populate this field
964 , infoMessageQueueLength = Nothing
965 , infoMonitors = Set.toList itsMons
966 , infoLinks = Set.toList itsLinks
968 where dispatch :: (Serializable a, Show a)
974 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
975 dispatch False dest node pInfo = do
976 liftIO $ sendMessage node
977 (NodeIdentifier (localNodeId node))
978 (ProcessIdentifier dest)
979 WithImplicitReconnect
982 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
986--------------------------------------------------------------------------------
988--------------------------------------------------------------------------------
990notifyDied :: ProcessId -- ^ Who to notify?
991 -> Identifier -- ^ Who died?
992 -> DiedReason -- ^ How did they die?
993 -> Maybe MonitorRef -- ^ 'Nothing' for linking
995notifyDied dest src reason mRef = do
997 case (isLocal node (ProcessIdentifier dest), mRef, src) of
998 (True, Just ref, ProcessIdentifier pid) ->
999 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1000 (True, Just ref, NodeIdentifier nid) ->
1001 postAsMessage dest $ NodeMonitorNotification ref nid reason
1002 (True, Just ref, SendPortIdentifier cid) ->
1003 postAsMessage dest $ PortMonitorNotification ref cid reason
1004 (True, Nothing, ProcessIdentifier pid) ->
1005 throwException dest $ ProcessLinkException pid reason
1006 (True, Nothing, NodeIdentifier pid) ->
1007 throwException dest $ NodeLinkException pid reason
1008 (True, Nothing, SendPortIdentifier pid) ->
1009 throwException dest $ PortLinkException pid reason
1011 -- The change in sender comes from [Unified: Table 10]
1012 liftIO $ sendBinary node
1013 (NodeIdentifier $ localNodeId node)
1014 (NodeIdentifier $ processNodeId dest)
1015 WithImplicitReconnect
1017 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1018 , ctrlMsgSignal = Died src reason
1021-- | [Unified: Table 8]
1022destNid :: ProcessSignal -> Maybe NodeId
1023destNid (Link ident) = Just $ nodeOf ident
1024destNid (Unlink ident) = Just $ nodeOf ident
1025destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1026destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1027destNid (Spawn _ _) = Nothing
1028destNid (Register _ _ _ _) = Nothing
1029destNid (WhereIs _) = Nothing
1030destNid (NamedSend _ _) = Nothing
1031-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1032-- then when a local process dies the monitoring/linking machinery will take
1033-- care of notifying remote nodes
1034destNid (Died _ _) = Nothing
1035destNid (Kill pid _) = Just $ processNodeId pid
1036destNid (Exit pid _) = Just $ processNodeId pid
1037destNid (GetInfo pid) = Just $ processNodeId pid
1038destNid (LocalSend pid _) = Just $ processNodeId pid
1039destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1040destNid (SigShutdown) = Nothing
1042-- | Check if a process is local to our own node
1043isLocal :: LocalNode -> Identifier -> Bool
1044isLocal nid ident = nodeOf ident == localNodeId nid
1046-- | Lookup a local closure
1047unClosure :: Typeable a => Closure a -> NC (Either String a)
1048unClosure closure = do
1049 rtable <- remoteTable <$> ask
1050 return (Static.unclosure rtable closure)
1052-- | Check if an identifier refers to a valid local object
1053isValidLocalIdentifier :: Identifier -> NC Bool
1054isValidLocalIdentifier ident = do
1056 liftIO . withMVar (localState node) $ \nSt ->
1058 NodeIdentifier nid ->
1059 return $ nid == localNodeId node
1060 ProcessIdentifier pid -> do
1061 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1062 return $ isJust mProc
1063 SendPortIdentifier cid -> do
1064 let pid = sendPortProcessId cid
1065 mProc = nSt ^. localProcessWithId (processLocalId pid)
1067 Nothing -> return False
1068 Just proc -> withMVar (processState proc) $ \pSt -> do
1069 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1072--------------------------------------------------------------------------------
1073-- Messages to local processes --
1074--------------------------------------------------------------------------------
1076postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1077postAsMessage pid = postMessage pid . createUnencodedMessage
1079postMessage :: ProcessId -> Message -> NC ()
1080postMessage pid msg = do
1082 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1084throwException :: Exception e => ProcessId -> e -> NC ()
1085throwException pid e = do
1087 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1089withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1090withLocalProc node pid p =
1091 -- By [Unified: table 6, rule missing_process] messages to dead processes
1092 -- can silently be dropped
1093 let lpid = processLocalId pid in do
1094 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1097--------------------------------------------------------------------------------
1099--------------------------------------------------------------------------------
1101links :: Accessor NCState (Map Identifier (Set ProcessId))
1102links = accessor _links (\ls st -> st { _links = ls })
1104monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1105monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1107registeredHere :: Accessor NCState (Map String ProcessId)
1108registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1110registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1111registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1113linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1114linksFor ident = links >>> DAC.mapDefault Set.empty ident
1116monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1117monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1119registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1120registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1122registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1123registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1125-- | @splitNotif ident@ splits a notifications map into those
1126-- notifications that should trigger when 'ident' fails and those links that
1129-- There is a hierarchy between identifiers: failure of a node implies failure
1130-- of all processes on that node, and failure of a process implies failure of
1131-- all typed channels to that process. In other words, if 'ident' refers to a
1132-- node, then the /should trigger/ set will include
1134-- * the notifications for the node specifically
1135-- * the notifications for processes on that node, and
1136-- * the notifications for typed channels to processes on that node.
1138-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1141-- * the notifications for that process specifically and
1142-- * the notifications for typed channels to that process.
1144-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1145splitNotif :: Identifier
1147 -> (Map Identifier a, Map Identifier a)
1148splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1150--------------------------------------------------------------------------------
1152--------------------------------------------------------------------------------
1154-- | Modify and evaluate the state
1155modify' :: MonadState s m => (s -> s) -> m ()
1156modify' 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