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
161import Control.Distributed.Process.Internal.Trace.Tracer
165import Control.Distributed.Process.Serializable (Serializable)
166import Control.Distributed.Process.Internal.Messaging
169 , closeImplicitReconnections
172import Control.Distributed.Process.Internal.Primitives
179import Control.Distributed.Process.Internal.Types (SendPort)
180import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
181import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
182import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
186import System.Environment (getEnv)
189--------------------------------------------------------------------------------
191--------------------------------------------------------------------------------
193initRemoteTable :: RemoteTable
194initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
196-- | Initialize a new local node.
197newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
198newLocalNode transport rtable = do
199 mEndPoint <- NT.newEndPoint transport
201 Left ex -> throwIO ex
203 localNode <- createBareLocalNode endPoint rtable
204 startServiceProcesses localNode
207-- | Create a new local node (without any service processes running)
208createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
209createBareLocalNode endPoint rtable = do
211 state <- newMVar LocalNodeState
212 { _localProcesses = Map.empty
213 , _localPidCounter = firstNonReservedProcessId
214 , _localPidUnique = unq
215 , _localConnections = Map.empty
218 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
219 , localEndPoint = endPoint
221 , localCtrlChan = ctrlChan
222 , localTracer = InactiveTracer
223 , remoteTable = rtable
225 tracedNode <- startTracing node
227 -- Once the NC terminates, the endpoint isn't much use,
228 void $ forkIO $ Exception.finally (runNodeController tracedNode)
229 (NT.closeEndPoint (localEndPoint node))
231 -- whilst a closed/failing endpoint will terminate the NC
232 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
238 writeChan (localCtrlChan node) NCMsg
239 { ctrlMsgSender = NodeIdentifier (localNodeId node)
240 , ctrlMsgSignal = SigShutdown
243startTracing :: LocalNode -> IO LocalNode
245 -- TODO: use tryGetEnv once we drop support for GHC 7.2.x
247 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
248 (\(_ :: IOError) -> return node)
249 where procTracer :: LocalNode -> IO LocalNode
251 -- see note [tracer/forkProcess races]
252 mv <- MVar.newEmptyMVar
253 pid <- forkProcess n (traceController mv)
254 wqRef <- MVar.takeMVar mv
255 return n { localTracer = (ActiveTracer pid wqRef) }
257startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
258startDefaultTracer node =
260 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
261 (\(_ :: IOError) -> return Nothing)
262 where go :: LocalNode -> IO (Maybe ProcessId)
264 registerTraceController node'
265 pid <- forkProcess node' defaultTracer
268 registerTraceController :: LocalNode -> IO ()
269 registerTraceController n =
270 let t = localTracer n in
272 InactiveTracer -> return ()
273 (ActiveTracer pid _) -> runProcess n $ register "trace.controller" pid
275-- | Start and register the service processes on a node
276startServiceProcesses :: LocalNode -> IO ()
277startServiceProcesses node = do
278 -- tracing /spawns/ relies on the tracer being enabled, but we start
279 -- the default tracer first, even though it might @nsend@ to the logger
280 -- before /that/ process has started - this is a totally harmless race
281 -- however, so we deliberably ignore it
282 mPid <- startDefaultTracer node
285 Just pid -> runProcess node $ register "tracer.initial" pid
287 logger <- forkProcess node loop
288 runProcess node $ register "logger" logger
292 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
293 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
295 , match $ \((time, string) :: (String, String)) -> do
296 -- this is a 'trace' message from the local node tracer
297 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
299 , match $ \(ch :: SendPort ()) -> -- a shutdown request
303-- | Force-close a local node
305-- TODO: for now we just close the associated endpoint
306closeLocalNode :: LocalNode -> IO ()
308 -- TODO: close all our processes, surely!?
309 NT.closeEndPoint (localEndPoint node)
311-- | Run a process on a local node and wait for it to finish
312runProcess :: LocalNode -> Process () -> IO ()
313runProcess node proc = do
315 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
318-- | Spawn a new process on a local node
319forkProcess :: LocalNode -> Process () -> IO ProcessId
320forkProcess node proc = modifyMVar (localState node) startProcess
322 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
324 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
325 , lpidUnique = st ^. localPidUnique
327 let pid = ProcessId { processNodeId = localNodeId node
328 , processLocalId = lpid
330 pst <- newMVar LocalProcessState { _monitorCounter = 0
332 , _channelCounter = 0
333 , _typedChannels = Map.empty
336 weakQueue <- mkWeakCQueue queue (return ())
337 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
338 let lproc = LocalProcess { processQueue = queue
339 , processWeakQ = weakQueue
342 , processThread = tid
346 reason <- Exception.catch
347 (runLocalProcess lproc proc >> return DiedNormal)
348 (return . DiedException . (show :: SomeException -> String))
350 -- [Unified: Table 4, rules termination and exiting]
351 modifyMVar_ (localState node) (cleanupProcess pid)
352 writeChan (localCtrlChan node) NCMsg
353 { ctrlMsgSender = ProcessIdentifier pid
354 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
359 -- see note [tracer/forkProcess races]
360 trace node (TraceEvSpawned pid)
362 if lpidCounter lpid == maxBound
364 newUnique <- randomIO
365 return ( (localProcessWithId lpid ^= Just lproc)
366 . (localPidCounter ^= firstNonReservedProcessId)
367 . (localPidUnique ^= newUnique)
372 return ( (localProcessWithId lpid ^= Just lproc)
373 . (localPidCounter ^: (+ 1))
378 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
379 cleanupProcess pid st = do
380 let pid' = ProcessIdentifier pid
381 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
382 mapM_ (NT.close . fst) (Map.elems affected)
383 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
384 . (localConnections ^= unaffected)
387-- note [tracer/forkProcess races]
389-- Our startTracing function uses forkProcess to start the trace controller
390-- process, and of course forkProcess attempts to call traceEvent once the
391-- process has started. This is harmless, as the localTracer is not updated
392-- until /after/ the initial forkProcess completes, so the first call to
393-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
396--------------------------------------------------------------------------------
397-- Handle incoming messages --
398--------------------------------------------------------------------------------
400type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
404 | ToProc ProcessId (Weak (CQueue Message))
405 | ToChan TypedChannel
408data ConnectionState = ConnectionState {
409 _incoming :: !(Map NT.ConnectionId IncomingConnection)
410 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
413initConnectionState :: ConnectionState
414initConnectionState = ConnectionState {
415 _incoming = Map.empty
416 , _incomingFrom = Map.empty
419incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
420incoming = accessor _incoming (\conns st -> st { _incoming = conns })
422incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
423incomingAt cid = incoming >>> DAC.mapMaybe cid
425incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
426incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
428 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
430handleIncomingMessages :: LocalNode -> IO ()
431handleIncomingMessages node = go initConnectionState
433 go :: ConnectionState -> IO ()
435 event <- NT.receive endpoint
437 NT.ConnectionOpened cid rel theirAddr ->
438 if rel == NT.ReliableOrdered
440 trace node (TraceEvConnected cid)
442 (incomingAt cid ^= Just (theirAddr, Uninit))
443 . (incomingFrom theirAddr ^: Set.insert cid)
446 else invalidRequest cid st
447 NT.Received cid payload ->
448 case st ^. incomingAt cid of
449 Just (_, ToProc pid weakQueue) -> do
450 mQueue <- deRefWeak weakQueue
451 forM_ mQueue $ \queue -> do
452 -- TODO: if we find that the queue is Nothing, should we remove
453 -- it from the NC state? (and same for channels, below)
454 let msg = payloadToMessage payload
455 enqueue queue msg -- 'enqueue' is strict
456 trace node (TraceEvReceived pid msg)
458 Just (_, ToChan (TypedChannel chan')) -> do
459 mChan <- deRefWeak chan'
460 -- If mChan is Nothing, the process has given up the read end of
461 -- the channel and we simply ignore the incoming message
462 forM_ mChan $ \chan -> atomically $
463 -- We make sure the message is fully decoded when it is enqueued
464 writeTQueue chan $! decode (BSL.fromChunks payload)
466 Just (_, ToNode) -> do
467 let ctrlMsg = decode . BSL.fromChunks $ payload
468 writeChan ctrlChan $! ctrlMsg
470 Just (src, Uninit) ->
471 case decode (BSL.fromChunks payload) of
472 ProcessIdentifier pid -> do
473 let lpid = processLocalId pid
474 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
477 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
479 invalidRequest cid st
480 SendPortIdentifier chId -> do
481 let lcid = sendPortLocalId chId
482 lpid = processLocalId (sendPortProcessId chId)
483 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
486 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
489 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
491 invalidRequest cid st
493 invalidRequest cid st
494 NodeIdentifier nid ->
495 if nid == localNodeId node
496 then go (incomingAt cid ^= Just (src, ToNode) $ st)
497 else invalidRequest cid st
499 invalidRequest cid st
500 NT.ConnectionClosed cid ->
501 case st ^. incomingAt cid of
503 invalidRequest cid st
505 trace node (TraceEvDisconnected cid)
506 go ( (incomingAt cid ^= Nothing)
507 . (incomingFrom src ^: Set.delete cid)
510 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
511 -- [Unified table 9, rule node_disconnect]
512 let nid = NodeIdentifier $ NodeId theirAddr
513 writeChan ctrlChan NCMsg
514 { ctrlMsgSender = nid
515 , ctrlMsgSignal = Died nid DiedDisconnect
517 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
518 closeImplicitReconnections node nid
519 go ( (incomingFrom theirAddr ^= Set.empty)
520 . (incoming ^: Map.filterWithKey (const . notLost))
523 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
524 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
525 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
526 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
529 NT.ReceivedMulticast _ _ ->
530 -- If we received a multicast message, something went horribly wrong
531 -- and we just give up
532 fail "Cloud Haskell fatal error: received unexpected multicast"
534 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
535 invalidRequest cid st = do
536 -- TODO: We should treat this as a fatal error on the part of the remote
537 -- node. That is, we should report the remote node as having died, and we
538 -- should close incoming connections (this requires a Transport layer
540 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
542 go ( incomingAt cid ^= Nothing
546 state = localState node
547 endpoint = localEndPoint node
548 ctrlChan = localCtrlChan node
550--------------------------------------------------------------------------------
551-- Top-level access to the node controller --
552--------------------------------------------------------------------------------
554runNodeController :: LocalNode -> IO ()
556 runReaderT (evalStateT (unNC nodeController) initNCState)
558--------------------------------------------------------------------------------
559-- Internal data types --
560--------------------------------------------------------------------------------
562data NCState = NCState
563 { -- Mapping from remote processes to linked local processes
564 _links :: !(Map Identifier (Set ProcessId))
565 -- Mapping from remote processes to monitoring local processes
566 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
567 -- Process registry: names and where they live, mapped to the PIDs
568 , _registeredHere :: !(Map String ProcessId)
569 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
572newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
573 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
575initNCState :: NCState
576initNCState = NCState { _links = Map.empty
577 , _monitors = Map.empty
578 , _registeredHere = Map.empty
579 , _registeredOnNodes = Map.empty
582-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
583-- type is deliberately not exported so it cannot be caught explicitly.
584data ProcessKillException =
585 ProcessKillException !ProcessId !String
588instance Exception ProcessKillException
589instance Show ProcessKillException where
590 show (ProcessKillException pid reason) =
591 "killed-by=" ++ show pid ++ ",reason=" ++ reason
593--------------------------------------------------------------------------------
594-- Tracing/Debugging --
595--------------------------------------------------------------------------------
599traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
600traceNotifyDied node ident reason =
601 -- TODO: sendPortDied notifications
602 liftIO $ withLocalTracer node $ \t ->
604 (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
605 (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
608traceEventFmtIO :: LocalNode
612traceEventFmtIO node fmt args =
613 withLocalTracer node $ \t -> traceLogFmt t fmt args
615trace :: LocalNode -> TraceEvent -> IO ()
616trace node ev = withLocalTracer node $ \t -> traceEvent t ev
618withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
619withLocalTracer node act = act (localTracer node)
621--------------------------------------------------------------------------------
622-- Core functionality --
623--------------------------------------------------------------------------------
626nodeController :: NC ()
630 msg <- liftIO $ readChan (localCtrlChan node)
632 -- [Unified: Table 7, rule nc_forward]
633 case destNid (ctrlMsgSignal msg) of
634 Just nid' | nid' /= localNodeId node ->
635 liftIO $ sendBinary node
637 (NodeIdentifier nid')
638 WithImplicitReconnect
644 NCMsg (ProcessIdentifier from) (Link them) ->
645 ncEffectMonitor from them Nothing
646 NCMsg (ProcessIdentifier from) (Monitor ref) ->
647 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
648 NCMsg (ProcessIdentifier from) (Unlink them) ->
649 ncEffectUnlink from them
650 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
651 ncEffectUnmonitor from ref
652 NCMsg _from (Died ident reason) ->
653 ncEffectDied ident reason
654 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
655 ncEffectSpawn from proc ref
656 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
657 ncEffectRegister from label atnode pid force
658 NCMsg (ProcessIdentifier from) (WhereIs label) ->
659 ncEffectWhereIs from label
660 NCMsg _ (NamedSend label msg') ->
661 ncEffectNamedSend label msg'
662 NCMsg _ (LocalSend to msg') ->
663 ncEffectLocalSend node to msg'
664 NCMsg _ (LocalPortSend to msg') ->
665 ncEffectLocalPortSend to msg'
666 NCMsg (ProcessIdentifier from) (Kill to reason) ->
667 ncEffectKill from to reason
668 NCMsg (ProcessIdentifier from) (Exit to reason) ->
669 ncEffectExit from to reason
670 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
671 ncEffectGetInfo from pid
672 NCMsg _ SigShutdown ->
674 NT.closeEndPoint (localEndPoint node)
675 `Exception.finally` throwIO ThreadKilled
676 -- ThreadKilled seems to make more sense than fail/error here
678 error $ "nodeController: unexpected message " ++ show unexpected
680-- [Unified: Table 10]
681ncEffectMonitor :: ProcessId -- ^ Who's watching?
682 -> Identifier -- ^ Who's being watched?
683 -> Maybe MonitorRef -- ^ 'Nothing' to link
685ncEffectMonitor from them mRef = do
688 if not (isLocal node them)
690 else isValidLocalIdentifier them
691 case (shouldLink, isLocal node (ProcessIdentifier from)) of
692 (True, _) -> -- [Unified: first rule]
694 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
695 Nothing -> modify' $ linksFor them ^: Set.insert from
696 (False, True) -> -- [Unified: second rule]
697 notifyDied from them DiedUnknownId mRef
698 (False, False) -> -- [Unified: third rule]
699 -- TODO: this is the right sender according to the Unified semantics,
700 -- but perhaps having 'them' as the sender would make more sense
701 -- (see also: notifyDied)
702 liftIO $ sendBinary node
703 (NodeIdentifier $ localNodeId node)
704 (NodeIdentifier $ processNodeId from)
705 WithImplicitReconnect
707 { ctrlMsgSender = NodeIdentifier (localNodeId node)
708 , ctrlMsgSignal = Died them DiedUnknownId
711-- [Unified: Table 11]
712ncEffectUnlink :: ProcessId -> Identifier -> NC ()
713ncEffectUnlink from them = do
715 when (isLocal node (ProcessIdentifier from)) $
717 ProcessIdentifier pid ->
718 postAsMessage from $ DidUnlinkProcess pid
719 NodeIdentifier nid ->
720 postAsMessage from $ DidUnlinkNode nid
721 SendPortIdentifier cid ->
722 postAsMessage from $ DidUnlinkPort cid
723 modify' $ linksFor them ^: Set.delete from
725-- [Unified: Table 11]
726ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
727ncEffectUnmonitor from ref = do
729 when (isLocal node (ProcessIdentifier from)) $
730 postAsMessage from $ DidUnmonitor ref
731 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
733-- [Unified: Table 12]
734ncEffectDied :: Identifier -> DiedReason -> NC ()
735ncEffectDied ident reason = do
737 traceNotifyDied node ident reason
738 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
739 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
741-- _registry :: !(Map (String,NodeId) ProcessId)
743 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
745 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
747 when (localOnly <= isLocal node (ProcessIdentifier us)) $
748 notifyDied us them reason Nothing
750 forM_ (Map.toList affectedMons) $ \(them, refs) ->
751 forM_ refs $ \(us, ref) ->
752 when (localOnly <= isLocal node (ProcessIdentifier us)) $
753 notifyDied us them reason (Just ref)
755 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
757 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
759 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
760 mapM (\(pid,nidlist) ->
761 case ident `impliesDeathOf` ProcessIdentifier pid of
763 do forM_ nidlist $ \(nid,_) ->
764 when (not $ isLocal node (NodeIdentifier nid))
765 (forwardNameDeath node nid)
767 False -> return $ Just (pid,nidlist) )
768 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
770 forwardNameDeath node nid =
771 liftIO $ sendBinary node
772 (NodeIdentifier $ localNodeId node)
773 (NodeIdentifier $ nid)
774 WithImplicitReconnect
776 { ctrlMsgSender = NodeIdentifier (localNodeId node)
777 , ctrlMsgSignal = Died ident reason
780-- [Unified: Table 13]
781ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
782ncEffectSpawn pid cProc ref = do
783 mProc <- unClosure cProc
784 -- If the closure does not exist, we spawn a process that throws an exception
785 -- This allows the remote node to find out what's happening
787 let proc = case mProc of
788 Left err -> fail $ "Error: Could not resolve closure: " ++ err
791 pid' <- liftIO $ forkProcess node proc
792 liftIO $ sendMessage node
793 (NodeIdentifier (localNodeId node))
794 (ProcessIdentifier pid)
795 WithImplicitReconnect
798-- Unified semantics does not explicitly describe how to implement 'register',
799-- but mentions it's "very similar to nsend" (Table 14)
800-- We send a response indicated if the operation is invalid
801ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
802ncEffectRegister from label atnode mPid reregistration = do
804 currentVal <- gets (^. registeredHereFor label)
807 Nothing -> -- unregister request
808 return $ isJust currentVal
809 Just thepid -> -- register request
810 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
811 return $ (isNothing currentVal /= reregistration) &&
812 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
813 if isLocal node (NodeIdentifier atnode)
814 then do when (isOk) $
815 do modify' $ registeredHereFor label ^= mPid
816 updateRemote node currentVal mPid
818 (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
819 Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
820 liftIO $ sendMessage node
821 (NodeIdentifier (localNodeId node))
822 (ProcessIdentifier from)
823 WithImplicitReconnect
824 (RegisterReply label isOk)
826 case reregistration of
828 False -> flip incList
831 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
832 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
833 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
834 forward node (processNodeId newval) (Register label atnode (Just newval) False)
835 updateRemote node Nothing (Just newval) =
836 forward node (processNodeId newval) (Register label atnode (Just newval) False)
837 updateRemote node (Just oldval) Nothing =
838 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
839 updateRemote _ _ _ = return ()
840 maybeify f Nothing = unmaybeify $ f []
841 maybeify f (Just x) = unmaybeify $ f x
843 unmaybeify [] = Nothing
844 unmaybeify x = Just x
845 incList [] tag = [(tag,1)]
846 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
847 incList (x:xs) tag = x : incList xs tag
849 decList ((atag,1):xs) tag | atag == tag = xs
850 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
851 decList (x:xs) tag = x:decList xs tag
852 forward node to reg =
853 when (not $ isLocal node (NodeIdentifier to)) $
854 liftIO $ sendBinary node
855 (ProcessIdentifier from)
857 WithImplicitReconnect
859 { ctrlMsgSender = ProcessIdentifier from
860 , ctrlMsgSignal = reg
864-- Unified semantics does not explicitly describe 'whereis'
865ncEffectWhereIs :: ProcessId -> String -> NC ()
866ncEffectWhereIs from label = do
868 mPid <- gets (^. registeredHereFor label)
869 liftIO $ sendMessage node
870 (NodeIdentifier (localNodeId node))
871 (ProcessIdentifier from)
872 WithImplicitReconnect
873 (WhereIsReply label mPid)
875-- [Unified: Table 14]
876ncEffectNamedSend :: String -> Message -> NC ()
877ncEffectNamedSend label msg = do
878 mPid <- gets (^. registeredHereFor label)
879 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
880 forM_ mPid $ \pid -> postMessage pid msg
883ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
884ncEffectLocalSend node to msg =
885 liftIO $ withLocalProc node to $ \p -> do
886 enqueue (processQueue p) msg
887 trace node (TraceEvReceived to msg)
890ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
891ncEffectLocalPortSend from msg = do
893 let pid = sendPortProcessId from
894 cid = sendPortLocalId from
895 liftIO $ withLocalProc node pid $ \proc -> do
896 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
898 -- in the unlikely event we know nothing about this channel id,
899 -- there's little to be done - perhaps some logging/tracing though...
901 Just (TypedChannel chan') -> do
902 -- If ch is Nothing, the process has given up the read end of
903 -- the channel and we simply ignore the incoming message - this
904 ch <- deRefWeak chan'
905 forM_ ch $ \chan -> deliverChan msg chan
906 where deliverChan :: forall a . Message -> TQueue a -> IO ()
907 deliverChan (UnencodedMessage _ raw) chan' =
908 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
909 deliverChan (EncodedMessage _ _) _ =
910 -- this will not happen unless someone screws with Primitives.hs
911 error "invalid local channel delivery"
914ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
915ncEffectKill from to reason = do
917 when (isLocal node (ProcessIdentifier to)) $
918 throwException to $ ProcessKillException from reason
921ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
922ncEffectExit from to reason = do
924 when (isLocal node (ProcessIdentifier to)) $
925 throwException to $ ProcessExitException from reason
928ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
929ncEffectGetInfo from pid =
930 let lpid = processLocalId pid
931 them = (ProcessIdentifier pid)
935 withMVar (localState node) $ return . (^. localProcessWithId lpid)
937 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
938 from node (ProcessInfoNone DiedUnknownId)
940 itsLinks <- gets (^. linksFor them)
941 itsMons <- gets (^. monitorsFor them)
942 registered <- gets (^. registeredHere)
944 let reg = registeredNames registered
945 dispatch (isLocal node (ProcessIdentifier from))
949 infoNode = (processNodeId pid)
950 , infoRegisteredNames = reg
951 -- we cannot populate this field
952 , infoMessageQueueLength = Nothing
953 , infoMonitors = Set.toList itsMons
954 , infoLinks = Set.toList itsLinks
956 where dispatch :: (Serializable a, Show a)
962 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
963 dispatch False dest node pInfo = do
964 liftIO $ sendMessage node
965 (NodeIdentifier (localNodeId node))
966 (ProcessIdentifier dest)
967 WithImplicitReconnect
970 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
974--------------------------------------------------------------------------------
976--------------------------------------------------------------------------------
978notifyDied :: ProcessId -- ^ Who to notify?
979 -> Identifier -- ^ Who died?
980 -> DiedReason -- ^ How did they die?
981 -> Maybe MonitorRef -- ^ 'Nothing' for linking
983notifyDied dest src reason mRef = do
985 case (isLocal node (ProcessIdentifier dest), mRef, src) of
986 (True, Just ref, ProcessIdentifier pid) ->
987 postAsMessage dest $ ProcessMonitorNotification ref pid reason
988 (True, Just ref, NodeIdentifier nid) ->
989 postAsMessage dest $ NodeMonitorNotification ref nid reason
990 (True, Just ref, SendPortIdentifier cid) ->
991 postAsMessage dest $ PortMonitorNotification ref cid reason
992 (True, Nothing, ProcessIdentifier pid) ->
993 throwException dest $ ProcessLinkException pid reason
994 (True, Nothing, NodeIdentifier pid) ->
995 throwException dest $ NodeLinkException pid reason
996 (True, Nothing, SendPortIdentifier pid) ->
997 throwException dest $ PortLinkException pid reason
999 -- The change in sender comes from [Unified: Table 10]
1000 liftIO $ sendBinary node
1001 (NodeIdentifier $ localNodeId node)
1002 (NodeIdentifier $ processNodeId dest)
1003 WithImplicitReconnect
1005 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1006 , ctrlMsgSignal = Died src reason
1009-- | [Unified: Table 8]
1010destNid :: ProcessSignal -> Maybe NodeId
1011destNid (Link ident) = Just $ nodeOf ident
1012destNid (Unlink ident) = Just $ nodeOf ident
1013destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1014destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1015destNid (Spawn _ _) = Nothing
1016destNid (Register _ _ _ _) = Nothing
1017destNid (WhereIs _) = Nothing
1018destNid (NamedSend _ _) = Nothing
1019-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1020-- then when a local process dies the monitoring/linking machinery will take
1021-- care of notifying remote nodes
1022destNid (Died _ _) = Nothing
1023destNid (Kill pid _) = Just $ processNodeId pid
1024destNid (Exit pid _) = Just $ processNodeId pid
1025destNid (GetInfo pid) = Just $ processNodeId pid
1026destNid (LocalSend pid _) = Just $ processNodeId pid
1027destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1028destNid (SigShutdown) = Nothing
1030-- | Check if a process is local to our own node
1031isLocal :: LocalNode -> Identifier -> Bool
1032isLocal nid ident = nodeOf ident == localNodeId nid
1034-- | Lookup a local closure
1035unClosure :: Typeable a => Closure a -> NC (Either String a)
1036unClosure closure = do
1037 rtable <- remoteTable <$> ask
1038 return (Static.unclosure rtable closure)
1040-- | Check if an identifier refers to a valid local object
1041isValidLocalIdentifier :: Identifier -> NC Bool
1042isValidLocalIdentifier ident = do
1044 liftIO . withMVar (localState node) $ \nSt ->
1046 NodeIdentifier nid ->
1047 return $ nid == localNodeId node
1048 ProcessIdentifier pid -> do
1049 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1050 return $ isJust mProc
1051 SendPortIdentifier cid -> do
1052 let pid = sendPortProcessId cid
1053 mProc = nSt ^. localProcessWithId (processLocalId pid)
1055 Nothing -> return False
1056 Just proc -> withMVar (processState proc) $ \pSt -> do
1057 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1060--------------------------------------------------------------------------------
1061-- Messages to local processes --
1062--------------------------------------------------------------------------------
1064postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1065postAsMessage pid = postMessage pid . createUnencodedMessage
1067postMessage :: ProcessId -> Message -> NC ()
1068postMessage pid msg = do
1070 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1072throwException :: Exception e => ProcessId -> e -> NC ()
1073throwException pid e = do
1075 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1077withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1078withLocalProc node pid p =
1079 -- By [Unified: table 6, rule missing_process] messages to dead processes
1080 -- can silently be dropped
1081 let lpid = processLocalId pid in do
1082 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1085--------------------------------------------------------------------------------
1087--------------------------------------------------------------------------------
1089links :: Accessor NCState (Map Identifier (Set ProcessId))
1090links = accessor _links (\ls st -> st { _links = ls })
1092monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1093monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1095registeredHere :: Accessor NCState (Map String ProcessId)
1096registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1098registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1099registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1101linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1102linksFor ident = links >>> DAC.mapDefault Set.empty ident
1104monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1105monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1107registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1108registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1110registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1111registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1113-- | @splitNotif ident@ splits a notifications map into those
1114-- notifications that should trigger when 'ident' fails and those links that
1117-- There is a hierarchy between identifiers: failure of a node implies failure
1118-- of all processes on that node, and failure of a process implies failure of
1119-- all typed channels to that process. In other words, if 'ident' refers to a
1120-- node, then the /should trigger/ set will include
1122-- * the notifications for the node specifically
1123-- * the notifications for processes on that node, and
1124-- * the notifications for typed channels to processes on that node.
1126-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1129-- * the notifications for that process specifically and
1130-- * the notifications for typed channels to that process.
1132-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1133splitNotif :: Identifier
1135 -> (Map Identifier a, Map Identifier a)
1136splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1138--------------------------------------------------------------------------------
1140--------------------------------------------------------------------------------
1142-- | Modify and evaluate the state
1143modify' :: MonadState s m => (s -> s) -> m ()
1144modify' 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