2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE BangPatterns #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE RecordWildCards #-}
8{-# LANGUAGE MagicHash #-}
12module Control.Distributed.Process.Node
22-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
24import System.IO (fixIO, hPutStrLn, stderr)
25import System.Mem.Weak (Weak, deRefWeak)
26import qualified Data.ByteString.Lazy as BSL (fromChunks)
27import Data.Binary (decode)
29import qualified Data.Map as Map
41import qualified Data.Set as Set
48import Data.Foldable (forM_)
49import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
50import Data.Typeable (Typeable)
51import Control.Category ((>>>))
52import Control.Applicative
53import Control.Monad (void, when, join)
54import Control.Monad.IO.Class (MonadIO, liftIO)
55import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
56import qualified Control.Monad.State.Strict as StateT (get, put)
57import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
58import Control.Exception
63 , uninterruptibleMask_
67import qualified Control.Exception as Exception
73import Control.Concurrent (forkIO, forkIOWithUnmask, killThread)
74import Control.Distributed.Process.Internal.StrictMVar
83import Control.Concurrent.Chan (newChan, writeChan, readChan)
84import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
85import Control.Concurrent.STM
88import Control.Distributed.Process.Internal.CQueue
95import qualified Network.Transport as NT
109 , Reliability(ReliableOrdered)
111import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
112import System.Random (randomIO)
113import Control.Distributed.Static (RemoteTable, Closure)
114import qualified Control.Distributed.Static as Static
118import Control.Distributed.Process.Internal.Types
125 , ValidLocalNodeState(..)
126 , withValidLocalState
127 , modifyValidLocalState
129 , LocalProcessState(..)
141 , NodeClosedException(..)
142 , ProcessMonitorNotification(..)
143 , NodeMonitorNotification(..)
144 , PortMonitorNotification(..)
145 , ProcessExitException(..)
146 , ProcessLinkException(..)
147 , NodeLinkException(..)
148 , PortLinkException(..)
150 , DidUnlinkProcess(..)
160 , ProcessInfoNone(..)
167 , createUnencodedMessage
168 , unsafeCreateUnencodedMessage
170 , firstNonReservedProcessId
171 , ImplicitReconnect(WithImplicitReconnect)
173import Control.Distributed.Process.Management.Internal.Agent
176import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
179import Control.Distributed.Process.Management.Internal.Trace.Tracer
182import Control.Distributed.Process.Management.Internal.Trace.Types
188import Control.Distributed.Process.Management.Internal.Types
191import Control.Distributed.Process.Serializable (Serializable)
192import Control.Distributed.Process.Internal.Messaging
194 , closeImplicitReconnections
197import Control.Distributed.Process.Internal.Primitives
204import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
205import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
206import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
207import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
211import Control.Monad.Catch (try)
212import GHC.IO (IO(..), unsafeUnmask)
213import GHC.Base ( maskAsyncExceptions# )
218-- Remove these definitions when the fix for
219-- https://ghc.haskell.org/trac/ghc/ticket/10149
220-- is included in all supported compilers:
222block (IO io) = IO $ maskAsyncExceptions# io
223unblock :: IO a -> IO a
224unblock = unsafeUnmask
226--------------------------------------------------------------------------------
228--------------------------------------------------------------------------------
230initRemoteTable :: RemoteTable
231initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
233-- | Initialize a new local node.
234newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
235newLocalNode transport rtable = do
236 mEndPoint <- NT.newEndPoint transport
238 Left ex -> throwIO ex
240 localNode <- createBareLocalNode endPoint rtable
241 startServiceProcesses localNode
244-- | Create a new local node (without any service processes running)
245createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
246createBareLocalNode endPoint rtable = do
248 state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
249 { _localProcesses = Map.empty
250 , _localPidCounter = firstNonReservedProcessId
251 , _localPidUnique = unq
252 , _localConnections = Map.empty
255 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
256 , localEndPoint = endPoint
258 , localCtrlChan = ctrlChan
259 , localEventBus = MxEventBusInitialising
260 , remoteTable = rtable
262 tracedNode <- startMxAgent node
264 -- Once the NC terminates, the endpoint isn't much use,
265 void $ forkIO $ Exception.finally (runNodeController tracedNode)
266 (NT.closeEndPoint (localEndPoint node))
268 -- whilst a closed/failing endpoint will terminate the NC
269 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
275 writeChan (localCtrlChan node) NCMsg
276 { ctrlMsgSender = NodeIdentifier (localNodeId node)
277 , ctrlMsgSignal = SigShutdown
280startMxAgent :: LocalNode -> IO LocalNode
281startMxAgent node = do
282 -- see note [tracer/forkProcess races]
283 let fork = forkProcess node
284 mv <- MVar.newEmptyMVar
285 pid <- fork $ mxAgentController fork mv
286 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
287 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
289startDefaultTracer :: LocalNode -> IO ()
290startDefaultTracer node' = do
291 let t = localEventBus node'
293 MxEventBus _ (Tracer pid _) _ _ -> do
294 runProcess node' $ register "trace.controller" pid
295 pid' <- forkProcess node' defaultTracer
296 enableTrace (localEventBus node') pid'
297 runProcess node' $ register "tracer.initial" pid'
300-- TODO: we need a better mechanism for defining and registering services
302-- | Start and register the service processes on a node
303startServiceProcesses :: LocalNode -> IO ()
304startServiceProcesses node = do
305 -- tracing /spawns/ relies on the tracer being enabled, but we start
306 -- the default tracer first, even though it might @nsend@ to the logger
307 -- before /that/ process has started - this is a totally harmless race
308 -- however, so we deliberably ignore it
309 startDefaultTracer node
310 logger <- forkProcess node loop
312 register "logger" logger
313 -- The trace.logger is used for tracing to the console to avoid feedback
314 -- loops during tracing if the user reregisters the "logger" with a custom
315 -- process which uses 'send' or other primitives which are traced.
316 register "trace.logger" logger
320 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
321 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
323 , match $ \((time, string) :: (String, String)) -> do
324 -- this is a 'trace' message from the local node tracer
325 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
327 , match $ \(ch :: SendPort ()) -> -- a shutdown request
331-- | Force-close a local node, killing all processes on that node.
332closeLocalNode :: LocalNode -> IO ()
333closeLocalNode node = do
334 -- Kill processes after refilling the mvar. Otherwise, there is potential for
335 -- deadlock as a dying process tries to get the mvar while masking exceptions
337 join $ modifyMVar (localState node) $ \st -> case st of
338 LocalNodeValid vst -> do
339 return ( LocalNodeClosed
340 , forM_ (vst ^. localProcesses) $ \lproc ->
341 -- Semantics of 'throwTo' guarantee that target thread will get
342 -- delivered an exception. Therefore, target thread will be
343 -- killed eventually and that's as good as we can do. No need
344 -- to wait for thread to actually finish dying.
345 killThread (processThread lproc)
347 LocalNodeClosed -> return (LocalNodeClosed, return ())
348 -- This call will have the effect of shutting down the NC as well (see
349 -- 'createBareLocalNode').
350 NT.closeEndPoint (localEndPoint node)
352-- | Run a process on a local node and wait for it to finish
353runProcess :: LocalNode -> Process () -> IO ()
354runProcess node proc = do
356 -- TODO; When forkProcess inherits the masking state, protect the forked
357 -- thread against async exceptions that could occur before 'try' is evaluated.
358 void $ forkProcess node $ try proc >>= liftIO . putMVar done
359 takeMVar done >>= either (throwIO :: SomeException -> IO a) return
361-- | Spawn a new process on a local node
362forkProcess :: LocalNode -> Process () -> IO ProcessId
363forkProcess node proc = do
364 ms <- getMaskingState
365 modifyMVarMasked (localState node) (startProcess ms)
367 startProcess :: MaskingState
369 -> IO (LocalNodeState, ProcessId)
370 startProcess ms (LocalNodeValid vst) = do
371 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
372 , lpidUnique = vst ^. localPidUnique
374 let pid = ProcessId { processNodeId = localNodeId node
375 , processLocalId = lpid
377 pst <- newMVar LocalProcessState { _monitorCounter = 0
379 , _channelCounter = 0
380 , _typedChannels = Map.empty
383 weakQueue <- mkWeakCQueue queue (return ())
384 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
385 let lproc = LocalProcess { processQueue = queue
386 , processWeakQ = weakQueue
389 , processThread = tid
392 -- Rewrite this code when this is fixed:
393 -- https://ghc.haskell.org/trac/ghc/ticket/10149
394 let unmask = case ms of
396 MaskedInterruptible -> block
397 MaskedUninterruptible -> id
398 tid' <- uninterruptibleMask_ $ forkIO $ do
399 reason <- Exception.catches
400 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
401 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
402 mMsg <- unwrapMessage msg :: IO (Maybe String)
404 Nothing -> return $ DiedException $ show ex
405 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
407 (return . DiedException . (show :: SomeException -> String)))]
409 -- [Unified: Table 4, rules termination and exiting]
410 mconns <- modifyValidLocalState node (cleanupProcess pid)
411 -- XXX: Revisit after agreeing on the bigger picture for the semantics
412 -- of transport operations.
413 -- https://github.com/haskell-distributed/distributed-process/issues/204
414 forM_ mconns $ forkIO . mapM_ NT.close
416 writeChan (localCtrlChan node) NCMsg
417 { ctrlMsgSender = ProcessIdentifier pid
418 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
422 -- see note [tracer/forkProcess races]
423 trace node (MxSpawned pid)
425 if lpidCounter lpid == maxBound
427 -- TODO: this doesn't look right at all - how do we know
428 -- that newUnique represents a process id that is available!?
429 newUnique <- randomIO
430 return ( LocalNodeValid
431 $ (localProcessWithId lpid ^= Just lproc)
432 . (localPidCounter ^= firstNonReservedProcessId)
433 . (localPidUnique ^= newUnique)
438 return ( LocalNodeValid
439 $ (localProcessWithId lpid ^= Just lproc)
440 . (localPidCounter ^: (+ 1))
444 startProcess _ LocalNodeClosed =
445 throwIO $ NodeClosedException $ localNodeId node
447 cleanupProcess :: ProcessId
448 -> ValidLocalNodeState
449 -> IO (ValidLocalNodeState, [NT.Connection])
450 cleanupProcess pid vst = do
451 let pid' = ProcessIdentifier pid
452 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
453 return ( (localProcessWithId (processLocalId pid) ^= Nothing)
454 . (localConnections ^= unaffected)
456 , map fst $ Map.elems affected
459-- note [tracer/forkProcess races]
461-- Our startTracing function uses forkProcess to start the trace controller
462-- process, and of course forkProcess attempts to call traceEvent once the
463-- process has started. This is harmless, as the localEventBus is not updated
464-- until /after/ the initial forkProcess completes, so the first call to
465-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
468--------------------------------------------------------------------------------
469-- Handle incoming messages --
470--------------------------------------------------------------------------------
472type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
476 | ToProc ProcessId (Weak (CQueue Message))
477 | ToChan TypedChannel
480data ConnectionState = ConnectionState {
481 _incoming :: !(Map NT.ConnectionId IncomingConnection)
482 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
485initConnectionState :: ConnectionState
486initConnectionState = ConnectionState {
487 _incoming = Map.empty
488 , _incomingFrom = Map.empty
491incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
492incoming = accessor _incoming (\conns st -> st { _incoming = conns })
494incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
495incomingAt cid = incoming >>> DAC.mapMaybe cid
497incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
498incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
500 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
502handleIncomingMessages :: LocalNode -> IO ()
503handleIncomingMessages node = go initConnectionState
504 `Exception.catch` \(NodeClosedException _) -> return ()
506 go :: ConnectionState -> IO ()
508 event <- NT.receive endpoint
510 NT.ConnectionOpened cid rel theirAddr ->
511 if rel == NT.ReliableOrdered
513 trace node (MxConnected cid theirAddr)
515 (incomingAt cid ^= Just (theirAddr, Uninit))
516 . (incomingFrom theirAddr ^: Set.insert cid)
519 else invalidRequest cid st $
520 "attempt to connect with unsupported reliability " ++ show rel
521 NT.Received cid payload ->
522 case st ^. incomingAt cid of
523 Just (_, ToProc pid weakQueue) -> do
524 mQueue <- deRefWeak weakQueue
525 forM_ mQueue $ \queue -> do
526 -- TODO: if we find that the queue is Nothing, should we remove
527 -- it from the NC state? (and same for channels, below)
528 let msg = payloadToMessage payload
529 enqueue queue msg -- 'enqueue' is strict
530 trace node (MxReceived pid msg)
532 Just (_, ToChan (TypedChannel chan')) -> do
533 mChan <- deRefWeak chan'
534 -- If mChan is Nothing, the process has given up the read end of
535 -- the channel and we simply ignore the incoming message
536 forM_ mChan $ \chan -> atomically $
537 -- We make sure the message is fully decoded when it is enqueued
538 writeTQueue chan $! decode (BSL.fromChunks payload)
540 Just (_, ToNode) -> do
541 let ctrlMsg = decode . BSL.fromChunks $ payload
542 writeChan ctrlChan $! ctrlMsg
544 Just (src, Uninit) ->
545 case decode (BSL.fromChunks payload) of
546 ProcessIdentifier pid -> do
547 let lpid = processLocalId pid
548 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
551 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
553 invalidRequest cid st $
554 "incoming attempt to connect to unknown process "
556 SendPortIdentifier chId -> do
557 let lcid = sendPortLocalId chId
558 lpid = processLocalId (sendPortProcessId chId)
559 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
562 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
565 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
567 invalidRequest cid st $
568 "incoming attempt to connect to unknown channel of"
569 ++ " process " ++ show (sendPortProcessId chId)
571 invalidRequest cid st $
572 "incoming attempt to connect to channel of unknown"
573 ++ " process " ++ show (sendPortProcessId chId)
574 NodeIdentifier nid ->
575 if nid == localNodeId node
576 then go (incomingAt cid ^= Just (src, ToNode) $ st)
577 else invalidRequest cid st $
578 "incoming attempt to connect to a different node -"
579 ++ " I'm " ++ show (localNodeId node)
580 ++ " but the remote peer wants to connect to "
583 invalidRequest cid st
584 "message received from an unknown connection"
585 NT.ConnectionClosed cid ->
586 case st ^. incomingAt cid of
588 invalidRequest cid st "closed unknown connection"
590 trace node (MxDisconnected cid src)
591 go ( (incomingAt cid ^= Nothing)
592 . (incomingFrom src ^: Set.delete cid)
595 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
596 -- [Unified table 9, rule node_disconnect]
597 let nid = NodeIdentifier $ NodeId theirAddr
598 writeChan ctrlChan NCMsg
599 { ctrlMsgSender = nid
600 , ctrlMsgSignal = Died nid DiedDisconnect
602 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
603 closeImplicitReconnections node nid
604 go ( (incomingFrom theirAddr ^= Set.empty)
605 . (incoming ^: Map.filterWithKey (const . notLost))
608 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
609 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
610 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
611 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
614 NT.ReceivedMulticast _ _ ->
615 -- If we received a multicast message, something went horribly wrong
616 -- and we just give up
617 fail "Cloud Haskell fatal error: received unexpected multicast"
619 invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
620 invalidRequest cid st msg = do
621 -- TODO: We should treat this as a fatal error on the part of the remote
622 -- node. That is, we should report the remote node as having died, and we
623 -- should close incoming connections (this requires a Transport layer
625 traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
626 ++ " (" ++ msg ++ "): "
629 go ( incomingAt cid ^= Nothing
633 endpoint = localEndPoint node
634 ctrlChan = localCtrlChan node
636--------------------------------------------------------------------------------
637-- Top-level access to the node controller --
638--------------------------------------------------------------------------------
640runNodeController :: LocalNode -> IO ()
641runNodeController node =
642 runReaderT (evalStateT (unNC nodeController) initNCState) node
643 `Exception.catch` \(NodeClosedException _) -> return ()
645--------------------------------------------------------------------------------
646-- Internal data types --
647--------------------------------------------------------------------------------
649data NCState = NCState
650 { -- Mapping from remote processes to linked local processes
651 _links :: !(Map Identifier (Set ProcessId))
652 -- Mapping from remote processes to monitoring local processes
653 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
654 -- Process registry: names and where they live, mapped to the PIDs
655 , _registeredHere :: !(Map String ProcessId)
656 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
659newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
660 deriving ( Applicative
665 , MonadReader LocalNode
668initNCState :: NCState
669initNCState = NCState { _links = Map.empty
670 , _monitors = Map.empty
671 , _registeredHere = Map.empty
672 , _registeredOnNodes = Map.empty
675-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
676-- type is deliberately not exported so it cannot be caught explicitly.
677data ProcessKillException =
678 ProcessKillException !ProcessId !String
681instance Exception ProcessKillException
682instance Show ProcessKillException where
683 show (ProcessKillException pid reason) =
684 "killed-by=" ++ show pid ++ ",reason=" ++ reason
686ncSendToProcess :: ProcessId -> Message -> NC ()
687ncSendToProcess = ncSendToProcessAndTrace True
689ncSendToProcessAndTrace :: Bool -> ProcessId -> Message -> NC ()
690ncSendToProcessAndTrace shouldTrace pid msg = do
692 if processNodeId pid == localNodeId node
693 then ncEffectLocalSendAndTrace shouldTrace node pid msg
694 else liftIO $ sendBinary node
695 (NodeIdentifier $ localNodeId node)
696 (NodeIdentifier $ processNodeId pid)
697 WithImplicitReconnect
698 NCMsg { ctrlMsgSender = NodeIdentifier $ localNodeId node
699 , ctrlMsgSignal = UnreliableSend (processLocalId pid) msg
702ncSendToNode :: NodeId -> NCMsg -> NC ()
703ncSendToNode to msg = do
705 liftIO $ if to == localNodeId node
706 then writeChan (localCtrlChan node) $! msg
708 (NodeIdentifier $ localNodeId node)
710 WithImplicitReconnect
713--------------------------------------------------------------------------------
714-- Tracing/Debugging --
715--------------------------------------------------------------------------------
717-- [Issue #104 / DP-13]
719traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
720traceNotifyDied node ident reason =
721 -- TODO: sendPortDied notifications
722 liftIO $ withLocalTracer node $ \t ->
724 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
725 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
728traceEventFmtIO :: LocalNode
732traceEventFmtIO node fmt args =
733 withLocalTracer node $ \t -> traceLogFmt t fmt args
735trace :: LocalNode -> MxEvent -> IO ()
736trace node ev = withLocalTracer node $ \t -> traceEvent t ev
738withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
739withLocalTracer node act = act (localEventBus node)
741--------------------------------------------------------------------------------
742-- Core functionality --
743--------------------------------------------------------------------------------
746nodeController :: NC ()
750 msg <- liftIO $ readChan (localCtrlChan node)
752 -- [Unified: Table 7, rule nc_forward]
753 case destNid (ctrlMsgSignal msg) of
754 Just nid' | nid' /= localNodeId node ->
755 ncSendToNode nid' msg
760 NCMsg (ProcessIdentifier from) (Link them) ->
761 ncEffectMonitor from them Nothing
762 NCMsg (ProcessIdentifier from) (Monitor ref) ->
763 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
764 NCMsg (ProcessIdentifier from) (Unlink them) ->
765 ncEffectUnlink from them
766 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
767 ncEffectUnmonitor from ref
768 NCMsg _from (Died ident reason) ->
769 ncEffectDied ident reason
770 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
771 ncEffectSpawn from proc ref
772 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
773 ncEffectRegister from label atnode pid force
774 NCMsg (ProcessIdentifier from) (WhereIs label) ->
775 ncEffectWhereIs from label
776 NCMsg _ (NamedSend label msg') ->
777 ncEffectNamedSend label msg'
778 NCMsg _ (UnreliableSend lpid msg') ->
779 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
780 NCMsg _ (LocalSend to msg') ->
781 ncEffectLocalSend node to msg'
782 NCMsg _ (LocalPortSend to msg') ->
783 ncEffectLocalPortSend to msg'
784 NCMsg (ProcessIdentifier from) (Kill to reason) ->
785 ncEffectKill from to reason
786 NCMsg (ProcessIdentifier from) (Exit to reason) ->
787 ncEffectExit from to reason
788 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
789 ncEffectGetInfo from pid
790 NCMsg _ SigShutdown ->
792 NT.closeEndPoint (localEndPoint node)
793 `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
794 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
795 ncEffectGetNodeStats from nid
797 error $ "nodeController: unexpected message " ++ show unexpected
799-- [Unified: Table 10]
800ncEffectMonitor :: ProcessId -- ^ Who's watching?
801 -> Identifier -- ^ Who's being watched?
802 -> Maybe MonitorRef -- ^ 'Nothing' to link
804ncEffectMonitor from them mRef = do
807 if not (isLocal node them)
809 else isValidLocalIdentifier them
810 case (shouldLink, isLocal node (ProcessIdentifier from)) of
811 (True, _) -> -- [Unified: first rule]
813 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
814 Nothing -> modify' $ linksFor them ^: Set.insert from
815 (False, True) -> -- [Unified: second rule]
816 notifyDied from them DiedUnknownId mRef
817 (False, False) -> -- [Unified: third rule]
818 -- TODO: this is the right sender according to the Unified semantics,
819 -- but perhaps having 'them' as the sender would make more sense
820 -- (see also: notifyDied)
821 ncSendToNode (processNodeId from) $ NCMsg
822 { ctrlMsgSender = NodeIdentifier (localNodeId node)
823 , ctrlMsgSignal = Died them DiedUnknownId
826-- [Unified: Table 11]
827ncEffectUnlink :: ProcessId -> Identifier -> NC ()
828ncEffectUnlink from them = do
830 when (isLocal node (ProcessIdentifier from)) $
832 ProcessIdentifier pid ->
833 postAsMessage from $ DidUnlinkProcess pid
834 NodeIdentifier nid ->
835 postAsMessage from $ DidUnlinkNode nid
836 SendPortIdentifier cid ->
837 postAsMessage from $ DidUnlinkPort cid
838 modify' $ linksFor them ^: Set.delete from
840-- [Unified: Table 11]
841ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
842ncEffectUnmonitor from ref = do
844 when (isLocal node (ProcessIdentifier from)) $
845 postAsMessage from $ DidUnmonitor ref
846 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
848-- [Unified: Table 12]
849ncEffectDied :: Identifier -> DiedReason -> NC ()
850ncEffectDied ident reason = do
852 traceNotifyDied node ident reason
853 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
854 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
856-- _registry :: !(Map (String,NodeId) ProcessId)
858 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
860 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
862 when (localOnly <= isLocal node (ProcessIdentifier us)) $
863 notifyDied us them reason Nothing
865 forM_ (Map.toList affectedMons) $ \(them, refs) ->
866 forM_ refs $ \(us, ref) ->
867 when (localOnly <= isLocal node (ProcessIdentifier us)) $
868 notifyDied us them reason (Just ref)
870 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
872 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
874 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
875 mapM (\(pid,nidlist) ->
876 case ident `impliesDeathOf` ProcessIdentifier pid of
878 do forM_ nidlist $ \(nid,_) ->
879 when (not $ isLocal node (NodeIdentifier nid))
880 (forwardNameDeath node nid)
882 False -> return $ Just (pid,nidlist) )
883 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
885 forwardNameDeath node nid = ncSendToNode nid
886 NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
887 , ctrlMsgSignal = Died ident reason
890-- [Unified: Table 13]
891ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
892ncEffectSpawn pid cProc ref = do
893 mProc <- unClosure cProc
894 -- If the closure does not exist, we spawn a process that throws an exception
895 -- This allows the remote node to find out what's happening
897 let proc = case mProc of
898 Left err -> fail $ "Error: Could not resolve closure: " ++ err
901 pid' <- liftIO $ forkProcess node proc
902 ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
904-- Unified semantics does not explicitly describe how to implement 'register',
905-- but mentions it's "very similar to nsend" (Table 14)
906-- We send a response indicated if the operation is invalid
907ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
908ncEffectRegister from label atnode mPid reregistration = do
910 currentVal <- gets (^. registeredHereFor label)
913 Nothing -> -- unregister request
914 return $ isJust currentVal
915 Just thepid -> -- register request
916 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
917 return $ (isNothing currentVal /= reregistration) &&
918 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
919 if isLocal node (NodeIdentifier atnode)
921 do modify' $ registeredHereFor label ^= mPid
922 updateRemote node currentVal mPid
924 (Just p) -> liftIO $ trace node (MxRegistered p label)
925 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
926 newVal <- gets (^. registeredHereFor label)
927 ncSendToProcess from $ unsafeCreateUnencodedMessage $
928 RegisterReply label isOk newVal
930 case reregistration of
932 False -> flip incList
935 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
936 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
937 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
938 forward node (processNodeId newval) (Register label atnode (Just newval) False)
939 updateRemote node Nothing (Just newval) =
940 forward node (processNodeId newval) (Register label atnode (Just newval) False)
941 updateRemote node (Just oldval) Nothing =
942 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
943 updateRemote _ _ _ = return ()
944 maybeify f Nothing = unmaybeify $ f []
945 maybeify f (Just x) = unmaybeify $ f x
947 unmaybeify [] = Nothing
948 unmaybeify x = Just x
949 incList [] tag = [(tag,1)]
950 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
951 incList (x:xs) tag = x : incList xs tag
953 decList ((atag,1):xs) tag | atag == tag = xs
954 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
955 decList (x:xs) tag = x:decList xs tag
956 forward node to reg =
957 when (not $ isLocal node (NodeIdentifier to)) $
958 ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
959 , ctrlMsgSignal = reg
963-- Unified semantics does not explicitly describe 'whereis'
964ncEffectWhereIs :: ProcessId -> String -> NC ()
965ncEffectWhereIs from label = do
966 mPid <- gets (^. registeredHereFor label)
967 ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
969-- [Unified: Table 14]
970ncEffectNamedSend :: String -> Message -> NC ()
971ncEffectNamedSend label msg = do
972 mPid <- gets (^. registeredHereFor label)
973 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
975 -- If this is a trace message we don't trace it to avoid entering a loop
976 -- where trace messages produce more trace messages.
977 ncSendToProcessAndTrace (label /= "trace.logger") to msg
980ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
981ncEffectLocalSend = ncEffectLocalSendAndTrace True
983ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
984ncEffectLocalSendAndTrace shouldTrace node to msg =
985 liftIO $ withLocalProc node to $ \p -> do
986 enqueue (processQueue p) msg
987 when shouldTrace $ trace node (MxReceived to msg)
990ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
991ncEffectLocalPortSend from msg = do
993 let pid = sendPortProcessId from
994 cid = sendPortLocalId from
995 liftIO $ withLocalProc node pid $ \proc -> do
996 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
998 -- in the unlikely event we know nothing about this channel id,
999 -- there's little to be done - perhaps some logging/tracing though...
1000 Nothing -> return ()
1001 Just (TypedChannel chan') -> do
1002 -- If ch is Nothing, the process has given up the read end of
1003 -- the channel and we simply ignore the incoming message - this
1004 ch <- deRefWeak chan'
1005 forM_ ch $ \chan -> deliverChan msg chan
1006 where deliverChan :: forall a . Message -> TQueue a -> IO ()
1007 deliverChan (UnencodedMessage _ raw) chan' =
1008 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1009 deliverChan (EncodedMessage _ _) _ =
1010 -- this will not happen unless someone screws with Primitives.hs
1011 error "invalid local channel delivery"
1014ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
1015ncEffectKill from to reason = do
1017 when (isLocal node (ProcessIdentifier to)) $
1018 throwException to $ ProcessKillException from reason
1021ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
1022ncEffectExit from to reason = do
1024 when (isLocal node (ProcessIdentifier to)) $
1025 throwException to $ ProcessExitException from reason
1028ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
1029ncEffectGetInfo from pid =
1030 let lpid = processLocalId pid
1031 them = (ProcessIdentifier pid)
1034 mProc <- liftIO $ withValidLocalState node
1035 $ return . (^. localProcessWithId lpid)
1037 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1038 from (ProcessInfoNone DiedUnknownId)
1040 itsLinks <- gets (^. linksFor them)
1041 itsMons <- gets (^. monitorsFor them)
1042 registered <- gets (^. registeredHere)
1043 size <- liftIO $ queueSize $ processQueue $ proc
1045 let reg = registeredNames registered
1046 dispatch (isLocal node (ProcessIdentifier from))
1049 infoNode = (processNodeId pid)
1050 , infoRegisteredNames = reg
1051 , infoMessageQueueLength = size
1052 , infoMonitors = Set.toList itsMons
1053 , infoLinks = Set.toList itsLinks
1055 where dispatch :: (Serializable a, Show a)
1060 dispatch True dest pInfo = postAsMessage dest $ pInfo
1061 dispatch False dest pInfo =
1062 ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
1064 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1068ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1069ncEffectGetNodeStats from _nid = do
1071 ncState <- StateT.get
1072 nodeState <- liftIO $ withValidLocalState node return
1075 nodeStatsNode = localNodeId node
1076 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1077 , nodeStatsMonitors = Map.size $ ncState ^. monitors
1078 , nodeStatsLinks = Map.size $ ncState ^. links
1079 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1081 postAsMessage from stats
1083--------------------------------------------------------------------------------
1085--------------------------------------------------------------------------------
1087notifyDied :: ProcessId -- ^ Who to notify?
1088 -> Identifier -- ^ Who died?
1089 -> DiedReason -- ^ How did they die?
1090 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1092notifyDied dest src reason mRef = do
1094 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1095 (True, Just ref, ProcessIdentifier pid) ->
1096 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1097 (True, Just ref, NodeIdentifier nid) ->
1098 postAsMessage dest $ NodeMonitorNotification ref nid reason
1099 (True, Just ref, SendPortIdentifier cid) ->
1100 postAsMessage dest $ PortMonitorNotification ref cid reason
1101 (True, Nothing, ProcessIdentifier pid) ->
1102 throwException dest $ ProcessLinkException pid reason
1103 (True, Nothing, NodeIdentifier pid) ->
1104 throwException dest $ NodeLinkException pid reason
1105 (True, Nothing, SendPortIdentifier pid) ->
1106 throwException dest $ PortLinkException pid reason
1108 -- The change in sender comes from [Unified: Table 10]
1109 ncSendToNode (processNodeId dest) $ NCMsg
1110 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1111 , ctrlMsgSignal = Died src reason
1114-- | [Unified: Table 8]
1115destNid :: ProcessSignal -> Maybe NodeId
1116destNid (Link ident) = Just $ nodeOf ident
1117destNid (Unlink ident) = Just $ nodeOf ident
1118destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1119destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1120destNid (Spawn _ _) = Nothing
1121destNid (Register _ _ _ _) = Nothing
1122destNid (WhereIs _) = Nothing
1123destNid (NamedSend _ _) = Nothing
1124destNid (UnreliableSend _ _) = Nothing
1125-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1126-- then when a local process dies the monitoring/linking machinery will take
1127-- care of notifying remote nodes
1128destNid (Died _ _) = Nothing
1129destNid (Kill pid _) = Just $ processNodeId pid
1130destNid (Exit pid _) = Just $ processNodeId pid
1131destNid (GetInfo pid) = Just $ processNodeId pid
1132destNid (GetNodeStats nid) = Just nid
1133destNid (LocalSend pid _) = Just $ processNodeId pid
1134destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1135destNid (SigShutdown) = Nothing
1137-- | Check if a process is local to our own node
1138isLocal :: LocalNode -> Identifier -> Bool
1139isLocal nid ident = nodeOf ident == localNodeId nid
1141-- | Lookup a local closure
1142unClosure :: Typeable a => Closure a -> NC (Either String a)
1143unClosure closure = do
1144 rtable <- remoteTable <$> ask
1145 return (Static.unclosure rtable closure)
1147-- | Check if an identifier refers to a valid local object
1148isValidLocalIdentifier :: Identifier -> NC Bool
1149isValidLocalIdentifier ident = do
1151 liftIO . withValidLocalState node $ \nSt ->
1153 NodeIdentifier nid ->
1154 return $ nid == localNodeId node
1155 ProcessIdentifier pid -> do
1156 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1157 return $ isJust mProc
1158 SendPortIdentifier cid -> do
1159 let pid = sendPortProcessId cid
1160 mProc = nSt ^. localProcessWithId (processLocalId pid)
1162 Nothing -> return False
1163 Just proc -> withMVar (processState proc) $ \pSt -> do
1164 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1167--------------------------------------------------------------------------------
1168-- Messages to local processes --
1169--------------------------------------------------------------------------------
1171postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1172postAsMessage pid = postMessage pid . createUnencodedMessage
1174postMessage :: ProcessId -> Message -> NC ()
1175postMessage pid msg = do
1177 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1179throwException :: Exception e => ProcessId -> e -> NC ()
1180throwException pid e = do
1182 -- throwTo blocks until the exception is received by the target thread.
1183 -- We cannot easily make it happen asynchronpusly because then 'unlink'
1184 -- semantics would break.
1185 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1187withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1188withLocalProc node pid p =
1189 -- By [Unified: table 6, rule missing_process] messages to dead processes
1190 -- can silently be dropped
1191 let lpid = processLocalId pid in do
1192 join $ withValidLocalState node $ \vst ->
1193 return $ forM_ (vst ^. localProcessWithId lpid) p
1195--------------------------------------------------------------------------------
1197--------------------------------------------------------------------------------
1199links :: Accessor NCState (Map Identifier (Set ProcessId))
1200links = accessor _links (\ls st -> st { _links = ls })
1202monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1203monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1205registeredHere :: Accessor NCState (Map String ProcessId)
1206registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1208registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1209registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1211linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1212linksFor ident = links >>> DAC.mapDefault Set.empty ident
1214monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1215monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1217registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1218registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1220registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1221registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1223-- | @splitNotif ident@ splits a notifications map into those
1224-- notifications that should trigger when 'ident' fails and those links that
1227-- There is a hierarchy between identifiers: failure of a node implies failure
1228-- of all processes on that node, and failure of a process implies failure of
1229-- all typed channels to that process. In other words, if 'ident' refers to a
1230-- node, then the /should trigger/ set will include
1232-- * the notifications for the node specifically
1233-- * the notifications for processes on that node, and
1234-- * the notifications for typed channels to processes on that node.
1236-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1239-- * the notifications for that process specifically and
1240-- * the notifications for typed channels to that process.
1242-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1243splitNotif :: Identifier
1245 -> (Map Identifier a, Map Identifier a)
1246splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1248--------------------------------------------------------------------------------
1250--------------------------------------------------------------------------------
1252-- | Modify and evaluate the state
1253modify' :: MonadState s m => (s -> s) -> m ()
1254modify' 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