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
150 , unsafeCreateUnencodedMessage
152 , firstNonReservedProcessId
153 , ImplicitReconnect(WithImplicitReconnect)
155import Control.Distributed.Process.Management.Agent
158import qualified Control.Distributed.Process.Management.Bus as Mx
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 wqRef mxNew')
263 , localTracer = tracer'
266startDefaultTracer :: LocalNode -> IO ()
267startDefaultTracer node' = do
268 let (Tracer pid _) = localTracer node'
269 runProcess node' $ register "trace.controller" pid
270 pid' <- forkProcess node' defaultTracer
271 enableTrace (localTracer node') pid'
272 runProcess node' $ register "tracer.initial" pid'
274-- | Start and register the service processes on a node
275startServiceProcesses :: LocalNode -> IO ()
276startServiceProcesses node = do
277 -- tracing /spawns/ relies on the tracer being enabled, but we start
278 -- the default tracer first, even though it might @nsend@ to the logger
279 -- before /that/ process has started - this is a totally harmless race
280 -- however, so we deliberably ignore it
281 startDefaultTracer node
282 logger <- forkProcess node loop
283 runProcess node $ register "logger" logger
287 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
288 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
290 , match $ \((time, string) :: (String, String)) -> do
291 -- this is a 'trace' message from the local node tracer
292 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
294 , match $ \(ch :: SendPort ()) -> -- a shutdown request
298-- | Force-close a local node
300-- TODO: for now we just close the associated endpoint
301closeLocalNode :: LocalNode -> IO ()
303 -- TODO: close all our processes, surely!?
304 NT.closeEndPoint (localEndPoint node)
306-- | Run a process on a local node and wait for it to finish
307runProcess :: LocalNode -> Process () -> IO ()
308runProcess node proc = do
311 void $ forkProcess node $ do
312 catch (proc `finally` liftIO (putMVar done ()))
313 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
316-- | Spawn a new process on a local node
317forkProcess :: LocalNode -> Process () -> IO ProcessId
318forkProcess node proc = modifyMVar (localState node) startProcess
320 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
322 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
323 , lpidUnique = st ^. localPidUnique
325 let pid = ProcessId { processNodeId = localNodeId node
326 , processLocalId = lpid
328 pst <- newMVar LocalProcessState { _monitorCounter = 0
330 , _channelCounter = 0
331 , _typedChannels = Map.empty
334 weakQueue <- mkWeakCQueue queue (return ())
335 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
336 let lproc = LocalProcess { processQueue = queue
337 , processWeakQ = weakQueue
340 , processThread = tid
344 reason <- Exception.catches
345 (runLocalProcess lproc proc >> return DiedNormal)
346 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
347 mMsg <- unwrapMessage msg :: IO (Maybe String)
349 Nothing -> return $ DiedException $ show ex
350 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
352 (return . DiedException . (show :: SomeException -> String)))]
354 -- [Unified: Table 4, rules termination and exiting]
355 modifyMVar_ (localState node) (cleanupProcess pid)
356 writeChan (localCtrlChan node) NCMsg
357 { ctrlMsgSender = ProcessIdentifier pid
358 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
362 -- see note [tracer/forkProcess races]
363 mxNotify node (MxSpawned pid)
365 if lpidCounter lpid == maxBound
367 -- TODO: this doesn't look right at all - how do we know
368 -- that newUnique represents a process id that is available!?
369 newUnique <- randomIO
370 return ( (localProcessWithId lpid ^= Just lproc)
371 . (localPidCounter ^= firstNonReservedProcessId)
372 . (localPidUnique ^= newUnique)
377 return ( (localProcessWithId lpid ^= Just lproc)
378 . (localPidCounter ^: (+ 1))
383 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
384 cleanupProcess pid st = do
385 let pid' = ProcessIdentifier pid
386 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
387 mapM_ (NT.close . fst) (Map.elems affected)
388 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
389 . (localConnections ^= unaffected)
392-- note [tracer/forkProcess races]
394-- Our startTracing function uses forkProcess to start the trace controller
395-- process, and of course forkProcess attempts to call traceEvent once the
396-- process has started. This is harmless, as the localEventBus is not updated
397-- until /after/ the initial forkProcess completes, so the first call to
398-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
401--------------------------------------------------------------------------------
402-- Handle incoming messages --
403--------------------------------------------------------------------------------
405type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
409 | ToProc ProcessId (Weak (CQueue Message))
410 | ToChan TypedChannel
413data ConnectionState = ConnectionState {
414 _incoming :: !(Map NT.ConnectionId IncomingConnection)
415 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
418initConnectionState :: ConnectionState
419initConnectionState = ConnectionState {
420 _incoming = Map.empty
421 , _incomingFrom = Map.empty
424incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
425incoming = accessor _incoming (\conns st -> st { _incoming = conns })
427incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
428incomingAt cid = incoming >>> DAC.mapMaybe cid
430incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
431incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
433 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
435handleIncomingMessages :: LocalNode -> IO ()
436handleIncomingMessages node = go initConnectionState
438 go :: ConnectionState -> IO ()
440 event <- NT.receive endpoint
442 NT.ConnectionOpened cid rel theirAddr ->
443 if rel == NT.ReliableOrdered
445 mxNotify node (MxConnected cid theirAddr)
447 (incomingAt cid ^= Just (theirAddr, Uninit))
448 . (incomingFrom theirAddr ^: Set.insert cid)
451 else invalidRequest cid st
452 NT.Received cid payload ->
453 case st ^. incomingAt cid of
454 Just (_, ToProc pid weakQueue) -> do
455 mQueue <- deRefWeak weakQueue
456 forM_ mQueue $ \queue -> do
457 -- TODO: if we find that the queue is Nothing, should we remove
458 -- it from the NC state? (and same for channels, below)
459 let msg = payloadToMessage payload
460 enqueue queue msg -- 'enqueue' is strict
461 mxNotify node (MxReceived pid msg)
463 Just (_, ToChan (TypedChannel chan')) -> do
464 mChan <- deRefWeak chan'
465 -- If mChan is Nothing, the process has given up the read end of
466 -- the channel and we simply ignore the incoming message
467 forM_ mChan $ \chan -> atomically $
468 -- We make sure the message is fully decoded when it is enqueued
469 writeTQueue chan $! decode (BSL.fromChunks payload)
471 Just (_, ToNode) -> do
472 let ctrlMsg = decode . BSL.fromChunks $ payload
473 writeChan ctrlChan $! ctrlMsg
475 Just (src, Uninit) ->
476 case decode (BSL.fromChunks payload) of
477 ProcessIdentifier pid -> do
478 let lpid = processLocalId pid
479 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
482 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
484 invalidRequest cid st
485 SendPortIdentifier chId -> do
486 let lcid = sendPortLocalId chId
487 lpid = processLocalId (sendPortProcessId chId)
488 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
491 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
494 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
496 invalidRequest cid st
498 invalidRequest cid st
499 NodeIdentifier nid ->
500 if nid == localNodeId node
501 then go (incomingAt cid ^= Just (src, ToNode) $ st)
502 else invalidRequest cid st
504 invalidRequest cid st
505 NT.ConnectionClosed cid -> do
506 putStrLn "connection closed..."
507 case st ^. incomingAt cid of
509 invalidRequest cid st
511 mxNotify node (MxDisconnected cid src)
512 go ( (incomingAt cid ^= Nothing)
513 . (incomingFrom src ^: Set.delete cid)
516 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
517 -- [Unified table 9, rule node_disconnect]
518 let nid = NodeIdentifier $ NodeId theirAddr
519 writeChan ctrlChan NCMsg
520 { ctrlMsgSender = nid
521 , ctrlMsgSignal = Died nid DiedDisconnect
523 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
524 closeImplicitReconnections node nid
525 go ( (incomingFrom theirAddr ^= Set.empty)
526 . (incoming ^: Map.filterWithKey (const . notLost))
529 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
530 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
531 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
532 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
535 NT.ReceivedMulticast _ _ ->
536 -- If we received a multicast message, something went horribly wrong
537 -- and we just give up
538 fail "Cloud Haskell fatal error: received unexpected multicast"
540 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
541 invalidRequest cid st = do
542 -- TODO: We should treat this as a fatal error on the part of the remote
543 -- node. That is, we should report the remote node as having died, and we
544 -- should close incoming connections (this requires a Transport layer
546 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
548 go ( incomingAt cid ^= Nothing
552 state = localState node
553 endpoint = localEndPoint node
554 ctrlChan = localCtrlChan node
556--------------------------------------------------------------------------------
557-- Top-level access to the node controller --
558--------------------------------------------------------------------------------
560runNodeController :: LocalNode -> IO ()
562 runReaderT (evalStateT (unNC nodeController) initNCState)
564--------------------------------------------------------------------------------
565-- Internal data types --
566--------------------------------------------------------------------------------
568data NCState = NCState
569 { -- Mapping from remote processes to linked local processes
570 _links :: !(Map Identifier (Set ProcessId))
571 -- Mapping from remote processes to monitoring local processes
572 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
573 -- Process registry: names and where they live, mapped to the PIDs
574 , _registeredHere :: !(Map String ProcessId)
575 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
578newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
579 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
581initNCState :: NCState
582initNCState = NCState { _links = Map.empty
583 , _monitors = Map.empty
584 , _registeredHere = Map.empty
585 , _registeredOnNodes = Map.empty
588-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
589-- type is deliberately not exported so it cannot be caught explicitly.
590data ProcessKillException =
591 ProcessKillException !ProcessId !String
594instance Exception ProcessKillException
595instance Show ProcessKillException where
596 show (ProcessKillException pid reason) =
597 "killed-by=" ++ show pid ++ ",reason=" ++ reason
599--------------------------------------------------------------------------------
600-- Tracing/Debugging --
601--------------------------------------------------------------------------------
603-- [Issue #104 / DP-13]
605traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
606traceNotifyDied node ident reason =
607 -- TODO: SendPortDied notifications
609 (NodeIdentifier nid) -> liftIO $ mxNotify node (MxNodeDied nid reason)
610 (ProcessIdentifier pid) -> liftIO $ mxNotify node (MxProcessDied pid reason)
613traceEventFmtIO :: LocalNode
617traceEventFmtIO LocalNode{..} fmt args = traceLogFmt localTracer fmt args
619mxNotify :: LocalNode -> MxEvent -> IO ()
620mxNotify LocalNode{..} ev =
621 Mx.publishEvent localEventBus $ unsafeCreateUnencodedMessage ev
623--------------------------------------------------------------------------------
624-- Core functionality --
625--------------------------------------------------------------------------------
628nodeController :: NC ()
632 msg <- liftIO $ readChan (localCtrlChan node)
634 -- [Unified: Table 7, rule nc_forward]
635 case destNid (ctrlMsgSignal msg) of
636 Just nid' | nid' /= localNodeId node ->
637 liftIO $ sendBinary node
639 (NodeIdentifier nid')
640 WithImplicitReconnect
646 NCMsg (ProcessIdentifier from) (Link them) ->
647 ncEffectMonitor from them Nothing
648 NCMsg (ProcessIdentifier from) (Monitor ref) ->
649 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
650 NCMsg (ProcessIdentifier from) (Unlink them) ->
651 ncEffectUnlink from them
652 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
653 ncEffectUnmonitor from ref
654 NCMsg _from (Died ident reason) ->
655 ncEffectDied ident reason
656 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
657 ncEffectSpawn from proc ref
658 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
659 ncEffectRegister from label atnode pid force
660 NCMsg (ProcessIdentifier from) (WhereIs label) ->
661 ncEffectWhereIs from label
662 NCMsg _ (NamedSend label msg') ->
663 ncEffectNamedSend label msg'
664 NCMsg _ (LocalSend to msg') ->
665 ncEffectLocalSend node to msg'
666 NCMsg _ (LocalPortSend to msg') ->
667 ncEffectLocalPortSend to msg'
668 NCMsg (ProcessIdentifier from) (Kill to reason) ->
669 ncEffectKill from to reason
670 NCMsg (ProcessIdentifier from) (Exit to reason) ->
671 ncEffectExit from to reason
672 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
673 ncEffectGetInfo from pid
674 NCMsg _ SigShutdown ->
676 NT.closeEndPoint (localEndPoint node)
677 `Exception.finally` throwIO ThreadKilled
678 -- ThreadKilled seems to make more sense than fail/error here
680 error $ "nodeController: unexpected message " ++ show unexpected
682-- [Unified: Table 10]
683ncEffectMonitor :: ProcessId -- ^ Who's watching?
684 -> Identifier -- ^ Who's being watched?
685 -> Maybe MonitorRef -- ^ 'Nothing' to link
687ncEffectMonitor from them mRef = do
690 if not (isLocal node them)
692 else isValidLocalIdentifier them
693 case (shouldLink, isLocal node (ProcessIdentifier from)) of
694 (True, _) -> -- [Unified: first rule]
696 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
697 Nothing -> modify' $ linksFor them ^: Set.insert from
698 (False, True) -> -- [Unified: second rule]
699 notifyDied from them DiedUnknownId mRef
700 (False, False) -> -- [Unified: third rule]
701 -- TODO: this is the right sender according to the Unified semantics,
702 -- but perhaps having 'them' as the sender would make more sense
703 -- (see also: notifyDied)
704 liftIO $ sendBinary node
705 (NodeIdentifier $ localNodeId node)
706 (NodeIdentifier $ processNodeId from)
707 WithImplicitReconnect
709 { ctrlMsgSender = NodeIdentifier (localNodeId node)
710 , ctrlMsgSignal = Died them DiedUnknownId
713-- [Unified: Table 11]
714ncEffectUnlink :: ProcessId -> Identifier -> NC ()
715ncEffectUnlink from them = do
717 when (isLocal node (ProcessIdentifier from)) $
719 ProcessIdentifier pid ->
720 postAsMessage from $ DidUnlinkProcess pid
721 NodeIdentifier nid ->
722 postAsMessage from $ DidUnlinkNode nid
723 SendPortIdentifier cid ->
724 postAsMessage from $ DidUnlinkPort cid
725 modify' $ linksFor them ^: Set.delete from
727-- [Unified: Table 11]
728ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
729ncEffectUnmonitor from ref = do
731 when (isLocal node (ProcessIdentifier from)) $
732 postAsMessage from $ DidUnmonitor ref
733 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
735-- [Unified: Table 12]
736ncEffectDied :: Identifier -> DiedReason -> NC ()
737ncEffectDied ident reason = do
739 traceNotifyDied node ident reason
740 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
741 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
743-- _registry :: !(Map (String,NodeId) ProcessId)
745 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
747 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
749 when (localOnly <= isLocal node (ProcessIdentifier us)) $
750 notifyDied us them reason Nothing
752 forM_ (Map.toList affectedMons) $ \(them, refs) ->
753 forM_ refs $ \(us, ref) ->
754 when (localOnly <= isLocal node (ProcessIdentifier us)) $
755 notifyDied us them reason (Just ref)
757 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
759 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
761 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
762 mapM (\(pid,nidlist) ->
763 case ident `impliesDeathOf` ProcessIdentifier pid of
765 do forM_ nidlist $ \(nid,_) ->
766 when (not $ isLocal node (NodeIdentifier nid))
767 (forwardNameDeath node nid)
769 False -> return $ Just (pid,nidlist) )
770 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
772 forwardNameDeath node nid =
773 liftIO $ sendBinary node
774 (NodeIdentifier $ localNodeId node)
775 (NodeIdentifier $ nid)
776 WithImplicitReconnect
778 { ctrlMsgSender = NodeIdentifier (localNodeId node)
779 , ctrlMsgSignal = Died ident reason
782-- [Unified: Table 13]
783ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
784ncEffectSpawn pid cProc ref = do
785 mProc <- unClosure cProc
786 -- If the closure does not exist, we spawn a process that throws an exception
787 -- This allows the remote node to find out what's happening
789 let proc = case mProc of
790 Left err -> fail $ "Error: Could not resolve closure: " ++ err
793 pid' <- liftIO $ forkProcess node proc
794 liftIO $ sendMessage node
795 (NodeIdentifier (localNodeId node))
796 (ProcessIdentifier pid)
797 WithImplicitReconnect
800-- Unified semantics does not explicitly describe how to implement 'register',
801-- but mentions it's "very similar to nsend" (Table 14)
802-- We send a response indicated if the operation is invalid
803ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
804ncEffectRegister from label atnode mPid reregistration = do
806 currentVal <- gets (^. registeredHereFor label)
809 Nothing -> -- unregister request
810 return $ isJust currentVal
811 Just thepid -> -- register request
812 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
813 return $ (isNothing currentVal /= reregistration) &&
814 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
815 if isLocal node (NodeIdentifier atnode)
816 then do when (isOk) $
817 do modify' $ registeredHereFor label ^= mPid
818 updateRemote node currentVal mPid
820 (Just p) -> liftIO $ mxNotify node (MxRegistered p label)
821 Nothing -> liftIO $ mxNotify node (MxUnRegistered (fromJust currentVal) label)
822 liftIO $ sendMessage node
823 (NodeIdentifier (localNodeId node))
824 (ProcessIdentifier from)
825 WithImplicitReconnect
826 (RegisterReply label isOk)
828 case reregistration of
830 False -> flip incList
833 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
834 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
835 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
836 forward node (processNodeId newval) (Register label atnode (Just newval) False)
837 updateRemote node Nothing (Just newval) =
838 forward node (processNodeId newval) (Register label atnode (Just newval) False)
839 updateRemote node (Just oldval) Nothing =
840 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
841 updateRemote _ _ _ = return ()
842 maybeify f Nothing = unmaybeify $ f []
843 maybeify f (Just x) = unmaybeify $ f x
845 unmaybeify [] = Nothing
846 unmaybeify x = Just x
847 incList [] tag = [(tag,1)]
848 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
849 incList (x:xs) tag = x : incList xs tag
851 decList ((atag,1):xs) tag | atag == tag = xs
852 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
853 decList (x:xs) tag = x:decList xs tag
854 forward node to reg =
855 when (not $ isLocal node (NodeIdentifier to)) $
856 liftIO $ sendBinary node
857 (ProcessIdentifier from)
859 WithImplicitReconnect
861 { ctrlMsgSender = ProcessIdentifier from
862 , ctrlMsgSignal = reg
866-- Unified semantics does not explicitly describe 'whereis'
867ncEffectWhereIs :: ProcessId -> String -> NC ()
868ncEffectWhereIs from label = do
870 mPid <- gets (^. registeredHereFor label)
871 liftIO $ sendMessage node
872 (NodeIdentifier (localNodeId node))
873 (ProcessIdentifier from)
874 WithImplicitReconnect
875 (WhereIsReply label mPid)
877-- [Unified: Table 14]
878ncEffectNamedSend :: String -> Message -> NC ()
879ncEffectNamedSend label msg = do
880 mPid <- gets (^. registeredHereFor label)
881 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
882 forM_ mPid $ \pid -> postMessage pid msg
885ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
886ncEffectLocalSend node to msg =
887 liftIO $ withLocalProc node to $ \p -> do
888 enqueue (processQueue p) msg
889 mxNotify node (MxReceived to msg)
892ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
893ncEffectLocalPortSend from msg = do
895 let pid = sendPortProcessId from
896 cid = sendPortLocalId from
897 liftIO $ withLocalProc node pid $ \proc -> do
898 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
900 -- in the unlikely event we know nothing about this channel id,
901 -- there's little to be done - perhaps some logging/tracing though...
903 Just (TypedChannel chan') -> do
904 -- If ch is Nothing, the process has given up the read end of
905 -- the channel and we simply ignore the incoming message - this
906 ch <- deRefWeak chan'
907 forM_ ch $ \chan -> deliverChan msg chan
908 where deliverChan :: forall a . Message -> TQueue a -> IO ()
909 deliverChan (UnencodedMessage _ raw) chan' =
910 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
911 deliverChan (EncodedMessage _ _) _ =
912 -- this will not happen unless someone screws with Primitives.hs
913 error "invalid local channel delivery"
916ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
917ncEffectKill from to reason = do
919 when (isLocal node (ProcessIdentifier to)) $
920 throwException to $ ProcessKillException from reason
923ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
924ncEffectExit from to reason = do
926 when (isLocal node (ProcessIdentifier to)) $
927 throwException to $ ProcessExitException from reason
930ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
931ncEffectGetInfo from pid =
932 let lpid = processLocalId pid
933 them = (ProcessIdentifier pid)
937 withMVar (localState node) $ return . (^. localProcessWithId lpid)
939 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
940 from node (ProcessInfoNone DiedUnknownId)
942 itsLinks <- gets (^. linksFor them)
943 itsMons <- gets (^. monitorsFor them)
944 registered <- gets (^. registeredHere)
946 let reg = registeredNames registered
947 dispatch (isLocal node (ProcessIdentifier from))
951 infoNode = (processNodeId pid)
952 , infoRegisteredNames = reg
953 -- we cannot populate this field
954 , infoMessageQueueLength = Nothing
955 , infoMonitors = Set.toList itsMons
956 , infoLinks = Set.toList itsLinks
958 where dispatch :: (Serializable a, Show a)
964 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
965 dispatch False dest node pInfo = do
966 liftIO $ sendMessage node
967 (NodeIdentifier (localNodeId node))
968 (ProcessIdentifier dest)
969 WithImplicitReconnect
972 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
976--------------------------------------------------------------------------------
978--------------------------------------------------------------------------------
980notifyDied :: ProcessId -- ^ Who to notify?
981 -> Identifier -- ^ Who died?
982 -> DiedReason -- ^ How did they die?
983 -> Maybe MonitorRef -- ^ 'Nothing' for linking
985notifyDied dest src reason mRef = do
987 case (isLocal node (ProcessIdentifier dest), mRef, src) of
988 (True, Just ref, ProcessIdentifier pid) ->
989 postAsMessage dest $ ProcessMonitorNotification ref pid reason
990 (True, Just ref, NodeIdentifier nid) ->
991 postAsMessage dest $ NodeMonitorNotification ref nid reason
992 (True, Just ref, SendPortIdentifier cid) ->
993 postAsMessage dest $ PortMonitorNotification ref cid reason
994 (True, Nothing, ProcessIdentifier pid) ->
995 throwException dest $ ProcessLinkException pid reason
996 (True, Nothing, NodeIdentifier pid) ->
997 throwException dest $ NodeLinkException pid reason
998 (True, Nothing, SendPortIdentifier pid) ->
999 throwException dest $ PortLinkException pid reason
1001 -- The change in sender comes from [Unified: Table 10]
1002 liftIO $ sendBinary node
1003 (NodeIdentifier $ localNodeId node)
1004 (NodeIdentifier $ processNodeId dest)
1005 WithImplicitReconnect
1007 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1008 , ctrlMsgSignal = Died src reason
1011-- | [Unified: Table 8]
1012destNid :: ProcessSignal -> Maybe NodeId
1013destNid (Link ident) = Just $ nodeOf ident
1014destNid (Unlink ident) = Just $ nodeOf ident
1015destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1016destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1017destNid (Spawn _ _) = Nothing
1018destNid (Register _ _ _ _) = Nothing
1019destNid (WhereIs _) = Nothing
1020destNid (NamedSend _ _) = Nothing
1021-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1022-- then when a local process dies the monitoring/linking machinery will take
1023-- care of notifying remote nodes
1024destNid (Died _ _) = Nothing
1025destNid (Kill pid _) = Just $ processNodeId pid
1026destNid (Exit pid _) = Just $ processNodeId pid
1027destNid (GetInfo pid) = Just $ processNodeId pid
1028destNid (LocalSend pid _) = Just $ processNodeId pid
1029destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1030destNid (SigShutdown) = Nothing
1032-- | Check if a process is local to our own node
1033isLocal :: LocalNode -> Identifier -> Bool
1034isLocal nid ident = nodeOf ident == localNodeId nid
1036-- | Lookup a local closure
1037unClosure :: Typeable a => Closure a -> NC (Either String a)
1038unClosure closure = do
1039 rtable <- remoteTable <$> ask
1040 return (Static.unclosure rtable closure)
1042-- | Check if an identifier refers to a valid local object
1043isValidLocalIdentifier :: Identifier -> NC Bool
1044isValidLocalIdentifier ident = do
1046 liftIO . withMVar (localState node) $ \nSt ->
1048 NodeIdentifier nid ->
1049 return $ nid == localNodeId node
1050 ProcessIdentifier pid -> do
1051 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1052 return $ isJust mProc
1053 SendPortIdentifier cid -> do
1054 let pid = sendPortProcessId cid
1055 mProc = nSt ^. localProcessWithId (processLocalId pid)
1057 Nothing -> return False
1058 Just proc -> withMVar (processState proc) $ \pSt -> do
1059 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1062--------------------------------------------------------------------------------
1063-- Messages to local processes --
1064--------------------------------------------------------------------------------
1066postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1067postAsMessage pid = postMessage pid . createUnencodedMessage
1069postMessage :: ProcessId -> Message -> NC ()
1070postMessage pid msg = do
1072 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1074throwException :: Exception e => ProcessId -> e -> NC ()
1075throwException pid e = do
1077 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1079withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1080withLocalProc node pid p =
1081 -- By [Unified: table 6, rule missing_process] messages to dead processes
1082 -- can silently be dropped
1083 let lpid = processLocalId pid in do
1084 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1087--------------------------------------------------------------------------------
1089--------------------------------------------------------------------------------
1091links :: Accessor NCState (Map Identifier (Set ProcessId))
1092links = accessor _links (\ls st -> st { _links = ls })
1094monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1095monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1097registeredHere :: Accessor NCState (Map String ProcessId)
1098registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1100registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1101registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1103linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1104linksFor ident = links >>> DAC.mapDefault Set.empty ident
1106monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1107monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1109registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1110registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1112registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1113registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1115-- | @splitNotif ident@ splits a notifications map into those
1116-- notifications that should trigger when 'ident' fails and those links that
1119-- There is a hierarchy between identifiers: failure of a node implies failure
1120-- of all processes on that node, and failure of a process implies failure of
1121-- all typed channels to that process. In other words, if 'ident' refers to a
1122-- node, then the /should trigger/ set will include
1124-- * the notifications for the node specifically
1125-- * the notifications for processes on that node, and
1126-- * the notifications for typed channels to processes on that node.
1128-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1131-- * the notifications for that process specifically and
1132-- * the notifications for typed channels to that process.
1134-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1135splitNotif :: Identifier
1137 -> (Map Identifier a, Map Identifier a)
1138splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1140--------------------------------------------------------------------------------
1142--------------------------------------------------------------------------------
1144-- | Modify and evaluate the state
1145modify' :: MonadState s m => (s -> s) -> m ()
1146modify' 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