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 (Handler(..), catch, catches, finally)
60import Control.Concurrent (forkIO, myThreadId)
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
165import Control.Distributed.Process.Serializable (Serializable)
166import Control.Distributed.Process.Internal.Messaging
169 , closeImplicitReconnections
172import Control.Distributed.Process.Internal.Primitives
181import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
182import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
183import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
184import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
188import Control.Distributed.Process.Management.Agent
191import System.Environment (getEnv)
194--------------------------------------------------------------------------------
196--------------------------------------------------------------------------------
198initRemoteTable :: RemoteTable
199initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
201-- | Initialize a new local node.
202newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
203newLocalNode transport rtable = do
204 mEndPoint <- NT.newEndPoint transport
206 Left ex -> throwIO ex
208 localNode <- createBareLocalNode endPoint rtable
209 startServiceProcesses localNode
212-- | Create a new local node (without any service processes running)
213createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
214createBareLocalNode endPoint rtable = do
216 state <- newMVar LocalNodeState
217 { _localProcesses = Map.empty
218 , _localPidCounter = firstNonReservedProcessId
219 , _localPidUnique = unq
220 , _localConnections = Map.empty
223 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
224 , localEndPoint = endPoint
226 , localCtrlChan = ctrlChan
227 , localEventBus = MxEventBusInitialising
228 , remoteTable = rtable
230 tracedNode <- startMxAgent node
232 -- Once the NC terminates, the endpoint isn't much use,
233 void $ forkIO $ Exception.finally (runNodeController tracedNode)
234 (NT.closeEndPoint (localEndPoint node))
236 -- whilst a closed/failing endpoint will terminate the NC
237 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
243 writeChan (localCtrlChan node) NCMsg
244 { ctrlMsgSender = NodeIdentifier (localNodeId node)
245 , ctrlMsgSignal = SigShutdown
248startMxAgent :: LocalNode -> IO LocalNode
249startMxAgent node = do
250 -- see note [tracer/forkProcess races]
251 let fork = forkProcess node
252 mv <- MVar.newEmptyMVar
253 pid <- fork $ mxAgentController fork mv
254 (tracer, wqRef, mxNew) <- MVar.takeMVar mv
255 return node { localEventBus = (MxEventBus pid tracer wqRef mxNew) }
257startDefaultTracer :: LocalNode -> IO ()
258startDefaultTracer node' = do
259 let t = localEventBus node'
261 MxEventBus _ (Tracer pid _) _ _ -> do
262 runProcess node' $ register "trace.controller" pid
263 pid' <- forkProcess node' defaultTracer
264 enableTrace (localEventBus node') pid'
265 runProcess node' $ register "tracer.initial" pid'
268-- | Start and register the service processes on a node
269startServiceProcesses :: LocalNode -> IO ()
270startServiceProcesses node = do
271 -- tracing /spawns/ relies on the tracer being enabled, but we start
272 -- the default tracer first, even though it might @nsend@ to the logger
273 -- before /that/ process has started - this is a totally harmless race
274 -- however, so we deliberably ignore it
275 startDefaultTracer node
276 logger <- forkProcess node loop
277 runProcess node $ register "logger" logger
281 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
282 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
284 , match $ \((time, string) :: (String, String)) -> do
285 -- this is a 'trace' message from the local node tracer
286 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
288 , match $ \(ch :: SendPort ()) -> -- a shutdown request
292-- | Force-close a local node
294-- TODO: for now we just close the associated endpoint
295closeLocalNode :: LocalNode -> IO ()
297 -- TODO: close all our processes, surely!?
298 NT.closeEndPoint (localEndPoint node)
300-- | Run a process on a local node and wait for it to finish
301runProcess :: LocalNode -> Process () -> IO ()
302runProcess node proc = do
305 void $ forkProcess node $ do
306 catch (proc `finally` liftIO (putMVar done ()))
307 (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
310-- | Spawn a new process on a local node
311forkProcess :: LocalNode -> Process () -> IO ProcessId
312forkProcess node proc = modifyMVar (localState node) startProcess
314 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
316 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
317 , lpidUnique = st ^. localPidUnique
319 let pid = ProcessId { processNodeId = localNodeId node
320 , processLocalId = lpid
322 pst <- newMVar LocalProcessState { _monitorCounter = 0
324 , _channelCounter = 0
325 , _typedChannels = Map.empty
328 weakQueue <- mkWeakCQueue queue (return ())
329 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
330 let lproc = LocalProcess { processQueue = queue
331 , processWeakQ = weakQueue
334 , processThread = tid
338 reason <- Exception.catches
339 (runLocalProcess lproc proc >> return DiedNormal)
340 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
341 mMsg <- unwrapMessage msg :: IO (Maybe String)
343 Nothing -> return $ DiedException $ show ex
344 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
346 (return . DiedException . (show :: SomeException -> String)))]
348 -- [Unified: Table 4, rules termination and exiting]
349 modifyMVar_ (localState node) (cleanupProcess pid)
350 writeChan (localCtrlChan node) NCMsg
351 { ctrlMsgSender = ProcessIdentifier pid
352 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
357 -- see note [tracer/forkProcess races]
358 trace node (TraceEvSpawned pid)
360 if lpidCounter lpid == maxBound
362 newUnique <- randomIO
363 return ( (localProcessWithId lpid ^= Just lproc)
364 . (localPidCounter ^= firstNonReservedProcessId)
365 . (localPidUnique ^= newUnique)
370 return ( (localProcessWithId lpid ^= Just lproc)
371 . (localPidCounter ^: (+ 1))
376 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
377 cleanupProcess pid st = do
378 let pid' = ProcessIdentifier pid
379 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
380 mapM_ (NT.close . fst) (Map.elems affected)
381 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
382 . (localConnections ^= unaffected)
385-- note [tracer/forkProcess races]
387-- Our startTracing function uses forkProcess to start the trace controller
388-- process, and of course forkProcess attempts to call traceEvent once the
389-- process has started. This is harmless, as the localEventBus is not updated
390-- until /after/ the initial forkProcess completes, so the first call to
391-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
394--------------------------------------------------------------------------------
395-- Handle incoming messages --
396--------------------------------------------------------------------------------
398type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
402 | ToProc ProcessId (Weak (CQueue Message))
403 | ToChan TypedChannel
406data ConnectionState = ConnectionState {
407 _incoming :: !(Map NT.ConnectionId IncomingConnection)
408 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
411initConnectionState :: ConnectionState
412initConnectionState = ConnectionState {
413 _incoming = Map.empty
414 , _incomingFrom = Map.empty
417incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
418incoming = accessor _incoming (\conns st -> st { _incoming = conns })
420incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
421incomingAt cid = incoming >>> DAC.mapMaybe cid
423incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
424incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
426 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
428handleIncomingMessages :: LocalNode -> IO ()
429handleIncomingMessages node = go initConnectionState
431 go :: ConnectionState -> IO ()
433 event <- NT.receive endpoint
435 NT.ConnectionOpened cid rel theirAddr ->
436 if rel == NT.ReliableOrdered
438 trace node (TraceEvConnected cid)
440 (incomingAt cid ^= Just (theirAddr, Uninit))
441 . (incomingFrom theirAddr ^: Set.insert cid)
444 else invalidRequest cid st
445 NT.Received cid payload ->
446 case st ^. incomingAt cid of
447 Just (_, ToProc pid weakQueue) -> do
448 mQueue <- deRefWeak weakQueue
449 forM_ mQueue $ \queue -> do
450 -- TODO: if we find that the queue is Nothing, should we remove
451 -- it from the NC state? (and same for channels, below)
452 let msg = payloadToMessage payload
453 enqueue queue msg -- 'enqueue' is strict
454 trace node (TraceEvReceived pid msg)
456 Just (_, ToChan (TypedChannel chan')) -> do
457 mChan <- deRefWeak chan'
458 -- If mChan is Nothing, the process has given up the read end of
459 -- the channel and we simply ignore the incoming message
460 forM_ mChan $ \chan -> atomically $
461 -- We make sure the message is fully decoded when it is enqueued
462 writeTQueue chan $! decode (BSL.fromChunks payload)
464 Just (_, ToNode) -> do
465 let ctrlMsg = decode . BSL.fromChunks $ payload
466 writeChan ctrlChan $! ctrlMsg
468 Just (src, Uninit) ->
469 case decode (BSL.fromChunks payload) of
470 ProcessIdentifier pid -> do
471 let lpid = processLocalId pid
472 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
475 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
477 invalidRequest cid st
478 SendPortIdentifier chId -> do
479 let lcid = sendPortLocalId chId
480 lpid = processLocalId (sendPortProcessId chId)
481 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
484 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
487 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
489 invalidRequest cid st
491 invalidRequest cid st
492 NodeIdentifier nid ->
493 if nid == localNodeId node
494 then go (incomingAt cid ^= Just (src, ToNode) $ st)
495 else invalidRequest cid st
497 invalidRequest cid st
498 NT.ConnectionClosed cid ->
499 case st ^. incomingAt cid of
501 invalidRequest cid st
503 trace node (TraceEvDisconnected cid)
504 go ( (incomingAt cid ^= Nothing)
505 . (incomingFrom src ^: Set.delete cid)
508 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
509 -- [Unified table 9, rule node_disconnect]
510 let nid = NodeIdentifier $ NodeId theirAddr
511 writeChan ctrlChan NCMsg
512 { ctrlMsgSender = nid
513 , ctrlMsgSignal = Died nid DiedDisconnect
515 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
516 closeImplicitReconnections node nid
517 go ( (incomingFrom theirAddr ^= Set.empty)
518 . (incoming ^: Map.filterWithKey (const . notLost))
521 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
522 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
523 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
524 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
527 NT.ReceivedMulticast _ _ ->
528 -- If we received a multicast message, something went horribly wrong
529 -- and we just give up
530 fail "Cloud Haskell fatal error: received unexpected multicast"
532 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
533 invalidRequest cid st = do
534 -- TODO: We should treat this as a fatal error on the part of the remote
535 -- node. That is, we should report the remote node as having died, and we
536 -- should close incoming connections (this requires a Transport layer
538 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
540 go ( incomingAt cid ^= Nothing
544 state = localState node
545 endpoint = localEndPoint node
546 ctrlChan = localCtrlChan node
548--------------------------------------------------------------------------------
549-- Top-level access to the node controller --
550--------------------------------------------------------------------------------
552runNodeController :: LocalNode -> IO ()
554 runReaderT (evalStateT (unNC nodeController) initNCState)
556--------------------------------------------------------------------------------
557-- Internal data types --
558--------------------------------------------------------------------------------
560data NCState = NCState
561 { -- Mapping from remote processes to linked local processes
562 _links :: !(Map Identifier (Set ProcessId))
563 -- Mapping from remote processes to monitoring local processes
564 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
565 -- Process registry: names and where they live, mapped to the PIDs
566 , _registeredHere :: !(Map String ProcessId)
567 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
570newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
571 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
573initNCState :: NCState
574initNCState = NCState { _links = Map.empty
575 , _monitors = Map.empty
576 , _registeredHere = Map.empty
577 , _registeredOnNodes = Map.empty
580-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
581-- type is deliberately not exported so it cannot be caught explicitly.
582data ProcessKillException =
583 ProcessKillException !ProcessId !String
586instance Exception ProcessKillException
587instance Show ProcessKillException where
588 show (ProcessKillException pid reason) =
589 "killed-by=" ++ show pid ++ ",reason=" ++ reason
591--------------------------------------------------------------------------------
592-- Tracing/Debugging --
593--------------------------------------------------------------------------------
595-- [Issue #104 / DP-13]
597traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
598traceNotifyDied node ident reason =
599 -- TODO: sendPortDied notifications
600 liftIO $ withLocalTracer node $ \t ->
602 (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
603 (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
606traceEventFmtIO :: LocalNode
610traceEventFmtIO node fmt args =
611 withLocalTracer node $ \t -> traceLogFmt t fmt args
613trace :: LocalNode -> TraceEvent -> IO ()
614trace node ev = withLocalTracer node $ \t -> traceEvent t ev
616withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
617withLocalTracer node act = act (localEventBus node)
619--------------------------------------------------------------------------------
620-- Core functionality --
621--------------------------------------------------------------------------------
624nodeController :: NC ()
628 msg <- liftIO $ readChan (localCtrlChan node)
630 -- [Unified: Table 7, rule nc_forward]
631 case destNid (ctrlMsgSignal msg) of
632 Just nid' | nid' /= localNodeId node ->
633 liftIO $ sendBinary node
635 (NodeIdentifier nid')
636 WithImplicitReconnect
642 NCMsg (ProcessIdentifier from) (Link them) ->
643 ncEffectMonitor from them Nothing
644 NCMsg (ProcessIdentifier from) (Monitor ref) ->
645 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
646 NCMsg (ProcessIdentifier from) (Unlink them) ->
647 ncEffectUnlink from them
648 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
649 ncEffectUnmonitor from ref
650 NCMsg _from (Died ident reason) ->
651 ncEffectDied ident reason
652 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
653 ncEffectSpawn from proc ref
654 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
655 ncEffectRegister from label atnode pid force
656 NCMsg (ProcessIdentifier from) (WhereIs label) ->
657 ncEffectWhereIs from label
658 NCMsg _ (NamedSend label msg') ->
659 ncEffectNamedSend label msg'
660 NCMsg _ (LocalSend to msg') ->
661 ncEffectLocalSend node to msg'
662 NCMsg _ (LocalPortSend to msg') ->
663 ncEffectLocalPortSend to msg'
664 NCMsg (ProcessIdentifier from) (Kill to reason) ->
665 ncEffectKill from to reason
666 NCMsg (ProcessIdentifier from) (Exit to reason) ->
667 ncEffectExit from to reason
668 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
669 ncEffectGetInfo from pid
670 NCMsg _ SigShutdown ->
672 NT.closeEndPoint (localEndPoint node)
673 `Exception.finally` throwIO ThreadKilled
674 -- ThreadKilled seems to make more sense than fail/error here
676 error $ "nodeController: unexpected message " ++ show unexpected
678-- [Unified: Table 10]
679ncEffectMonitor :: ProcessId -- ^ Who's watching?
680 -> Identifier -- ^ Who's being watched?
681 -> Maybe MonitorRef -- ^ 'Nothing' to link
683ncEffectMonitor from them mRef = do
686 if not (isLocal node them)
688 else isValidLocalIdentifier them
689 case (shouldLink, isLocal node (ProcessIdentifier from)) of
690 (True, _) -> -- [Unified: first rule]
692 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
693 Nothing -> modify' $ linksFor them ^: Set.insert from
694 (False, True) -> -- [Unified: second rule]
695 notifyDied from them DiedUnknownId mRef
696 (False, False) -> -- [Unified: third rule]
697 -- TODO: this is the right sender according to the Unified semantics,
698 -- but perhaps having 'them' as the sender would make more sense
699 -- (see also: notifyDied)
700 liftIO $ sendBinary node
701 (NodeIdentifier $ localNodeId node)
702 (NodeIdentifier $ processNodeId from)
703 WithImplicitReconnect
705 { ctrlMsgSender = NodeIdentifier (localNodeId node)
706 , ctrlMsgSignal = Died them DiedUnknownId
709-- [Unified: Table 11]
710ncEffectUnlink :: ProcessId -> Identifier -> NC ()
711ncEffectUnlink from them = do
713 when (isLocal node (ProcessIdentifier from)) $
715 ProcessIdentifier pid ->
716 postAsMessage from $ DidUnlinkProcess pid
717 NodeIdentifier nid ->
718 postAsMessage from $ DidUnlinkNode nid
719 SendPortIdentifier cid ->
720 postAsMessage from $ DidUnlinkPort cid
721 modify' $ linksFor them ^: Set.delete from
723-- [Unified: Table 11]
724ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
725ncEffectUnmonitor from ref = do
727 when (isLocal node (ProcessIdentifier from)) $
728 postAsMessage from $ DidUnmonitor ref
729 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
731-- [Unified: Table 12]
732ncEffectDied :: Identifier -> DiedReason -> NC ()
733ncEffectDied ident reason = do
735 traceNotifyDied node ident reason
736 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
737 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
739-- _registry :: !(Map (String,NodeId) ProcessId)
741 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
743 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
745 when (localOnly <= isLocal node (ProcessIdentifier us)) $
746 notifyDied us them reason Nothing
748 forM_ (Map.toList affectedMons) $ \(them, refs) ->
749 forM_ refs $ \(us, ref) ->
750 when (localOnly <= isLocal node (ProcessIdentifier us)) $
751 notifyDied us them reason (Just ref)
753 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
755 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
757 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
758 mapM (\(pid,nidlist) ->
759 case ident `impliesDeathOf` ProcessIdentifier pid of
761 do forM_ nidlist $ \(nid,_) ->
762 when (not $ isLocal node (NodeIdentifier nid))
763 (forwardNameDeath node nid)
765 False -> return $ Just (pid,nidlist) )
766 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
768 forwardNameDeath node nid =
769 liftIO $ sendBinary node
770 (NodeIdentifier $ localNodeId node)
771 (NodeIdentifier $ nid)
772 WithImplicitReconnect
774 { ctrlMsgSender = NodeIdentifier (localNodeId node)
775 , ctrlMsgSignal = Died ident reason
778-- [Unified: Table 13]
779ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
780ncEffectSpawn pid cProc ref = do
781 mProc <- unClosure cProc
782 -- If the closure does not exist, we spawn a process that throws an exception
783 -- This allows the remote node to find out what's happening
785 let proc = case mProc of
786 Left err -> fail $ "Error: Could not resolve closure: " ++ err
789 pid' <- liftIO $ forkProcess node proc
790 liftIO $ sendMessage node
791 (NodeIdentifier (localNodeId node))
792 (ProcessIdentifier pid)
793 WithImplicitReconnect
796-- Unified semantics does not explicitly describe how to implement 'register',
797-- but mentions it's "very similar to nsend" (Table 14)
798-- We send a response indicated if the operation is invalid
799ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
800ncEffectRegister from label atnode mPid reregistration = do
802 currentVal <- gets (^. registeredHereFor label)
805 Nothing -> -- unregister request
806 return $ isJust currentVal
807 Just thepid -> -- register request
808 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
809 return $ (isNothing currentVal /= reregistration) &&
810 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
811 if isLocal node (NodeIdentifier atnode)
812 then do when (isOk) $
813 do modify' $ registeredHereFor label ^= mPid
814 updateRemote node currentVal mPid
816 (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
817 Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
818 liftIO $ sendMessage node
819 (NodeIdentifier (localNodeId node))
820 (ProcessIdentifier from)
821 WithImplicitReconnect
822 (RegisterReply label isOk)
824 case reregistration of
826 False -> flip incList
829 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
830 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
831 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
832 forward node (processNodeId newval) (Register label atnode (Just newval) False)
833 updateRemote node Nothing (Just newval) =
834 forward node (processNodeId newval) (Register label atnode (Just newval) False)
835 updateRemote node (Just oldval) Nothing =
836 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
837 updateRemote _ _ _ = return ()
838 maybeify f Nothing = unmaybeify $ f []
839 maybeify f (Just x) = unmaybeify $ f x
841 unmaybeify [] = Nothing
842 unmaybeify x = Just x
843 incList [] tag = [(tag,1)]
844 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
845 incList (x:xs) tag = x : incList xs tag
847 decList ((atag,1):xs) tag | atag == tag = xs
848 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
849 decList (x:xs) tag = x:decList xs tag
850 forward node to reg =
851 when (not $ isLocal node (NodeIdentifier to)) $
852 liftIO $ sendBinary node
853 (ProcessIdentifier from)
855 WithImplicitReconnect
857 { ctrlMsgSender = ProcessIdentifier from
858 , ctrlMsgSignal = reg
862-- Unified semantics does not explicitly describe 'whereis'
863ncEffectWhereIs :: ProcessId -> String -> NC ()
864ncEffectWhereIs from label = do
866 mPid <- gets (^. registeredHereFor label)
867 liftIO $ sendMessage node
868 (NodeIdentifier (localNodeId node))
869 (ProcessIdentifier from)
870 WithImplicitReconnect
871 (WhereIsReply label mPid)
873-- [Unified: Table 14]
874ncEffectNamedSend :: String -> Message -> NC ()
875ncEffectNamedSend label msg = do
876 mPid <- gets (^. registeredHereFor label)
877 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
878 forM_ mPid $ \pid -> postMessage pid msg
881ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
882ncEffectLocalSend node to msg =
883 liftIO $ withLocalProc node to $ \p -> do
884 enqueue (processQueue p) msg
885 trace node (TraceEvReceived to msg)
888ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
889ncEffectLocalPortSend from msg = do
891 let pid = sendPortProcessId from
892 cid = sendPortLocalId from
893 liftIO $ withLocalProc node pid $ \proc -> do
894 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
896 -- in the unlikely event we know nothing about this channel id,
897 -- there's little to be done - perhaps some logging/tracing though...
899 Just (TypedChannel chan') -> do
900 -- If ch is Nothing, the process has given up the read end of
901 -- the channel and we simply ignore the incoming message - this
902 ch <- deRefWeak chan'
903 forM_ ch $ \chan -> deliverChan msg chan
904 where deliverChan :: forall a . Message -> TQueue a -> IO ()
905 deliverChan (UnencodedMessage _ raw) chan' =
906 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
907 deliverChan (EncodedMessage _ _) _ =
908 -- this will not happen unless someone screws with Primitives.hs
909 error "invalid local channel delivery"
912ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
913ncEffectKill from to reason = do
915 when (isLocal node (ProcessIdentifier to)) $
916 throwException to $ ProcessKillException from reason
919ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
920ncEffectExit from to reason = do
922 when (isLocal node (ProcessIdentifier to)) $
923 throwException to $ ProcessExitException from reason
926ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
927ncEffectGetInfo from pid =
928 let lpid = processLocalId pid
929 them = (ProcessIdentifier pid)
933 withMVar (localState node) $ return . (^. localProcessWithId lpid)
935 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
936 from node (ProcessInfoNone DiedUnknownId)
938 itsLinks <- gets (^. linksFor them)
939 itsMons <- gets (^. monitorsFor them)
940 registered <- gets (^. registeredHere)
942 let reg = registeredNames registered
943 dispatch (isLocal node (ProcessIdentifier from))
947 infoNode = (processNodeId pid)
948 , infoRegisteredNames = reg
949 -- we cannot populate this field
950 , infoMessageQueueLength = Nothing
951 , infoMonitors = Set.toList itsMons
952 , infoLinks = Set.toList itsLinks
954 where dispatch :: (Serializable a, Show a)
960 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
961 dispatch False dest node pInfo = do
962 liftIO $ sendMessage node
963 (NodeIdentifier (localNodeId node))
964 (ProcessIdentifier dest)
965 WithImplicitReconnect
968 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
972--------------------------------------------------------------------------------
974--------------------------------------------------------------------------------
976notifyDied :: ProcessId -- ^ Who to notify?
977 -> Identifier -- ^ Who died?
978 -> DiedReason -- ^ How did they die?
979 -> Maybe MonitorRef -- ^ 'Nothing' for linking
981notifyDied dest src reason mRef = do
983 case (isLocal node (ProcessIdentifier dest), mRef, src) of
984 (True, Just ref, ProcessIdentifier pid) ->
985 postAsMessage dest $ ProcessMonitorNotification ref pid reason
986 (True, Just ref, NodeIdentifier nid) ->
987 postAsMessage dest $ NodeMonitorNotification ref nid reason
988 (True, Just ref, SendPortIdentifier cid) ->
989 postAsMessage dest $ PortMonitorNotification ref cid reason
990 (True, Nothing, ProcessIdentifier pid) ->
991 throwException dest $ ProcessLinkException pid reason
992 (True, Nothing, NodeIdentifier pid) ->
993 throwException dest $ NodeLinkException pid reason
994 (True, Nothing, SendPortIdentifier pid) ->
995 throwException dest $ PortLinkException pid reason
997 -- The change in sender comes from [Unified: Table 10]
998 liftIO $ sendBinary node
999 (NodeIdentifier $ localNodeId node)
1000 (NodeIdentifier $ processNodeId dest)
1001 WithImplicitReconnect
1003 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1004 , ctrlMsgSignal = Died src reason
1007-- | [Unified: Table 8]
1008destNid :: ProcessSignal -> Maybe NodeId
1009destNid (Link ident) = Just $ nodeOf ident
1010destNid (Unlink ident) = Just $ nodeOf ident
1011destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1012destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1013destNid (Spawn _ _) = Nothing
1014destNid (Register _ _ _ _) = Nothing
1015destNid (WhereIs _) = Nothing
1016destNid (NamedSend _ _) = Nothing
1017-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1018-- then when a local process dies the monitoring/linking machinery will take
1019-- care of notifying remote nodes
1020destNid (Died _ _) = Nothing
1021destNid (Kill pid _) = Just $ processNodeId pid
1022destNid (Exit pid _) = Just $ processNodeId pid
1023destNid (GetInfo pid) = Just $ processNodeId pid
1024destNid (LocalSend pid _) = Just $ processNodeId pid
1025destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1026destNid (SigShutdown) = Nothing
1028-- | Check if a process is local to our own node
1029isLocal :: LocalNode -> Identifier -> Bool
1030isLocal nid ident = nodeOf ident == localNodeId nid
1032-- | Lookup a local closure
1033unClosure :: Typeable a => Closure a -> NC (Either String a)
1034unClosure closure = do
1035 rtable <- remoteTable <$> ask
1036 return (Static.unclosure rtable closure)
1038-- | Check if an identifier refers to a valid local object
1039isValidLocalIdentifier :: Identifier -> NC Bool
1040isValidLocalIdentifier ident = do
1042 liftIO . withMVar (localState node) $ \nSt ->
1044 NodeIdentifier nid ->
1045 return $ nid == localNodeId node
1046 ProcessIdentifier pid -> do
1047 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1048 return $ isJust mProc
1049 SendPortIdentifier cid -> do
1050 let pid = sendPortProcessId cid
1051 mProc = nSt ^. localProcessWithId (processLocalId pid)
1053 Nothing -> return False
1054 Just proc -> withMVar (processState proc) $ \pSt -> do
1055 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1058--------------------------------------------------------------------------------
1059-- Messages to local processes --
1060--------------------------------------------------------------------------------
1062postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1063postAsMessage pid = postMessage pid . createUnencodedMessage
1065postMessage :: ProcessId -> Message -> NC ()
1066postMessage pid msg = do
1068 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1070throwException :: Exception e => ProcessId -> e -> NC ()
1071throwException pid e = do
1073 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1075withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1076withLocalProc node pid p =
1077 -- By [Unified: table 6, rule missing_process] messages to dead processes
1078 -- can silently be dropped
1079 let lpid = processLocalId pid in do
1080 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1083--------------------------------------------------------------------------------
1085--------------------------------------------------------------------------------
1087links :: Accessor NCState (Map Identifier (Set ProcessId))
1088links = accessor _links (\ls st -> st { _links = ls })
1090monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1091monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1093registeredHere :: Accessor NCState (Map String ProcessId)
1094registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1096registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1097registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1099linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1100linksFor ident = links >>> DAC.mapDefault Set.empty ident
1102monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1103monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1105registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1106registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1108registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1109registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1111-- | @splitNotif ident@ splits a notifications map into those
1112-- notifications that should trigger when 'ident' fails and those links that
1115-- There is a hierarchy between identifiers: failure of a node implies failure
1116-- of all processes on that node, and failure of a process implies failure of
1117-- all typed channels to that process. In other words, if 'ident' refers to a
1118-- node, then the /should trigger/ set will include
1120-- * the notifications for the node specifically
1121-- * the notifications for processes on that node, and
1122-- * the notifications for typed channels to processes on that node.
1124-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1127-- * the notifications for that process specifically and
1128-- * the notifications for typed channels to that process.
1130-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1131splitNotif :: Identifier
1133 -> (Map Identifier a, Map Identifier a)
1134splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1136--------------------------------------------------------------------------------
1138--------------------------------------------------------------------------------
1140-- | Modify and evaluate the state
1141modify' :: MonadState s m => (s -> s) -> m ()
1142modify' 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