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 Control.Distributed.Process.Internal.Trace.Primitives
156import Control.Distributed.Process.Internal.Trace.Types
160import Control.Distributed.Process.Internal.Trace.Tracer
164import Control.Distributed.Process.Serializable (Serializable)
165import Control.Distributed.Process.Internal.Messaging
168 , closeImplicitReconnections
171import Control.Distributed.Process.Internal.Primitives
178import Control.Distributed.Process.Internal.Types (SendPort)
179import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
180import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
181import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
185import System.Environment (getEnv)
188--------------------------------------------------------------------------------
190--------------------------------------------------------------------------------
192initRemoteTable :: RemoteTable
193initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
195-- | Initialize a new local node.
196newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
197newLocalNode transport rtable = do
198 mEndPoint <- NT.newEndPoint transport
200 Left ex -> throwIO ex
202 localNode <- createBareLocalNode endPoint rtable
203 startServiceProcesses localNode
206-- | Create a new local node (without any service processes running)
207createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
208createBareLocalNode endPoint rtable = do
210 state <- newMVar LocalNodeState
211 { _localProcesses = Map.empty
212 , _localPidCounter = firstNonReservedProcessId
213 , _localPidUnique = unq
214 , _localConnections = Map.empty
217 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
218 , localEndPoint = endPoint
220 , localCtrlChan = ctrlChan
221 , localTracer = InactiveTracer
222 , remoteTable = rtable
224 tracedNode <- startTracing node
226 -- Once the NC terminates, the endpoint isn't much use,
227 void $ forkIO $ Exception.finally (runNodeController tracedNode)
228 (NT.closeEndPoint (localEndPoint node))
230 -- whilst a closed/failing endpoint will terminate the NC
231 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
237 writeChan (localCtrlChan node) NCMsg
238 { ctrlMsgSender = NodeIdentifier (localNodeId node)
239 , ctrlMsgSignal = SigShutdown
242startTracing :: LocalNode -> IO LocalNode
245 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> procTracer node)
246 (\(_ :: IOError) -> return node)
247 where procTracer :: LocalNode -> IO LocalNode
249 -- see note [tracer/forkProcess races]
250 mv <- MVar.newEmptyMVar
251 pid <- forkProcess n (traceController mv)
252 wqRef <- MVar.takeMVar mv
253 return n { localTracer = (ActiveTracer pid wqRef) }
255startDefaultTracer :: LocalNode -> IO (Maybe ProcessId)
256startDefaultTracer node =
258 (getEnv "DISTRIBUTED_PROCESS_TRACE_ENABLED" >> go node)
259 (\(_ :: IOError) -> return Nothing)
260 where go :: LocalNode -> IO (Maybe ProcessId)
262 pid <- forkProcess node' defaultTracer
265-- | Start and register the service processes on a node
266startServiceProcesses :: LocalNode -> IO ()
267startServiceProcesses node = do
268 -- tracing /spawns/ relies on the tracer being enabled, but we start
269 -- the default tracer first, even though it might @nsend@ to the logger
270 -- before /that/ process has started - this is a totally harmless race
271 mPid <- startDefaultTracer node
275 runProcess node (register "tracer" pid >> register "tracer.initial" pid)
277 logger <- forkProcess node loop
278 runProcess node $ register "logger" logger
282 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
283 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
285 , match $ \((time, string) :: (String, String)) -> do
286 -- this is a 'trace' message from the local node tracer
287 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
289 , match $ \(ch :: SendPort ()) -> -- a shutdown request
293-- | Force-close a local node
295-- TODO: for now we just close the associated endpoint
296closeLocalNode :: LocalNode -> IO ()
298 -- TODO: close all our processes, surely!?
299 NT.closeEndPoint (localEndPoint node)
301-- | Run a process on a local node and wait for it to finish
302runProcess :: LocalNode -> Process () -> IO ()
303runProcess node proc = do
305 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
308-- | Spawn a new process on a local node
309forkProcess :: LocalNode -> Process () -> IO ProcessId
310forkProcess node proc = modifyMVar (localState node) startProcess
312 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
314 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
315 , lpidUnique = st ^. localPidUnique
317 let pid = ProcessId { processNodeId = localNodeId node
318 , processLocalId = lpid
320 pst <- newMVar LocalProcessState { _monitorCounter = 0
322 , _channelCounter = 0
323 , _typedChannels = Map.empty
326 weakQueue <- mkWeakCQueue queue (return ())
327 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
328 let lproc = LocalProcess { processQueue = queue
329 , processWeakQ = weakQueue
332 , processThread = tid
336 reason <- Exception.catch
337 (runLocalProcess lproc proc >> return DiedNormal)
338 (return . DiedException . (show :: SomeException -> String))
340 -- [Unified: Table 4, rules termination and exiting]
341 modifyMVar_ (localState node) (cleanupProcess pid)
342 writeChan (localCtrlChan node) NCMsg
343 { ctrlMsgSender = ProcessIdentifier pid
344 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
349 -- see note [tracer/forkProcess races]
350 trace node (TraceEvSpawned pid)
352 if lpidCounter lpid == maxBound
354 newUnique <- randomIO
355 return ( (localProcessWithId lpid ^= Just lproc)
356 . (localPidCounter ^= firstNonReservedProcessId)
357 . (localPidUnique ^= newUnique)
362 return ( (localProcessWithId lpid ^= Just lproc)
363 . (localPidCounter ^: (+ 1))
368 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
369 cleanupProcess pid st = do
370 let pid' = ProcessIdentifier pid
371 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
372 mapM_ (NT.close . fst) (Map.elems affected)
373 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
374 . (localConnections ^= unaffected)
377-- note [tracer/forkProcess races]
379-- Our startTracing function uses forkProcess to start the trace controller
380-- process, and of course forkProcess attempts to call traceEvent once the
381-- process has started. This is harmless, as the localTracer is not updated
382-- until /after/ the initial forkProcess completes, so the first call to
383-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
386--------------------------------------------------------------------------------
387-- Handle incoming messages --
388--------------------------------------------------------------------------------
390type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
394 | ToProc ProcessId (Weak (CQueue Message))
395 | ToChan TypedChannel
398data ConnectionState = ConnectionState {
399 _incoming :: !(Map NT.ConnectionId IncomingConnection)
400 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
403initConnectionState :: ConnectionState
404initConnectionState = ConnectionState {
405 _incoming = Map.empty
406 , _incomingFrom = Map.empty
409incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
410incoming = accessor _incoming (\conns st -> st { _incoming = conns })
412incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
413incomingAt cid = incoming >>> DAC.mapMaybe cid
415incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
416incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
418 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
420handleIncomingMessages :: LocalNode -> IO ()
421handleIncomingMessages node = go initConnectionState
423 go :: ConnectionState -> IO ()
425 event <- NT.receive endpoint
427 NT.ConnectionOpened cid rel theirAddr ->
428 if rel == NT.ReliableOrdered
430 trace node (TraceEvConnected cid)
432 (incomingAt cid ^= Just (theirAddr, Uninit))
433 . (incomingFrom theirAddr ^: Set.insert cid)
436 else invalidRequest cid st
437 NT.Received cid payload ->
438 case st ^. incomingAt cid of
439 Just (_, ToProc pid weakQueue) -> do
440 mQueue <- deRefWeak weakQueue
441 forM_ mQueue $ \queue -> do
442 -- TODO: if we find that the queue is Nothing, should we remove
443 -- it from the NC state? (and same for channels, below)
444 let msg = payloadToMessage payload
445 enqueue queue msg -- 'enqueue' is strict
446 trace node (TraceEvReceived pid msg)
448 Just (_, ToChan (TypedChannel chan')) -> do
449 mChan <- deRefWeak chan'
450 -- If mChan is Nothing, the process has given up the read end of
451 -- the channel and we simply ignore the incoming message
452 forM_ mChan $ \chan -> atomically $
453 -- We make sure the message is fully decoded when it is enqueued
454 writeTQueue chan $! decode (BSL.fromChunks payload)
456 Just (_, ToNode) -> do
457 let ctrlMsg = decode . BSL.fromChunks $ payload
458 writeChan ctrlChan $! ctrlMsg
460 Just (src, Uninit) ->
461 case decode (BSL.fromChunks payload) of
462 ProcessIdentifier pid -> do
463 let lpid = processLocalId pid
464 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
467 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
469 invalidRequest cid st
470 SendPortIdentifier chId -> do
471 let lcid = sendPortLocalId chId
472 lpid = processLocalId (sendPortProcessId chId)
473 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
476 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
479 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
481 invalidRequest cid st
483 invalidRequest cid st
484 NodeIdentifier nid ->
485 if nid == localNodeId node
486 then go (incomingAt cid ^= Just (src, ToNode) $ st)
487 else invalidRequest cid st
489 invalidRequest cid st
490 NT.ConnectionClosed cid ->
491 case st ^. incomingAt cid of
493 invalidRequest cid st
495 trace node (TraceEvDisconnected cid)
496 go ( (incomingAt cid ^= Nothing)
497 . (incomingFrom src ^: Set.delete cid)
500 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
501 -- [Unified table 9, rule node_disconnect]
502 let nid = NodeIdentifier $ NodeId theirAddr
503 writeChan ctrlChan NCMsg
504 { ctrlMsgSender = nid
505 , ctrlMsgSignal = Died nid DiedDisconnect
507 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
508 closeImplicitReconnections node nid
509 go ( (incomingFrom theirAddr ^= Set.empty)
510 . (incoming ^: Map.filterWithKey (const . notLost))
513 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
514 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
515 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
516 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
519 NT.ReceivedMulticast _ _ ->
520 -- If we received a multicast message, something went horribly wrong
521 -- and we just give up
522 fail "Cloud Haskell fatal error: received unexpected multicast"
524 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
525 invalidRequest cid st = do
526 -- TODO: We should treat this as a fatal error on the part of the remote
527 -- node. That is, we should report the remote node as having died, and we
528 -- should close incoming connections (this requires a Transport layer
530 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
532 go ( incomingAt cid ^= Nothing
536 state = localState node
537 endpoint = localEndPoint node
538 ctrlChan = localCtrlChan node
540--------------------------------------------------------------------------------
541-- Top-level access to the node controller --
542--------------------------------------------------------------------------------
544runNodeController :: LocalNode -> IO ()
546 runReaderT (evalStateT (unNC nodeController) initNCState)
548--------------------------------------------------------------------------------
549-- Internal data types --
550--------------------------------------------------------------------------------
552data NCState = NCState
553 { -- Mapping from remote processes to linked local processes
554 _links :: !(Map Identifier (Set ProcessId))
555 -- Mapping from remote processes to monitoring local processes
556 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
557 -- Process registry: names and where they live, mapped to the PIDs
558 , _registeredHere :: !(Map String ProcessId)
559 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
562newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
563 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
565initNCState :: NCState
566initNCState = NCState { _links = Map.empty
567 , _monitors = Map.empty
568 , _registeredHere = Map.empty
569 , _registeredOnNodes = Map.empty
572-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
573-- type is deliberately not exported so it cannot be caught explicitly.
574data ProcessKillException =
575 ProcessKillException !ProcessId !String
578instance Exception ProcessKillException
579instance Show ProcessKillException where
580 show (ProcessKillException pid reason) =
581 "killed-by=" ++ show pid ++ ",reason=" ++ reason
583--------------------------------------------------------------------------------
584-- Tracing/Debugging --
585--------------------------------------------------------------------------------
589traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
590traceNotifyDied node ident reason =
591 -- TODO: sendPortDied notifications
592 liftIO $ withLocalTracer node $ \t ->
594 (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
595 (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
598traceEventFmtIO :: LocalNode
602traceEventFmtIO node fmt args =
603 withLocalTracer node $ \t -> traceLogFmt t fmt args
605trace :: LocalNode -> TraceEvent -> IO ()
606trace node ev = withLocalTracer node $ \t -> traceEvent t ev
608withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
609withLocalTracer node act = act (localTracer node)
611--------------------------------------------------------------------------------
612-- Core functionality --
613--------------------------------------------------------------------------------
616nodeController :: NC ()
620 msg <- liftIO $ readChan (localCtrlChan node)
622 -- [Unified: Table 7, rule nc_forward]
623 case destNid (ctrlMsgSignal msg) of
624 Just nid' | nid' /= localNodeId node ->
625 liftIO $ sendBinary node
627 (NodeIdentifier nid')
628 WithImplicitReconnect
634 NCMsg (ProcessIdentifier from) (Link them) ->
635 ncEffectMonitor from them Nothing
636 NCMsg (ProcessIdentifier from) (Monitor ref) ->
637 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
638 NCMsg (ProcessIdentifier from) (Unlink them) ->
639 ncEffectUnlink from them
640 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
641 ncEffectUnmonitor from ref
642 NCMsg _from (Died ident reason) ->
643 ncEffectDied ident reason
644 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
645 ncEffectSpawn from proc ref
646 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
647 ncEffectRegister from label atnode pid force
648 NCMsg (ProcessIdentifier from) (WhereIs label) ->
649 ncEffectWhereIs from label
650 NCMsg _ (NamedSend label msg') ->
651 ncEffectNamedSend label msg'
652 NCMsg _ (LocalSend to msg') ->
653 ncEffectLocalSend node to msg'
654 NCMsg _ (LocalPortSend to msg') ->
655 ncEffectLocalPortSend to msg'
656 NCMsg (ProcessIdentifier from) (Kill to reason) ->
657 ncEffectKill from to reason
658 NCMsg (ProcessIdentifier from) (Exit to reason) ->
659 ncEffectExit from to reason
660 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
661 ncEffectGetInfo from pid
662 NCMsg _ SigShutdown ->
664 NT.closeEndPoint (localEndPoint node)
665 `Exception.finally` throwIO ThreadKilled
666 -- ThreadKilled seems to make more sense than fail/error here
668 error $ "nodeController: unexpected message " ++ show unexpected
670-- [Unified: Table 10]
671ncEffectMonitor :: ProcessId -- ^ Who's watching?
672 -> Identifier -- ^ Who's being watched?
673 -> Maybe MonitorRef -- ^ 'Nothing' to link
675ncEffectMonitor from them mRef = do
678 if not (isLocal node them)
680 else isValidLocalIdentifier them
681 case (shouldLink, isLocal node (ProcessIdentifier from)) of
682 (True, _) -> -- [Unified: first rule]
684 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
685 Nothing -> modify' $ linksFor them ^: Set.insert from
686 (False, True) -> -- [Unified: second rule]
687 notifyDied from them DiedUnknownId mRef
688 (False, False) -> -- [Unified: third rule]
689 -- TODO: this is the right sender according to the Unified semantics,
690 -- but perhaps having 'them' as the sender would make more sense
691 -- (see also: notifyDied)
692 liftIO $ sendBinary node
693 (NodeIdentifier $ localNodeId node)
694 (NodeIdentifier $ processNodeId from)
695 WithImplicitReconnect
697 { ctrlMsgSender = NodeIdentifier (localNodeId node)
698 , ctrlMsgSignal = Died them DiedUnknownId
701-- [Unified: Table 11]
702ncEffectUnlink :: ProcessId -> Identifier -> NC ()
703ncEffectUnlink from them = do
705 when (isLocal node (ProcessIdentifier from)) $
707 ProcessIdentifier pid ->
708 postAsMessage from $ DidUnlinkProcess pid
709 NodeIdentifier nid ->
710 postAsMessage from $ DidUnlinkNode nid
711 SendPortIdentifier cid ->
712 postAsMessage from $ DidUnlinkPort cid
713 modify' $ linksFor them ^: Set.delete from
715-- [Unified: Table 11]
716ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
717ncEffectUnmonitor from ref = do
719 when (isLocal node (ProcessIdentifier from)) $
720 postAsMessage from $ DidUnmonitor ref
721 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
723-- [Unified: Table 12]
724ncEffectDied :: Identifier -> DiedReason -> NC ()
725ncEffectDied ident reason = do
727 traceNotifyDied node ident reason
728 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
729 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
731-- _registry :: !(Map (String,NodeId) ProcessId)
733 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
735 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
737 when (localOnly <= isLocal node (ProcessIdentifier us)) $
738 notifyDied us them reason Nothing
740 forM_ (Map.toList affectedMons) $ \(them, refs) ->
741 forM_ refs $ \(us, ref) ->
742 when (localOnly <= isLocal node (ProcessIdentifier us)) $
743 notifyDied us them reason (Just ref)
745 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
747 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
749 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
750 mapM (\(pid,nidlist) ->
751 case ident `impliesDeathOf` ProcessIdentifier pid of
753 do forM_ nidlist $ \(nid,_) ->
754 when (not $ isLocal node (NodeIdentifier nid))
755 (forwardNameDeath node nid)
757 False -> return $ Just (pid,nidlist) )
758 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
760 forwardNameDeath node nid =
761 liftIO $ sendBinary node
762 (NodeIdentifier $ localNodeId node)
763 (NodeIdentifier $ nid)
764 WithImplicitReconnect
766 { ctrlMsgSender = NodeIdentifier (localNodeId node)
767 , ctrlMsgSignal = Died ident reason
770-- [Unified: Table 13]
771ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
772ncEffectSpawn pid cProc ref = do
773 mProc <- unClosure cProc
774 -- If the closure does not exist, we spawn a process that throws an exception
775 -- This allows the remote node to find out what's happening
777 let proc = case mProc of
778 Left err -> fail $ "Error: Could not resolve closure: " ++ err
781 pid' <- liftIO $ forkProcess node proc
782 liftIO $ sendMessage node
783 (NodeIdentifier (localNodeId node))
784 (ProcessIdentifier pid)
785 WithImplicitReconnect
788-- Unified semantics does not explicitly describe how to implement 'register',
789-- but mentions it's "very similar to nsend" (Table 14)
790-- We send a response indicated if the operation is invalid
791ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
792ncEffectRegister from label atnode mPid reregistration = do
794 currentVal <- gets (^. registeredHereFor label)
797 Nothing -> -- unregister request
798 return $ isJust currentVal
799 Just thepid -> -- register request
800 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
801 return $ (isNothing currentVal /= reregistration) &&
802 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
803 if isLocal node (NodeIdentifier atnode)
804 then do when (isOk) $
805 do modify' $ registeredHereFor label ^= mPid
806 updateRemote node currentVal mPid
808 (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
809 Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
810 liftIO $ sendMessage node
811 (NodeIdentifier (localNodeId node))
812 (ProcessIdentifier from)
813 WithImplicitReconnect
814 (RegisterReply label isOk)
816 case reregistration of
818 False -> flip incList
821 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
822 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
823 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
824 forward node (processNodeId newval) (Register label atnode (Just newval) False)
825 updateRemote node Nothing (Just newval) =
826 forward node (processNodeId newval) (Register label atnode (Just newval) False)
827 updateRemote node (Just oldval) Nothing =
828 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
829 updateRemote _ _ _ = return ()
830 maybeify f Nothing = unmaybeify $ f []
831 maybeify f (Just x) = unmaybeify $ f x
833 unmaybeify [] = Nothing
834 unmaybeify x = Just x
835 incList [] tag = [(tag,1)]
836 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
837 incList (x:xs) tag = x : incList xs tag
839 decList ((atag,1):xs) tag | atag == tag = xs
840 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
841 decList (x:xs) tag = x:decList xs tag
842 forward node to reg =
843 when (not $ isLocal node (NodeIdentifier to)) $
844 liftIO $ sendBinary node
845 (ProcessIdentifier from)
847 WithImplicitReconnect
849 { ctrlMsgSender = ProcessIdentifier from
850 , ctrlMsgSignal = reg
854-- Unified semantics does not explicitly describe 'whereis'
855ncEffectWhereIs :: ProcessId -> String -> NC ()
856ncEffectWhereIs from label = do
858 mPid <- gets (^. registeredHereFor label)
859 liftIO $ sendMessage node
860 (NodeIdentifier (localNodeId node))
861 (ProcessIdentifier from)
862 WithImplicitReconnect
863 (WhereIsReply label mPid)
865-- [Unified: Table 14]
866ncEffectNamedSend :: String -> Message -> NC ()
867ncEffectNamedSend label msg = do
868 mPid <- gets (^. registeredHereFor label)
869 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
870 forM_ mPid $ \pid -> postMessage pid msg
873ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
874ncEffectLocalSend node to msg =
875 liftIO $ withLocalProc node to $ \p -> do
876 enqueue (processQueue p) msg
877 trace node (TraceEvReceived to msg)
880ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
881ncEffectLocalPortSend from msg = do
883 let pid = sendPortProcessId from
884 cid = sendPortLocalId from
885 liftIO $ withLocalProc node pid $ \proc -> do
886 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
888 -- in the unlikely event we know nothing about this channel id,
889 -- there's little to be done - perhaps some logging/tracing though...
891 Just (TypedChannel chan') -> do
892 -- If ch is Nothing, the process has given up the read end of
893 -- the channel and we simply ignore the incoming message - this
894 ch <- deRefWeak chan'
895 forM_ ch $ \chan -> deliverChan msg chan
896 where deliverChan :: forall a . Message -> TQueue a -> IO ()
897 deliverChan (UnencodedMessage _ raw) chan' =
898 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
899 deliverChan (EncodedMessage _ _) _ =
900 -- this will not happen unless someone screws with Primitives.hs
901 error "invalid local channel delivery"
904ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
905ncEffectKill from to reason = do
907 when (isLocal node (ProcessIdentifier to)) $
908 throwException to $ ProcessKillException from reason
911ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
912ncEffectExit from to reason = do
914 when (isLocal node (ProcessIdentifier to)) $
915 throwException to $ ProcessExitException from reason
918ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
919ncEffectGetInfo from pid =
920 let lpid = processLocalId pid
921 them = (ProcessIdentifier pid)
925 withMVar (localState node) $ return . (^. localProcessWithId lpid)
927 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
928 from node (ProcessInfoNone DiedUnknownId)
930 itsLinks <- gets (^. linksFor them)
931 itsMons <- gets (^. monitorsFor them)
932 registered <- gets (^. registeredHere)
934 let reg = registeredNames registered
935 dispatch (isLocal node (ProcessIdentifier from))
939 infoNode = (processNodeId pid)
940 , infoRegisteredNames = reg
941 -- we cannot populate this field
942 , infoMessageQueueLength = Nothing
943 , infoMonitors = Set.toList itsMons
944 , infoLinks = Set.toList itsLinks
946 where dispatch :: (Serializable a, Show a)
952 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
953 dispatch False dest node pInfo = do
954 liftIO $ sendMessage node
955 (NodeIdentifier (localNodeId node))
956 (ProcessIdentifier dest)
957 WithImplicitReconnect
960 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
964--------------------------------------------------------------------------------
966--------------------------------------------------------------------------------
968notifyDied :: ProcessId -- ^ Who to notify?
969 -> Identifier -- ^ Who died?
970 -> DiedReason -- ^ How did they die?
971 -> Maybe MonitorRef -- ^ 'Nothing' for linking
973notifyDied dest src reason mRef = do
975 case (isLocal node (ProcessIdentifier dest), mRef, src) of
976 (True, Just ref, ProcessIdentifier pid) ->
977 postAsMessage dest $ ProcessMonitorNotification ref pid reason
978 (True, Just ref, NodeIdentifier nid) ->
979 postAsMessage dest $ NodeMonitorNotification ref nid reason
980 (True, Just ref, SendPortIdentifier cid) ->
981 postAsMessage dest $ PortMonitorNotification ref cid reason
982 (True, Nothing, ProcessIdentifier pid) ->
983 throwException dest $ ProcessLinkException pid reason
984 (True, Nothing, NodeIdentifier pid) ->
985 throwException dest $ NodeLinkException pid reason
986 (True, Nothing, SendPortIdentifier pid) ->
987 throwException dest $ PortLinkException pid reason
989 -- The change in sender comes from [Unified: Table 10]
990 liftIO $ sendBinary node
991 (NodeIdentifier $ localNodeId node)
992 (NodeIdentifier $ processNodeId dest)
993 WithImplicitReconnect
995 { ctrlMsgSender = NodeIdentifier (localNodeId node)
996 , ctrlMsgSignal = Died src reason
999-- | [Unified: Table 8]
1000destNid :: ProcessSignal -> Maybe NodeId
1001destNid (Link ident) = Just $ nodeOf ident
1002destNid (Unlink ident) = Just $ nodeOf ident
1003destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1004destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1005destNid (Spawn _ _) = Nothing
1006destNid (Register _ _ _ _) = Nothing
1007destNid (WhereIs _) = Nothing
1008destNid (NamedSend _ _) = Nothing
1009-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1010-- then when a local process dies the monitoring/linking machinery will take
1011-- care of notifying remote nodes
1012destNid (Died _ _) = Nothing
1013destNid (Kill pid _) = Just $ processNodeId pid
1014destNid (Exit pid _) = Just $ processNodeId pid
1015destNid (GetInfo pid) = Just $ processNodeId pid
1016destNid (LocalSend pid _) = Just $ processNodeId pid
1017destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1018destNid (SigShutdown) = Nothing
1020-- | Check if a process is local to our own node
1021isLocal :: LocalNode -> Identifier -> Bool
1022isLocal nid ident = nodeOf ident == localNodeId nid
1024-- | Lookup a local closure
1025unClosure :: Typeable a => Closure a -> NC (Either String a)
1026unClosure closure = do
1027 rtable <- remoteTable <$> ask
1028 return (Static.unclosure rtable closure)
1030-- | Check if an identifier refers to a valid local object
1031isValidLocalIdentifier :: Identifier -> NC Bool
1032isValidLocalIdentifier ident = do
1034 liftIO . withMVar (localState node) $ \nSt ->
1036 NodeIdentifier nid ->
1037 return $ nid == localNodeId node
1038 ProcessIdentifier pid -> do
1039 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1040 return $ isJust mProc
1041 SendPortIdentifier cid -> do
1042 let pid = sendPortProcessId cid
1043 mProc = nSt ^. localProcessWithId (processLocalId pid)
1045 Nothing -> return False
1046 Just proc -> withMVar (processState proc) $ \pSt -> do
1047 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1050--------------------------------------------------------------------------------
1051-- Messages to local processes --
1052--------------------------------------------------------------------------------
1054postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1055postAsMessage pid = postMessage pid . createUnencodedMessage
1057postMessage :: ProcessId -> Message -> NC ()
1058postMessage pid msg = do
1060 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1062throwException :: Exception e => ProcessId -> e -> NC ()
1063throwException pid e = do
1065 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1067withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1068withLocalProc node pid p =
1069 -- By [Unified: table 6, rule missing_process] messages to dead processes
1070 -- can silently be dropped
1071 let lpid = processLocalId pid in do
1072 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1075--------------------------------------------------------------------------------
1077--------------------------------------------------------------------------------
1079links :: Accessor NCState (Map Identifier (Set ProcessId))
1080links = accessor _links (\ls st -> st { _links = ls })
1082monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1083monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1085registeredHere :: Accessor NCState (Map String ProcessId)
1086registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1088registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1089registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1091linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1092linksFor ident = links >>> DAC.mapDefault Set.empty ident
1094monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1095monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1097registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1098registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1100registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1101registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1103-- | @splitNotif ident@ splits a notifications map into those
1104-- notifications that should trigger when 'ident' fails and those links that
1107-- There is a hierarchy between identifiers: failure of a node implies failure
1108-- of all processes on that node, and failure of a process implies failure of
1109-- all typed channels to that process. In other words, if 'ident' refers to a
1110-- node, then the /should trigger/ set will include
1112-- * the notifications for the node specifically
1113-- * the notifications for processes on that node, and
1114-- * the notifications for typed channels to processes on that node.
1116-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1119-- * the notifications for that process specifically and
1120-- * the notifications for typed channels to that process.
1122-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1123splitNotif :: Identifier
1125 -> (Map Identifier a, Map Identifier a)
1126splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1128--------------------------------------------------------------------------------
1130--------------------------------------------------------------------------------
1132-- | Modify and evaluate the state
1133modify' :: MonadState s m => (s -> s) -> m ()
1134modify' 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