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, fromJust, 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
54 , AsyncException(ThreadKilled)
59import qualified Control.Exception as Exception (catch, finally)
60import Control.Concurrent (forkIO)
61import Control.Distributed.Process.Internal.StrictMVar
70import Control.Concurrent.Chan (newChan, writeChan, readChan)
71import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
72import Control.Concurrent.STM
75import Control.Distributed.Process.Internal.CQueue
81import qualified Network.Transport as NT
95 , Reliability(ReliableOrdered)
97import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
98import System.Random (randomIO)
99import Control.Distributed.Static (RemoteTable, Closure)
100import qualified Control.Distributed.Static as Static
104import Control.Distributed.Process.Internal.Types
112 , LocalProcessState(..)
123 , ProcessMonitorNotification(..)
124 , NodeMonitorNotification(..)
125 , PortMonitorNotification(..)
126 , ProcessExitException(..)
127 , ProcessLinkException(..)
128 , NodeLinkException(..)
129 , PortLinkException(..)
131 , DidUnlinkProcess(..)
141 , ProcessInfoNone(..)
147 , createUnencodedMessage
149 , firstNonReservedProcessId
150 , ImplicitReconnect(WithImplicitReconnect)
152import qualified Control.Distributed.Process.Internal.Trace.Remote as Trace
155import Control.Distributed.Process.Internal.Trace.Types
162import Control.Distributed.Process.Internal.Trace.Tracer
166import Control.Distributed.Process.Serializable (Serializable)
167import Control.Distributed.Process.Internal.Messaging
170 , closeImplicitReconnections
173import Control.Distributed.Process.Internal.Primitives
180import Control.Distributed.Process.Internal.Types (SendPort)
181import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
182import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
183import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
187import System.Environment (getEnv)
190--------------------------------------------------------------------------------
192--------------------------------------------------------------------------------
194initRemoteTable :: RemoteTable
195initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
197-- | Initialize a new local node.
198newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
199newLocalNode transport rtable = do
200 mEndPoint <- NT.newEndPoint transport
202 Left ex -> throwIO ex
204 localNode <- createBareLocalNode endPoint rtable
205 startServiceProcesses localNode
208-- | Create a new local node (without any service processes running)
209createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
210createBareLocalNode endPoint rtable = do
212 state <- newMVar LocalNodeState
213 { _localProcesses = Map.empty
214 , _localPidCounter = firstNonReservedProcessId
215 , _localPidUnique = unq
216 , _localConnections = Map.empty
219 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
220 , localEndPoint = endPoint
222 , localCtrlChan = ctrlChan
223 , localTracer = InactiveTracer
224 , remoteTable = rtable
226 tracedNode <- startTracing node
228 -- Once the NC terminates, the endpoint isn't much use,
229 void $ forkIO $ Exception.finally (runNodeController tracedNode)
230 (NT.closeEndPoint (localEndPoint node))
232 -- whilst a closed/failing endpoint will terminate the NC
233 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
239 writeChan (localCtrlChan node) NCMsg
240 { ctrlMsgSender = NodeIdentifier (localNodeId node)
241 , ctrlMsgSignal = SigShutdown
244startTracing :: LocalNode -> IO LocalNode
246 -- TODO: use tryGetEnv once we drop support for GHC 7.2.x
248 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
249 (\(_ :: IOError) -> return node)
250 where procTracer :: LocalNode -> IO LocalNode
252 -- see note [tracer/forkProcess races]
253 mv <- MVar.newEmptyMVar
254 pid <- forkProcess n (traceController mv)
255 wqRef <- MVar.takeMVar mv
256 return n { localTracer = (ActiveTracer pid wqRef) }
258startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
259startDefaultTracer node =
261 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
262 (\(_ :: IOError) -> return Nothing)
263 where go :: LocalNode -> IO (Maybe ProcessId)
265 registerTraceController node'
266 pid <- forkProcess node' defaultTracer
267 enableTrace (localTracer node') pid
270 registerTraceController :: LocalNode -> IO ()
271 registerTraceController n =
272 let t = localTracer n in
274 InactiveTracer -> return ()
275 (ActiveTracer pid _) -> runProcess n $ register "trace.controller" pid
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 mPid <- startDefaultTracer node
287 Just pid -> runProcess node $ register "tracer.initial" pid
289 logger <- forkProcess node loop
290 runProcess node $ register "logger" logger
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
317 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
320-- | Spawn a new process on a local node
321forkProcess :: LocalNode -> Process () -> IO ProcessId
322forkProcess node proc = modifyMVar (localState node) startProcess
324 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
326 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
327 , lpidUnique = st ^. localPidUnique
329 let pid = ProcessId { processNodeId = localNodeId node
330 , processLocalId = lpid
332 pst <- newMVar LocalProcessState { _monitorCounter = 0
334 , _channelCounter = 0
335 , _typedChannels = Map.empty
338 weakQueue <- mkWeakCQueue queue (return ())
339 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
340 let lproc = LocalProcess { processQueue = queue
341 , processWeakQ = weakQueue
344 , processThread = tid
348 reason <- Exception.catch
349 (runLocalProcess lproc proc >> return DiedNormal)
350 (return . DiedException . (show :: SomeException -> String))
352 -- [Unified: Table 4, rules termination and exiting]
353 modifyMVar_ (localState node) (cleanupProcess pid)
354 writeChan (localCtrlChan node) NCMsg
355 { ctrlMsgSender = ProcessIdentifier pid
356 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
361 -- see note [tracer/forkProcess races]
362 trace node (TraceEvSpawned pid)
364 if lpidCounter lpid == maxBound
366 newUnique <- randomIO
367 return ( (localProcessWithId lpid ^= Just lproc)
368 . (localPidCounter ^= firstNonReservedProcessId)
369 . (localPidUnique ^= newUnique)
374 return ( (localProcessWithId lpid ^= Just lproc)
375 . (localPidCounter ^: (+ 1))
380 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
381 cleanupProcess pid st = do
382 let pid' = ProcessIdentifier pid
383 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
384 mapM_ (NT.close . fst) (Map.elems affected)
385 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
386 . (localConnections ^= unaffected)
389-- note [tracer/forkProcess races]
391-- Our startTracing function uses forkProcess to start the trace controller
392-- process, and of course forkProcess attempts to call traceEvent once the
393-- process has started. This is harmless, as the localTracer is not updated
394-- until /after/ the initial forkProcess completes, so the first call to
395-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
398--------------------------------------------------------------------------------
399-- Handle incoming messages --
400--------------------------------------------------------------------------------
402type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
406 | ToProc ProcessId (Weak (CQueue Message))
407 | ToChan TypedChannel
410data ConnectionState = ConnectionState {
411 _incoming :: !(Map NT.ConnectionId IncomingConnection)
412 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
415initConnectionState :: ConnectionState
416initConnectionState = ConnectionState {
417 _incoming = Map.empty
418 , _incomingFrom = Map.empty
421incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
422incoming = accessor _incoming (\conns st -> st { _incoming = conns })
424incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
425incomingAt cid = incoming >>> DAC.mapMaybe cid
427incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
428incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
430 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
432handleIncomingMessages :: LocalNode -> IO ()
433handleIncomingMessages node = go initConnectionState
435 go :: ConnectionState -> IO ()
437 event <- NT.receive endpoint
439 NT.ConnectionOpened cid rel theirAddr ->
440 if rel == NT.ReliableOrdered
442 trace node (TraceEvConnected cid)
444 (incomingAt cid ^= Just (theirAddr, Uninit))
445 . (incomingFrom theirAddr ^: Set.insert cid)
448 else invalidRequest cid st
449 NT.Received cid payload ->
450 case st ^. incomingAt cid of
451 Just (_, ToProc pid weakQueue) -> do
452 mQueue <- deRefWeak weakQueue
453 forM_ mQueue $ \queue -> do
454 -- TODO: if we find that the queue is Nothing, should we remove
455 -- it from the NC state? (and same for channels, below)
456 let msg = payloadToMessage payload
457 enqueue queue msg -- 'enqueue' is strict
458 trace node (TraceEvReceived pid msg)
460 Just (_, ToChan (TypedChannel chan')) -> do
461 mChan <- deRefWeak chan'
462 -- If mChan is Nothing, the process has given up the read end of
463 -- the channel and we simply ignore the incoming message
464 forM_ mChan $ \chan -> atomically $
465 -- We make sure the message is fully decoded when it is enqueued
466 writeTQueue chan $! decode (BSL.fromChunks payload)
468 Just (_, ToNode) -> do
469 let ctrlMsg = decode . BSL.fromChunks $ payload
470 writeChan ctrlChan $! ctrlMsg
472 Just (src, Uninit) ->
473 case decode (BSL.fromChunks payload) of
474 ProcessIdentifier pid -> do
475 let lpid = processLocalId pid
476 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
479 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
481 invalidRequest cid st
482 SendPortIdentifier chId -> do
483 let lcid = sendPortLocalId chId
484 lpid = processLocalId (sendPortProcessId chId)
485 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
488 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
491 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
493 invalidRequest cid st
495 invalidRequest cid st
496 NodeIdentifier nid ->
497 if nid == localNodeId node
498 then go (incomingAt cid ^= Just (src, ToNode) $ st)
499 else invalidRequest cid st
501 invalidRequest cid st
502 NT.ConnectionClosed cid ->
503 case st ^. incomingAt cid of
505 invalidRequest cid st
507 trace node (TraceEvDisconnected cid)
508 go ( (incomingAt cid ^= Nothing)
509 . (incomingFrom src ^: Set.delete cid)
512 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
513 -- [Unified table 9, rule node_disconnect]
514 let nid = NodeIdentifier $ NodeId theirAddr
515 writeChan ctrlChan NCMsg
516 { ctrlMsgSender = nid
517 , ctrlMsgSignal = Died nid DiedDisconnect
519 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
520 closeImplicitReconnections node nid
521 go ( (incomingFrom theirAddr ^= Set.empty)
522 . (incoming ^: Map.filterWithKey (const . notLost))
525 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
526 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
527 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
528 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
531 NT.ReceivedMulticast _ _ ->
532 -- If we received a multicast message, something went horribly wrong
533 -- and we just give up
534 fail "Cloud Haskell fatal error: received unexpected multicast"
536 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
537 invalidRequest cid st = do
538 -- TODO: We should treat this as a fatal error on the part of the remote
539 -- node. That is, we should report the remote node as having died, and we
540 -- should close incoming connections (this requires a Transport layer
542 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
544 go ( incomingAt cid ^= Nothing
548 state = localState node
549 endpoint = localEndPoint node
550 ctrlChan = localCtrlChan node
552--------------------------------------------------------------------------------
553-- Top-level access to the node controller --
554--------------------------------------------------------------------------------
556runNodeController :: LocalNode -> IO ()
558 runReaderT (evalStateT (unNC nodeController) initNCState)
560--------------------------------------------------------------------------------
561-- Internal data types --
562--------------------------------------------------------------------------------
564data NCState = NCState
565 { -- Mapping from remote processes to linked local processes
566 _links :: !(Map Identifier (Set ProcessId))
567 -- Mapping from remote processes to monitoring local processes
568 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
569 -- Process registry: names and where they live, mapped to the PIDs
570 , _registeredHere :: !(Map String ProcessId)
571 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
574newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
575 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
577initNCState :: NCState
578initNCState = NCState { _links = Map.empty
579 , _monitors = Map.empty
580 , _registeredHere = Map.empty
581 , _registeredOnNodes = Map.empty
584-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
585-- type is deliberately not exported so it cannot be caught explicitly.
586data ProcessKillException =
587 ProcessKillException !ProcessId !String
590instance Exception ProcessKillException
591instance Show ProcessKillException where
592 show (ProcessKillException pid reason) =
593 "killed-by=" ++ show pid ++ ",reason=" ++ reason
595--------------------------------------------------------------------------------
596-- Tracing/Debugging --
597--------------------------------------------------------------------------------
601traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
602traceNotifyDied node ident reason =
603 -- TODO: sendPortDied notifications
604 liftIO $ withLocalTracer node $ \t ->
606 (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
607 (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
610traceEventFmtIO :: LocalNode
614traceEventFmtIO node fmt args =
615 withLocalTracer node $ \t -> traceLogFmt t fmt args
617trace :: LocalNode -> TraceEvent -> IO ()
618trace node ev = withLocalTracer node $ \t -> traceEvent t ev
620withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
621withLocalTracer node act = act (localTracer node)
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 $ trace node (TraceEvRegistered p label)
821 Nothing -> liftIO $ trace node (TraceEvUnRegistered (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 trace node (TraceEvReceived 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