3module Control.Distributed.Process.Node
13-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
15#if ! MIN_VERSION_base(4,6,0)
16import Prelude hiding (catch)
19import System.IO (fixIO, hPutStrLn, stderr)
20import System.Mem.Weak (Weak, deRefWeak)
21import qualified Data.ByteString.Lazy as BSL (fromChunks)
22import Data.Binary (decode)
24import qualified Data.Map as Map
35import qualified Data.Set as Set
42import Data.Foldable (forM_)
43import Data.Maybe (isJust, isNothing, catMaybes)
44import Data.Typeable (Typeable)
45import Control.Category ((>>>))
46import Control.Applicative ((<$>))
47import Control.Monad (void, when)
48import Control.Monad.IO.Class (MonadIO, liftIO)
49import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
50import qualified Control.Monad.State.Strict as StateT (get, put)
51import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
52import Control.Exception (throwIO, SomeException, Exception, throwTo)
53import qualified Control.Exception as Exception (catch)
54import Control.Concurrent (forkIO)
55import Control.Distributed.Process.Internal.StrictMVar
64import Control.Concurrent.Chan (newChan, writeChan, readChan)
65import Control.Concurrent.STM
68import Control.Distributed.Process.Internal.CQueue
74import qualified Network.Transport as NT
88 , Reliability(ReliableOrdered)
90import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
91import System.Random (randomIO)
92import Control.Distributed.Static (RemoteTable, Closure)
93import qualified Control.Distributed.Static as Static
97import Control.Distributed.Process.Internal.Types
102 , Tracer(InactiveTracer)
105 , LocalProcessState(..)
116 , ProcessMonitorNotification(..)
117 , NodeMonitorNotification(..)
118 , PortMonitorNotification(..)
119 , ProcessExitException(..)
120 , ProcessLinkException(..)
121 , NodeLinkException(..)
122 , PortLinkException(..)
124 , DidUnlinkProcess(..)
134 , ProcessInfoNone(..)
141 , createUnencodedMessage
143 , firstNonReservedProcessId
144 , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
146import Control.Distributed.Process.Internal.Trace
152import Control.Distributed.Process.Serializable (Serializable)
153import Control.Distributed.Process.Internal.Messaging
157 , closeImplicitReconnections
160import Control.Distributed.Process.Internal.Primitives
167import Control.Distributed.Process.Internal.Types (SendPort)
168import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
169import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
170import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
177--------------------------------------------------------------------------------
179--------------------------------------------------------------------------------
181initRemoteTable :: RemoteTable
182initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
184-- | Initialize a new local node.
185newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
186newLocalNode transport rtable = do
187 mEndPoint <- NT.newEndPoint transport
189 Left ex -> throwIO ex
191 localNode <- createBareLocalNode endPoint rtable
192 startServiceProcesses localNode
195-- | Create a new local node (without any service processes running)
196createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
197createBareLocalNode endPoint rtable = do
199 state <- newMVar LocalNodeState
200 { _localProcesses = Map.empty
201 , _localPidCounter = firstNonReservedProcessId
202 , _localPidUnique = unq
203 , _localConnections = Map.empty
206 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
207 , localEndPoint = endPoint
209 , localCtrlChan = ctrlChan
210 , localTracer = InactiveTracer
211 , remoteTable = rtable
213 tracedNode <- startTracing node
214 void . forkIO $ runNodeController tracedNode
215 void . forkIO $ handleIncomingMessages tracedNode
218-- | Start and register the service processes on a node
219-- (for now, this is only the logger)
220startServiceProcesses :: LocalNode -> IO ()
221startServiceProcesses node = do
222 logger <- forkProcess node loop
223 runProcess node $ register "logger" logger
227 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
228 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
230 , match $ \((time, string) :: (String, String)) -> do
231 -- this is a 'trace' message from the local node tracer
232 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
234 , match $ \(ch :: SendPort ()) -> -- a shutdown request
238-- | Force-close a local node
240-- TODO: for now we just close the associated endpoint
241closeLocalNode :: LocalNode -> IO ()
243 NT.closeEndPoint (localEndPoint node)
245-- | Run a process on a local node and wait for it to finish
246runProcess :: LocalNode -> Process () -> IO ()
247runProcess node proc = do
249 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
252-- | Spawn a new process on a local node
253forkProcess :: LocalNode -> Process () -> IO ProcessId
254forkProcess node proc = modifyMVar (localState node) startProcess
256 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
258 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
259 , lpidUnique = st ^. localPidUnique
261 let pid = ProcessId { processNodeId = localNodeId node
262 , processLocalId = lpid
264 pst <- newMVar LocalProcessState { _monitorCounter = 0
266 , _channelCounter = 0
267 , _typedChannels = Map.empty
270 weakQueue <- mkWeakCQueue queue (return ())
271 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
272 let lproc = LocalProcess { processQueue = queue
273 , processWeakQ = weakQueue
276 , processThread = tid
280 reason <- Exception.catch
281 (runLocalProcess lproc proc >> return DiedNormal)
282 (return . DiedException . (show :: SomeException -> String))
284 -- [Unified: Table 4, rules termination and exiting]
285 modifyMVar_ (localState node) (cleanupProcess pid)
286 writeChan (localCtrlChan node) NCMsg
287 { ctrlMsgSender = ProcessIdentifier pid
288 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
292 if lpidCounter lpid == maxBound
294 newUnique <- randomIO
295 return ( (localProcessWithId lpid ^= Just lproc)
296 . (localPidCounter ^= firstNonReservedProcessId)
297 . (localPidUnique ^= newUnique)
302 return ( (localProcessWithId lpid ^= Just lproc)
303 . (localPidCounter ^: (+ 1))
308 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
309 cleanupProcess pid st = do
310 let pid' = ProcessIdentifier pid
311 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
312 mapM_ (NT.close . fst) (Map.elems affected)
313 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
314 . (localConnections ^= unaffected)
317--------------------------------------------------------------------------------
318-- Handle incoming messages --
319--------------------------------------------------------------------------------
321type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
325 | ToProc (Weak (CQueue Message))
326 | ToChan TypedChannel
329data ConnectionState = ConnectionState {
330 _incoming :: !(Map NT.ConnectionId IncomingConnection)
331 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
334initConnectionState :: ConnectionState
335initConnectionState = ConnectionState {
336 _incoming = Map.empty
337 , _incomingFrom = Map.empty
340incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
341incoming = accessor _incoming (\conns st -> st { _incoming = conns })
343incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
344incomingAt cid = incoming >>> DAC.mapMaybe cid
346incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
347incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
349 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
351handleIncomingMessages :: LocalNode -> IO ()
352handleIncomingMessages node = go initConnectionState
354 go :: ConnectionState -> IO ()
356 event <- NT.receive endpoint
358 NT.ConnectionOpened cid rel theirAddr ->
359 if rel == NT.ReliableOrdered
360 then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
361 . (incomingFrom theirAddr ^: Set.insert cid)
364 else invalidRequest cid st
365 NT.Received cid payload ->
366 case st ^. incomingAt cid of
367 Just (_, ToProc weakQueue) -> do
368 mQueue <- deRefWeak weakQueue
369 forM_ mQueue $ \queue -> do
370 -- TODO: if we find that the queue is Nothing, should we remove
371 -- it from the NC state? (and same for channels, below)
372 let msg = payloadToMessage payload
373 enqueue queue msg -- 'enqueue' is strict
375 Just (_, ToChan (TypedChannel chan')) -> do
376 mChan <- deRefWeak chan'
377 -- If mChan is Nothing, the process has given up the read end of
378 -- the channel and we simply ignore the incoming message
379 forM_ mChan $ \chan -> atomically $
380 -- We make sure the message is fully decoded when it is enqueued
381 writeTQueue chan $! decode (BSL.fromChunks payload)
383 Just (_, ToNode) -> do
384 let ctrlMsg = decode . BSL.fromChunks $ payload
385 writeChan ctrlChan $! ctrlMsg
387 Just (src, Uninit) ->
388 case decode (BSL.fromChunks payload) of
389 ProcessIdentifier pid -> do
390 let lpid = processLocalId pid
391 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
394 go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
396 invalidRequest cid st
397 SendPortIdentifier chId -> do
398 let lcid = sendPortLocalId chId
399 lpid = processLocalId (sendPortProcessId chId)
400 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
403 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
406 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
408 invalidRequest cid st
410 invalidRequest cid st
411 NodeIdentifier nid ->
412 if nid == localNodeId node
413 then go (incomingAt cid ^= Just (src, ToNode) $ st)
414 else invalidRequest cid st
416 invalidRequest cid st
417 NT.ConnectionClosed cid ->
418 case st ^. incomingAt cid of
420 invalidRequest cid st
422 go ( (incomingAt cid ^= Nothing)
423 . (incomingFrom src ^: Set.delete cid)
426 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
427 -- [Unified table 9, rule node_disconnect]
428 let nid = NodeIdentifier $ NodeId theirAddr
429 writeChan ctrlChan NCMsg
430 { ctrlMsgSender = nid
431 , ctrlMsgSignal = Died nid DiedDisconnect
433 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
434 closeImplicitReconnections node nid
435 go ( (incomingFrom theirAddr ^= Set.empty)
436 . (incoming ^: Map.filterWithKey (const . notLost))
439 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
440 fatal $ "Cloud Haskell fatal error: end point failed: " ++ str
441 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
442 fatal $ "Cloud Haskell fatal error: transport failed: " ++ str
444 stopTracer (localTracer node) >> return ()
445 NT.ReceivedMulticast _ _ ->
446 -- If we received a multicast message, something went horribly wrong
447 -- and we just give up
448 fatal "Cloud Haskell fatal error: received unexpected multicast"
450 fatal :: String -> IO ()
451 fatal msg = stopTracer (localTracer node) >> fail msg
453 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
454 invalidRequest cid st = do
455 -- TODO: We should treat this as a fatal error on the part of the remote
456 -- node. That is, we should report the remote node as having died, and we
457 -- should close incoming connections (this requires a Transport layer
459 traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),
461 go ( incomingAt cid ^= Nothing
465 state = localState node
466 endpoint = localEndPoint node
467 ctrlChan = localCtrlChan node
469--------------------------------------------------------------------------------
470-- Top-level access to the node controller --
471--------------------------------------------------------------------------------
473runNodeController :: LocalNode -> IO ()
475 runReaderT (evalStateT (unNC nodeController) initNCState)
477--------------------------------------------------------------------------------
478-- Internal data types --
479--------------------------------------------------------------------------------
481data NCState = NCState
482 { -- Mapping from remote processes to linked local processes
483 _links :: !(Map Identifier (Set ProcessId))
484 -- Mapping from remote processes to monitoring local processes
485 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
486 -- Process registry: names and where they live, mapped to the PIDs
487 , _registeredHere :: !(Map String ProcessId)
488 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
491newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
492 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
494initNCState :: NCState
495initNCState = NCState { _links = Map.empty
496 , _monitors = Map.empty
497 , _registeredHere = Map.empty
498 , _registeredOnNodes = Map.empty
501-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
502-- type is deliberately not exported so it cannot be caught explicitly.
503data ProcessKillException =
504 ProcessKillException !ProcessId !String
507instance Exception ProcessKillException
508instance Show ProcessKillException where
509 show (ProcessKillException pid reason) =
510 "killed-by=" ++ show pid ++ ",reason=" ++ reason
512--------------------------------------------------------------------------------
513-- Tracing/Debugging --
514--------------------------------------------------------------------------------
518traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
519traceNotifyDied node ident reason =
521 DiedNormal -> return ()
522 _ -> traceNcEventFmt node " "
523 [(TraceStr "[node-controller]"),
527traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()
528traceNcEventFmt node fmt args =
529 liftIO $ traceEventFmtIO node fmt args
531traceEventFmtIO :: LocalNode
535traceEventFmtIO node fmt args =
536 withLocalTracer node $ \t -> traceFormat t fmt args
538withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
539withLocalTracer node act = act (localTracer node)
541--------------------------------------------------------------------------------
542-- Core functionality --
543--------------------------------------------------------------------------------
546nodeController :: NC ()
550 msg <- liftIO $ readChan (localCtrlChan node)
552 -- [Unified: Table 7, rule nc_forward]
553 case destNid (ctrlMsgSignal msg) of
554 Just nid' | nid' /= localNodeId node ->
555 liftIO $ sendBinary node
557 (NodeIdentifier nid')
558 WithImplicitReconnect
564 NCMsg (ProcessIdentifier from) (Link them) ->
565 ncEffectMonitor from them Nothing
566 NCMsg (ProcessIdentifier from) (Monitor ref) ->
567 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
568 NCMsg (ProcessIdentifier from) (Unlink them) ->
569 ncEffectUnlink from them
570 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
571 ncEffectUnmonitor from ref
572 NCMsg _from (Died ident reason) ->
573 ncEffectDied ident reason
574 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
575 ncEffectSpawn from proc ref
576 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
577 ncEffectRegister from label atnode pid force
578 NCMsg (ProcessIdentifier from) (WhereIs label) ->
579 ncEffectWhereIs from label
580 NCMsg from (NamedSend label msg') ->
581 ncEffectNamedSend from label msg'
582 NCMsg _ (LocalSend to msg') ->
583 ncEffectLocalSend to msg'
584 NCMsg _ (LocalPortSend to msg') ->
585 ncEffectLocalPortSend to msg'
586 NCMsg (ProcessIdentifier from) (Kill to reason) ->
587 ncEffectKill from to reason
588 NCMsg (ProcessIdentifier from) (Exit to reason) ->
589 ncEffectExit from to reason
590 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
591 ncEffectGetInfo from pid
593 error $ "nodeController: unexpected message " ++ show unexpected
595-- [Unified: Table 10]
596ncEffectMonitor :: ProcessId -- ^ Who's watching?
597 -> Identifier -- ^ Who's being watched?
598 -> Maybe MonitorRef -- ^ 'Nothing' to link
600ncEffectMonitor from them mRef = do
603 if not (isLocal node them)
605 else isValidLocalIdentifier them
606 case (shouldLink, isLocal node (ProcessIdentifier from)) of
607 (True, _) -> -- [Unified: first rule]
609 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
610 Nothing -> modify' $ linksFor them ^: Set.insert from
611 (False, True) -> -- [Unified: second rule]
612 notifyDied from them DiedUnknownId mRef
613 (False, False) -> -- [Unified: third rule]
614 -- TODO: this is the right sender according to the Unified semantics,
615 -- but perhaps having 'them' as the sender would make more sense
616 -- (see also: notifyDied)
617 liftIO $ sendBinary node
618 (NodeIdentifier $ localNodeId node)
619 (NodeIdentifier $ processNodeId from)
620 WithImplicitReconnect
622 { ctrlMsgSender = NodeIdentifier (localNodeId node)
623 , ctrlMsgSignal = Died them DiedUnknownId
626-- [Unified: Table 11]
627ncEffectUnlink :: ProcessId -> Identifier -> NC ()
628ncEffectUnlink from them = do
630 when (isLocal node (ProcessIdentifier from)) $
632 ProcessIdentifier pid ->
633 postAsMessage from $ DidUnlinkProcess pid
634 NodeIdentifier nid ->
635 postAsMessage from $ DidUnlinkNode nid
636 SendPortIdentifier cid ->
637 postAsMessage from $ DidUnlinkPort cid
638 modify' $ linksFor them ^: Set.delete from
640-- [Unified: Table 11]
641ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
642ncEffectUnmonitor from ref = do
644 when (isLocal node (ProcessIdentifier from)) $
645 postAsMessage from $ DidUnmonitor ref
646 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
648-- [Unified: Table 12]
649ncEffectDied :: Identifier -> DiedReason -> NC ()
650ncEffectDied ident reason = do
652 traceNotifyDied node ident reason
653 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
654 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
656-- _registry :: !(Map (String,NodeId) ProcessId)
658 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
660 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
662 when (localOnly <= isLocal node (ProcessIdentifier us)) $
663 notifyDied us them reason Nothing
665 forM_ (Map.toList affectedMons) $ \(them, refs) ->
666 forM_ refs $ \(us, ref) ->
667 when (localOnly <= isLocal node (ProcessIdentifier us)) $
668 notifyDied us them reason (Just ref)
670 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
672 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
674 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
675 mapM (\(pid,nidlist) ->
676 case ident `impliesDeathOf` ProcessIdentifier pid of
678 do forM_ nidlist $ \(nid,_) ->
679 when (not $ isLocal node (NodeIdentifier nid))
680 (forwardNameDeath node nid)
682 False -> return $ Just (pid,nidlist) )
683 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
685 forwardNameDeath node nid =
686 liftIO $ sendBinary node
687 (NodeIdentifier $ localNodeId node)
688 (NodeIdentifier $ nid)
689 WithImplicitReconnect
691 { ctrlMsgSender = NodeIdentifier (localNodeId node)
692 , ctrlMsgSignal = Died ident reason
695-- [Unified: Table 13]
696ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
697ncEffectSpawn pid cProc ref = do
698 mProc <- unClosure cProc
699 -- If the closure does not exist, we spawn a process that throws an exception
700 -- This allows the remote node to find out what's happening
701 let proc = case mProc of
702 Left err -> fail $ "Error: Could not resolve closure: " ++ err
705 pid' <- liftIO $ forkProcess node proc
706 liftIO $ sendMessage node
707 (NodeIdentifier (localNodeId node))
708 (ProcessIdentifier pid)
709 WithImplicitReconnect
712-- Unified semantics does not explicitly describe how to implement 'register',
713-- but mentions it's "very similar to nsend" (Table 14)
714-- We send a response indicated if the operation is invalid
715ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
716ncEffectRegister from label atnode mPid reregistration = do
718 currentVal <- gets (^. registeredHereFor label)
721 Nothing -> -- unregister request
722 return $ isJust currentVal
723 Just thepid -> -- register request
724 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
725 return $ (isNothing currentVal /= reregistration) &&
726 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
727 if isLocal node (NodeIdentifier atnode)
728 then do when (isOk) $
729 do modify' $ registeredHereFor label ^= mPid
730 updateRemote node currentVal mPid
731 liftIO $ sendMessage node
732 (NodeIdentifier (localNodeId node))
733 (ProcessIdentifier from)
734 WithImplicitReconnect
735 (RegisterReply label isOk)
737 case reregistration of
739 False -> flip incList
742 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
743 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
744 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
745 forward node (processNodeId newval) (Register label atnode (Just newval) False)
746 updateRemote node Nothing (Just newval) =
747 forward node (processNodeId newval) (Register label atnode (Just newval) False)
748 updateRemote node (Just oldval) Nothing =
749 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
750 updateRemote _ _ _ = return ()
751 maybeify f Nothing = unmaybeify $ f []
752 maybeify f (Just x) = unmaybeify $ f x
753 unmaybeify [] = Nothing
754 unmaybeify x = Just x
755 incList [] tag = [(tag,1)]
756 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
757 incList (x:xs) tag = x : incList xs tag
759 decList ((atag,1):xs) tag | atag == tag = xs
760 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
761 decList (x:xs) tag = x:decList xs tag
762 forward node to reg =
763 when (not $ isLocal node (NodeIdentifier to)) $
764 liftIO $ sendBinary node
765 (ProcessIdentifier from)
767 WithImplicitReconnect
769 { ctrlMsgSender = ProcessIdentifier from
770 , ctrlMsgSignal = reg
774-- Unified semantics does not explicitly describe 'whereis'
775ncEffectWhereIs :: ProcessId -> String -> NC ()
776ncEffectWhereIs from label = do
778 mPid <- gets (^. registeredHereFor label)
779 liftIO $ sendMessage node
780 (NodeIdentifier (localNodeId node))
781 (ProcessIdentifier from)
782 WithImplicitReconnect
783 (WhereIsReply label mPid)
785-- [Unified: Table 14]
786ncEffectNamedSend :: Identifier -> String -> Message -> NC ()
787ncEffectNamedSend from label msg = do
789 mPid <- gets (^. registeredHereFor label)
790 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
792 liftIO $ sendPayload node
794 (ProcessIdentifier pid)
796 (messageToPayload msg)
799ncEffectLocalSend :: ProcessId -> Message -> NC ()
800ncEffectLocalSend = postMessage
803ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
804ncEffectLocalPortSend from msg =
805 let pid = sendPortProcessId from
806 cid = sendPortLocalId from
808 withLocalProc pid $ \proc -> do
809 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
811 -- in the unlikely event we know nothing about this channel id,
812 -- there's little to be done - perhaps some logging/tracing though...
814 Just (TypedChannel chan') -> do
815 -- If ch is Nothing, the process has given up the read end of
816 -- the channel and we simply ignore the incoming message - this
817 ch <- deRefWeak chan'
818 forM_ ch $ \chan -> deliverChan msg chan
819 where deliverChan :: forall a . Message -> TQueue a -> IO ()
820 deliverChan (UnencodedMessage _ raw) chan' =
821 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
822 deliverChan (EncodedMessage _ _) _ =
823 -- this will not happen unless someone screws with Primitives.hs
824 error "invalid local channel delivery"
827ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
828ncEffectKill from to reason = do
830 when (isLocal node (ProcessIdentifier to)) $
831 throwException to $ ProcessKillException from reason
834ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
835ncEffectExit from to reason = do
837 when (isLocal node (ProcessIdentifier to)) $
838 throwException to $ ProcessExitException from reason
841ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
842ncEffectGetInfo from pid =
843 let lpid = processLocalId pid
844 them = (ProcessIdentifier pid)
848 withMVar (localState node) $ return . (^. localProcessWithId lpid)
850 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
851 from node (ProcessInfoNone DiedUnknownId)
853 itsLinks <- gets (^. linksFor them)
854 itsMons <- gets (^. monitorsFor them)
855 registered <- gets (^. registeredHere)
857 let reg = registeredNames registered
858 dispatch (isLocal node (ProcessIdentifier from))
862 infoNode = (processNodeId pid)
863 , infoRegisteredNames = reg
864 -- we cannot populate this field
865 , infoMessageQueueLength = Nothing
866 , infoMonitors = Set.toList itsMons
867 , infoLinks = Set.toList itsLinks
869 where dispatch :: (Serializable a, Show a)
875 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
876 dispatch False dest node pInfo = do
877 liftIO $ sendMessage node
878 (NodeIdentifier (localNodeId node))
879 (ProcessIdentifier dest)
880 WithImplicitReconnect
883 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
887--------------------------------------------------------------------------------
889--------------------------------------------------------------------------------
891notifyDied :: ProcessId -- ^ Who to notify?
892 -> Identifier -- ^ Who died?
893 -> DiedReason -- ^ How did they die?
894 -> Maybe MonitorRef -- ^ 'Nothing' for linking
896notifyDied dest src reason mRef = do
898 case (isLocal node (ProcessIdentifier dest), mRef, src) of
899 (True, Just ref, ProcessIdentifier pid) ->
900 postAsMessage dest $ ProcessMonitorNotification ref pid reason
901 (True, Just ref, NodeIdentifier nid) ->
902 postAsMessage dest $ NodeMonitorNotification ref nid reason
903 (True, Just ref, SendPortIdentifier cid) ->
904 postAsMessage dest $ PortMonitorNotification ref cid reason
905 (True, Nothing, ProcessIdentifier pid) ->
906 throwException dest $ ProcessLinkException pid reason
907 (True, Nothing, NodeIdentifier pid) ->
908 throwException dest $ NodeLinkException pid reason
909 (True, Nothing, SendPortIdentifier pid) ->
910 throwException dest $ PortLinkException pid reason
912 -- The change in sender comes from [Unified: Table 10]
913 liftIO $ sendBinary node
914 (NodeIdentifier $ localNodeId node)
915 (NodeIdentifier $ processNodeId dest)
916 WithImplicitReconnect
918 { ctrlMsgSender = NodeIdentifier (localNodeId node)
919 , ctrlMsgSignal = Died src reason
922-- | [Unified: Table 8]
923destNid :: ProcessSignal -> Maybe NodeId
924destNid (Link ident) = Just $ nodeOf ident
925destNid (Unlink ident) = Just $ nodeOf ident
926destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
927destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
928destNid (Spawn _ _) = Nothing
929destNid (Register _ _ _ _) = Nothing
930destNid (WhereIs _) = Nothing
931destNid (NamedSend _ _) = Nothing
932-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
933-- then when a local process dies the monitoring/linking machinery will take
934-- care of notifying remote nodes
935destNid (Died _ _) = Nothing
936destNid (Kill pid _) = Just $ processNodeId pid
937destNid (Exit pid _) = Just $ processNodeId pid
938destNid (GetInfo pid) = Just $ processNodeId pid
939destNid (LocalSend pid _) = Just $ processNodeId pid
940destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
942-- | Check if a process is local to our own node
943isLocal :: LocalNode -> Identifier -> Bool
944isLocal nid ident = nodeOf ident == localNodeId nid
946-- | Lookup a local closure
947unClosure :: Typeable a => Closure a -> NC (Either String a)
948unClosure closure = do
949 rtable <- remoteTable <$> ask
950 return (Static.unclosure rtable closure)
952-- | Check if an identifier refers to a valid local object
953isValidLocalIdentifier :: Identifier -> NC Bool
954isValidLocalIdentifier ident = do
956 liftIO . withMVar (localState node) $ \nSt ->
958 NodeIdentifier nid ->
959 return $ nid == localNodeId node
960 ProcessIdentifier pid -> do
961 let mProc = nSt ^. localProcessWithId (processLocalId pid)
962 return $ isJust mProc
963 SendPortIdentifier cid -> do
964 let pid = sendPortProcessId cid
965 mProc = nSt ^. localProcessWithId (processLocalId pid)
967 Nothing -> return False
968 Just proc -> withMVar (processState proc) $ \pSt -> do
969 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
972--------------------------------------------------------------------------------
973-- Messages to local processes --
974--------------------------------------------------------------------------------
976postAsMessage :: Serializable a => ProcessId -> a -> NC ()
977postAsMessage pid = postMessage pid . createUnencodedMessage
979postMessage :: ProcessId -> Message -> NC ()
980postMessage pid msg = do
981 withLocalProc pid $ \p -> enqueue (processQueue p) msg
983throwException :: Exception e => ProcessId -> e -> NC ()
984throwException pid e = withLocalProc pid $ \p ->
985 throwTo (processThread p) e
987withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
988withLocalProc pid p = do
991 -- By [Unified: table 6, rule missing_process] messages to dead processes
992 -- can silently be dropped
993 let lpid = processLocalId pid
994 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
997--------------------------------------------------------------------------------
999--------------------------------------------------------------------------------
1001links :: Accessor NCState (Map Identifier (Set ProcessId))
1002links = accessor _links (\ls st -> st { _links = ls })
1004monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1005monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1007registeredHere :: Accessor NCState (Map String ProcessId)
1008registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1010registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1011registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1013linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1014linksFor ident = links >>> DAC.mapDefault Set.empty ident
1016monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1017monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1019registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1020registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1022registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1023registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1025-- | @splitNotif ident@ splits a notifications map into those
1026-- notifications that should trigger when 'ident' fails and those links that
1029-- There is a hierarchy between identifiers: failure of a node implies failure
1030-- of all processes on that node, and failure of a process implies failure of
1031-- all typed channels to that process. In other words, if 'ident' refers to a
1032-- node, then the /should trigger/ set will include
1034-- * the notifications for the node specifically
1035-- * the notifications for processes on that node, and
1036-- * the notifications for typed channels to processes on that node.
1038-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1041-- * the notifications for that process specifically and
1042-- * the notifications for typed channels to that process.
1044-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1045splitNotif :: Identifier
1047 -> (Map Identifier a, Map Identifier a)
1048splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1050--------------------------------------------------------------------------------
1052--------------------------------------------------------------------------------
1054-- | Modify and evaluate the state
1055modify' :: MonadState s m => (s -> s) -> m ()
1056modify' 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