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 -- however, so we deliberably ignore it
272 mPid <- startDefaultTracer node
276 runProcess node (register "tracer" pid >> register "tracer.initial" pid)
278 logger <- forkProcess node loop
279 runProcess node $ register "logger" logger
283 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
284 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
286 , match $ \((time, string) :: (String, String)) -> do
287 -- this is a 'trace' message from the local node tracer
288 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
290 , match $ \(ch :: SendPort ()) -> -- a shutdown request
294-- | Force-close a local node
296-- TODO: for now we just close the associated endpoint
297closeLocalNode :: LocalNode -> IO ()
299 -- TODO: close all our processes, surely!?
300 NT.closeEndPoint (localEndPoint node)
302-- | Run a process on a local node and wait for it to finish
303runProcess :: LocalNode -> Process () -> IO ()
304runProcess node proc = do
306 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
309-- | Spawn a new process on a local node
310forkProcess :: LocalNode -> Process () -> IO ProcessId
311forkProcess node proc = modifyMVar (localState node) startProcess
313 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
315 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
316 , lpidUnique = st ^. localPidUnique
318 let pid = ProcessId { processNodeId = localNodeId node
319 , processLocalId = lpid
321 pst <- newMVar LocalProcessState { _monitorCounter = 0
323 , _channelCounter = 0
324 , _typedChannels = Map.empty
327 weakQueue <- mkWeakCQueue queue (return ())
328 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
329 let lproc = LocalProcess { processQueue = queue
330 , processWeakQ = weakQueue
333 , processThread = tid
337 reason <- Exception.catch
338 (runLocalProcess lproc proc >> return DiedNormal)
339 (return . DiedException . (show :: SomeException -> String))
341 -- [Unified: Table 4, rules termination and exiting]
342 modifyMVar_ (localState node) (cleanupProcess pid)
343 writeChan (localCtrlChan node) NCMsg
344 { ctrlMsgSender = ProcessIdentifier pid
345 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
350 -- see note [tracer/forkProcess races]
351 trace node (TraceEvSpawned pid)
353 if lpidCounter lpid == maxBound
355 newUnique <- randomIO
356 return ( (localProcessWithId lpid ^= Just lproc)
357 . (localPidCounter ^= firstNonReservedProcessId)
358 . (localPidUnique ^= newUnique)
363 return ( (localProcessWithId lpid ^= Just lproc)
364 . (localPidCounter ^: (+ 1))
369 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
370 cleanupProcess pid st = do
371 let pid' = ProcessIdentifier pid
372 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
373 mapM_ (NT.close . fst) (Map.elems affected)
374 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
375 . (localConnections ^= unaffected)
378-- note [tracer/forkProcess races]
380-- Our startTracing function uses forkProcess to start the trace controller
381-- process, and of course forkProcess attempts to call traceEvent once the
382-- process has started. This is harmless, as the localTracer is not updated
383-- until /after/ the initial forkProcess completes, so the first call to
384-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
387--------------------------------------------------------------------------------
388-- Handle incoming messages --
389--------------------------------------------------------------------------------
391type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
395 | ToProc ProcessId (Weak (CQueue Message))
396 | ToChan TypedChannel
399data ConnectionState = ConnectionState {
400 _incoming :: !(Map NT.ConnectionId IncomingConnection)
401 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
404initConnectionState :: ConnectionState
405initConnectionState = ConnectionState {
406 _incoming = Map.empty
407 , _incomingFrom = Map.empty
410incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
411incoming = accessor _incoming (\conns st -> st { _incoming = conns })
413incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
414incomingAt cid = incoming >>> DAC.mapMaybe cid
416incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
417incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
419 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
421handleIncomingMessages :: LocalNode -> IO ()
422handleIncomingMessages node = go initConnectionState
424 go :: ConnectionState -> IO ()
426 event <- NT.receive endpoint
428 NT.ConnectionOpened cid rel theirAddr ->
429 if rel == NT.ReliableOrdered
431 trace node (TraceEvConnected cid)
433 (incomingAt cid ^= Just (theirAddr, Uninit))
434 . (incomingFrom theirAddr ^: Set.insert cid)
437 else invalidRequest cid st
438 NT.Received cid payload ->
439 case st ^. incomingAt cid of
440 Just (_, ToProc pid weakQueue) -> do
441 mQueue <- deRefWeak weakQueue
442 forM_ mQueue $ \queue -> do
443 -- TODO: if we find that the queue is Nothing, should we remove
444 -- it from the NC state? (and same for channels, below)
445 let msg = payloadToMessage payload
446 enqueue queue msg -- 'enqueue' is strict
447 trace node (TraceEvReceived pid msg)
449 Just (_, ToChan (TypedChannel chan')) -> do
450 mChan <- deRefWeak chan'
451 -- If mChan is Nothing, the process has given up the read end of
452 -- the channel and we simply ignore the incoming message
453 forM_ mChan $ \chan -> atomically $
454 -- We make sure the message is fully decoded when it is enqueued
455 writeTQueue chan $! decode (BSL.fromChunks payload)
457 Just (_, ToNode) -> do
458 let ctrlMsg = decode . BSL.fromChunks $ payload
459 writeChan ctrlChan $! ctrlMsg
461 Just (src, Uninit) ->
462 case decode (BSL.fromChunks payload) of
463 ProcessIdentifier pid -> do
464 let lpid = processLocalId pid
465 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
468 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
470 invalidRequest cid st
471 SendPortIdentifier chId -> do
472 let lcid = sendPortLocalId chId
473 lpid = processLocalId (sendPortProcessId chId)
474 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
477 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
480 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
482 invalidRequest cid st
484 invalidRequest cid st
485 NodeIdentifier nid ->
486 if nid == localNodeId node
487 then go (incomingAt cid ^= Just (src, ToNode) $ st)
488 else invalidRequest cid st
490 invalidRequest cid st
491 NT.ConnectionClosed cid ->
492 case st ^. incomingAt cid of
494 invalidRequest cid st
496 trace node (TraceEvDisconnected cid)
497 go ( (incomingAt cid ^= Nothing)
498 . (incomingFrom src ^: Set.delete cid)
501 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
502 -- [Unified table 9, rule node_disconnect]
503 let nid = NodeIdentifier $ NodeId theirAddr
504 writeChan ctrlChan NCMsg
505 { ctrlMsgSender = nid
506 , ctrlMsgSignal = Died nid DiedDisconnect
508 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
509 closeImplicitReconnections node nid
510 go ( (incomingFrom theirAddr ^= Set.empty)
511 . (incoming ^: Map.filterWithKey (const . notLost))
514 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
515 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
516 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
517 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
520 NT.ReceivedMulticast _ _ ->
521 -- If we received a multicast message, something went horribly wrong
522 -- and we just give up
523 fail "Cloud Haskell fatal error: received unexpected multicast"
525 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
526 invalidRequest cid st = do
527 -- TODO: We should treat this as a fatal error on the part of the remote
528 -- node. That is, we should report the remote node as having died, and we
529 -- should close incoming connections (this requires a Transport layer
531 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
533 go ( incomingAt cid ^= Nothing
537 state = localState node
538 endpoint = localEndPoint node
539 ctrlChan = localCtrlChan node
541--------------------------------------------------------------------------------
542-- Top-level access to the node controller --
543--------------------------------------------------------------------------------
545runNodeController :: LocalNode -> IO ()
547 runReaderT (evalStateT (unNC nodeController) initNCState)
549--------------------------------------------------------------------------------
550-- Internal data types --
551--------------------------------------------------------------------------------
553data NCState = NCState
554 { -- Mapping from remote processes to linked local processes
555 _links :: !(Map Identifier (Set ProcessId))
556 -- Mapping from remote processes to monitoring local processes
557 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
558 -- Process registry: names and where they live, mapped to the PIDs
559 , _registeredHere :: !(Map String ProcessId)
560 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
563newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
564 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
566initNCState :: NCState
567initNCState = NCState { _links = Map.empty
568 , _monitors = Map.empty
569 , _registeredHere = Map.empty
570 , _registeredOnNodes = Map.empty
573-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
574-- type is deliberately not exported so it cannot be caught explicitly.
575data ProcessKillException =
576 ProcessKillException !ProcessId !String
579instance Exception ProcessKillException
580instance Show ProcessKillException where
581 show (ProcessKillException pid reason) =
582 "killed-by=" ++ show pid ++ ",reason=" ++ reason
584--------------------------------------------------------------------------------
585-- Tracing/Debugging --
586--------------------------------------------------------------------------------
590traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
591traceNotifyDied node ident reason =
592 -- TODO: sendPortDied notifications
593 liftIO $ withLocalTracer node $ \t ->
595 (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
596 (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
599traceEventFmtIO :: LocalNode
603traceEventFmtIO node fmt args =
604 withLocalTracer node $ \t -> traceLogFmt t fmt args
606trace :: LocalNode -> TraceEvent -> IO ()
607trace node ev = withLocalTracer node $ \t -> traceEvent t ev
609withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
610withLocalTracer node act = act (localTracer node)
612--------------------------------------------------------------------------------
613-- Core functionality --
614--------------------------------------------------------------------------------
617nodeController :: NC ()
621 msg <- liftIO $ readChan (localCtrlChan node)
623 -- [Unified: Table 7, rule nc_forward]
624 case destNid (ctrlMsgSignal msg) of
625 Just nid' | nid' /= localNodeId node ->
626 liftIO $ sendBinary node
628 (NodeIdentifier nid')
629 WithImplicitReconnect
635 NCMsg (ProcessIdentifier from) (Link them) ->
636 ncEffectMonitor from them Nothing
637 NCMsg (ProcessIdentifier from) (Monitor ref) ->
638 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
639 NCMsg (ProcessIdentifier from) (Unlink them) ->
640 ncEffectUnlink from them
641 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
642 ncEffectUnmonitor from ref
643 NCMsg _from (Died ident reason) ->
644 ncEffectDied ident reason
645 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
646 ncEffectSpawn from proc ref
647 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
648 ncEffectRegister from label atnode pid force
649 NCMsg (ProcessIdentifier from) (WhereIs label) ->
650 ncEffectWhereIs from label
651 NCMsg _ (NamedSend label msg') ->
652 ncEffectNamedSend label msg'
653 NCMsg _ (LocalSend to msg') ->
654 ncEffectLocalSend node to msg'
655 NCMsg _ (LocalPortSend to msg') ->
656 ncEffectLocalPortSend to msg'
657 NCMsg (ProcessIdentifier from) (Kill to reason) ->
658 ncEffectKill from to reason
659 NCMsg (ProcessIdentifier from) (Exit to reason) ->
660 ncEffectExit from to reason
661 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
662 ncEffectGetInfo from pid
663 NCMsg _ SigShutdown ->
665 NT.closeEndPoint (localEndPoint node)
666 `Exception.finally` throwIO ThreadKilled
667 -- ThreadKilled seems to make more sense than fail/error here
669 error $ "nodeController: unexpected message " ++ show unexpected
671-- [Unified: Table 10]
672ncEffectMonitor :: ProcessId -- ^ Who's watching?
673 -> Identifier -- ^ Who's being watched?
674 -> Maybe MonitorRef -- ^ 'Nothing' to link
676ncEffectMonitor from them mRef = do
679 if not (isLocal node them)
681 else isValidLocalIdentifier them
682 case (shouldLink, isLocal node (ProcessIdentifier from)) of
683 (True, _) -> -- [Unified: first rule]
685 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
686 Nothing -> modify' $ linksFor them ^: Set.insert from
687 (False, True) -> -- [Unified: second rule]
688 notifyDied from them DiedUnknownId mRef
689 (False, False) -> -- [Unified: third rule]
690 -- TODO: this is the right sender according to the Unified semantics,
691 -- but perhaps having 'them' as the sender would make more sense
692 -- (see also: notifyDied)
693 liftIO $ sendBinary node
694 (NodeIdentifier $ localNodeId node)
695 (NodeIdentifier $ processNodeId from)
696 WithImplicitReconnect
698 { ctrlMsgSender = NodeIdentifier (localNodeId node)
699 , ctrlMsgSignal = Died them DiedUnknownId
702-- [Unified: Table 11]
703ncEffectUnlink :: ProcessId -> Identifier -> NC ()
704ncEffectUnlink from them = do
706 when (isLocal node (ProcessIdentifier from)) $
708 ProcessIdentifier pid ->
709 postAsMessage from $ DidUnlinkProcess pid
710 NodeIdentifier nid ->
711 postAsMessage from $ DidUnlinkNode nid
712 SendPortIdentifier cid ->
713 postAsMessage from $ DidUnlinkPort cid
714 modify' $ linksFor them ^: Set.delete from
716-- [Unified: Table 11]
717ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
718ncEffectUnmonitor from ref = do
720 when (isLocal node (ProcessIdentifier from)) $
721 postAsMessage from $ DidUnmonitor ref
722 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
724-- [Unified: Table 12]
725ncEffectDied :: Identifier -> DiedReason -> NC ()
726ncEffectDied ident reason = do
728 traceNotifyDied node ident reason
729 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
730 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
732-- _registry :: !(Map (String,NodeId) ProcessId)
734 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
736 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
738 when (localOnly <= isLocal node (ProcessIdentifier us)) $
739 notifyDied us them reason Nothing
741 forM_ (Map.toList affectedMons) $ \(them, refs) ->
742 forM_ refs $ \(us, ref) ->
743 when (localOnly <= isLocal node (ProcessIdentifier us)) $
744 notifyDied us them reason (Just ref)
746 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
748 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
750 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
751 mapM (\(pid,nidlist) ->
752 case ident `impliesDeathOf` ProcessIdentifier pid of
754 do forM_ nidlist $ \(nid,_) ->
755 when (not $ isLocal node (NodeIdentifier nid))
756 (forwardNameDeath node nid)
758 False -> return $ Just (pid,nidlist) )
759 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
761 forwardNameDeath node nid =
762 liftIO $ sendBinary node
763 (NodeIdentifier $ localNodeId node)
764 (NodeIdentifier $ nid)
765 WithImplicitReconnect
767 { ctrlMsgSender = NodeIdentifier (localNodeId node)
768 , ctrlMsgSignal = Died ident reason
771-- [Unified: Table 13]
772ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
773ncEffectSpawn pid cProc ref = do
774 mProc <- unClosure cProc
775 -- If the closure does not exist, we spawn a process that throws an exception
776 -- This allows the remote node to find out what's happening
778 let proc = case mProc of
779 Left err -> fail $ "Error: Could not resolve closure: " ++ err
782 pid' <- liftIO $ forkProcess node proc
783 liftIO $ sendMessage node
784 (NodeIdentifier (localNodeId node))
785 (ProcessIdentifier pid)
786 WithImplicitReconnect
789-- Unified semantics does not explicitly describe how to implement 'register',
790-- but mentions it's "very similar to nsend" (Table 14)
791-- We send a response indicated if the operation is invalid
792ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
793ncEffectRegister from label atnode mPid reregistration = do
795 currentVal <- gets (^. registeredHereFor label)
798 Nothing -> -- unregister request
799 return $ isJust currentVal
800 Just thepid -> -- register request
801 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
802 return $ (isNothing currentVal /= reregistration) &&
803 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
804 if isLocal node (NodeIdentifier atnode)
805 then do when (isOk) $
806 do modify' $ registeredHereFor label ^= mPid
807 updateRemote node currentVal mPid
809 (Just p) -> liftIO $ trace node (TraceEvRegistered p label)
810 Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
811 liftIO $ sendMessage node
812 (NodeIdentifier (localNodeId node))
813 (ProcessIdentifier from)
814 WithImplicitReconnect
815 (RegisterReply label isOk)
817 case reregistration of
819 False -> flip incList
822 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
823 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
824 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
825 forward node (processNodeId newval) (Register label atnode (Just newval) False)
826 updateRemote node Nothing (Just newval) =
827 forward node (processNodeId newval) (Register label atnode (Just newval) False)
828 updateRemote node (Just oldval) Nothing =
829 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
830 updateRemote _ _ _ = return ()
831 maybeify f Nothing = unmaybeify $ f []
832 maybeify f (Just x) = unmaybeify $ f x
834 unmaybeify [] = Nothing
835 unmaybeify x = Just x
836 incList [] tag = [(tag,1)]
837 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
838 incList (x:xs) tag = x : incList xs tag
840 decList ((atag,1):xs) tag | atag == tag = xs
841 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
842 decList (x:xs) tag = x:decList xs tag
843 forward node to reg =
844 when (not $ isLocal node (NodeIdentifier to)) $
845 liftIO $ sendBinary node
846 (ProcessIdentifier from)
848 WithImplicitReconnect
850 { ctrlMsgSender = ProcessIdentifier from
851 , ctrlMsgSignal = reg
855-- Unified semantics does not explicitly describe 'whereis'
856ncEffectWhereIs :: ProcessId -> String -> NC ()
857ncEffectWhereIs from label = do
859 mPid <- gets (^. registeredHereFor label)
860 liftIO $ sendMessage node
861 (NodeIdentifier (localNodeId node))
862 (ProcessIdentifier from)
863 WithImplicitReconnect
864 (WhereIsReply label mPid)
866-- [Unified: Table 14]
867ncEffectNamedSend :: String -> Message -> NC ()
868ncEffectNamedSend label msg = do
869 mPid <- gets (^. registeredHereFor label)
870 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
871 forM_ mPid $ \pid -> postMessage pid msg
874ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
875ncEffectLocalSend node to msg =
876 liftIO $ withLocalProc node to $ \p -> do
877 enqueue (processQueue p) msg
878 trace node (TraceEvReceived to msg)
881ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
882ncEffectLocalPortSend from msg = do
884 let pid = sendPortProcessId from
885 cid = sendPortLocalId from
886 liftIO $ withLocalProc node pid $ \proc -> do
887 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
889 -- in the unlikely event we know nothing about this channel id,
890 -- there's little to be done - perhaps some logging/tracing though...
892 Just (TypedChannel chan') -> do
893 -- If ch is Nothing, the process has given up the read end of
894 -- the channel and we simply ignore the incoming message - this
895 ch <- deRefWeak chan'
896 forM_ ch $ \chan -> deliverChan msg chan
897 where deliverChan :: forall a . Message -> TQueue a -> IO ()
898 deliverChan (UnencodedMessage _ raw) chan' =
899 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
900 deliverChan (EncodedMessage _ _) _ =
901 -- this will not happen unless someone screws with Primitives.hs
902 error "invalid local channel delivery"
905ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
906ncEffectKill from to reason = do
908 when (isLocal node (ProcessIdentifier to)) $
909 throwException to $ ProcessKillException from reason
912ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
913ncEffectExit from to reason = do
915 when (isLocal node (ProcessIdentifier to)) $
916 throwException to $ ProcessExitException from reason
919ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
920ncEffectGetInfo from pid =
921 let lpid = processLocalId pid
922 them = (ProcessIdentifier pid)
926 withMVar (localState node) $ return . (^. localProcessWithId lpid)
928 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
929 from node (ProcessInfoNone DiedUnknownId)
931 itsLinks <- gets (^. linksFor them)
932 itsMons <- gets (^. monitorsFor them)
933 registered <- gets (^. registeredHere)
935 let reg = registeredNames registered
936 dispatch (isLocal node (ProcessIdentifier from))
940 infoNode = (processNodeId pid)
941 , infoRegisteredNames = reg
942 -- we cannot populate this field
943 , infoMessageQueueLength = Nothing
944 , infoMonitors = Set.toList itsMons
945 , infoLinks = Set.toList itsLinks
947 where dispatch :: (Serializable a, Show a)
953 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
954 dispatch False dest node pInfo = do
955 liftIO $ sendMessage node
956 (NodeIdentifier (localNodeId node))
957 (ProcessIdentifier dest)
958 WithImplicitReconnect
961 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
965--------------------------------------------------------------------------------
967--------------------------------------------------------------------------------
969notifyDied :: ProcessId -- ^ Who to notify?
970 -> Identifier -- ^ Who died?
971 -> DiedReason -- ^ How did they die?
972 -> Maybe MonitorRef -- ^ 'Nothing' for linking
974notifyDied dest src reason mRef = do
976 case (isLocal node (ProcessIdentifier dest), mRef, src) of
977 (True, Just ref, ProcessIdentifier pid) ->
978 postAsMessage dest $ ProcessMonitorNotification ref pid reason
979 (True, Just ref, NodeIdentifier nid) ->
980 postAsMessage dest $ NodeMonitorNotification ref nid reason
981 (True, Just ref, SendPortIdentifier cid) ->
982 postAsMessage dest $ PortMonitorNotification ref cid reason
983 (True, Nothing, ProcessIdentifier pid) ->
984 throwException dest $ ProcessLinkException pid reason
985 (True, Nothing, NodeIdentifier pid) ->
986 throwException dest $ NodeLinkException pid reason
987 (True, Nothing, SendPortIdentifier pid) ->
988 throwException dest $ PortLinkException pid reason
990 -- The change in sender comes from [Unified: Table 10]
991 liftIO $ sendBinary node
992 (NodeIdentifier $ localNodeId node)
993 (NodeIdentifier $ processNodeId dest)
994 WithImplicitReconnect
996 { ctrlMsgSender = NodeIdentifier (localNodeId node)
997 , ctrlMsgSignal = Died src reason
1000-- | [Unified: Table 8]
1001destNid :: ProcessSignal -> Maybe NodeId
1002destNid (Link ident) = Just $ nodeOf ident
1003destNid (Unlink ident) = Just $ nodeOf ident
1004destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1005destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1006destNid (Spawn _ _) = Nothing
1007destNid (Register _ _ _ _) = Nothing
1008destNid (WhereIs _) = Nothing
1009destNid (NamedSend _ _) = Nothing
1010-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1011-- then when a local process dies the monitoring/linking machinery will take
1012-- care of notifying remote nodes
1013destNid (Died _ _) = Nothing
1014destNid (Kill pid _) = Just $ processNodeId pid
1015destNid (Exit pid _) = Just $ processNodeId pid
1016destNid (GetInfo pid) = Just $ processNodeId pid
1017destNid (LocalSend pid _) = Just $ processNodeId pid
1018destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1019destNid (SigShutdown) = Nothing
1021-- | Check if a process is local to our own node
1022isLocal :: LocalNode -> Identifier -> Bool
1023isLocal nid ident = nodeOf ident == localNodeId nid
1025-- | Lookup a local closure
1026unClosure :: Typeable a => Closure a -> NC (Either String a)
1027unClosure closure = do
1028 rtable <- remoteTable <$> ask
1029 return (Static.unclosure rtable closure)
1031-- | Check if an identifier refers to a valid local object
1032isValidLocalIdentifier :: Identifier -> NC Bool
1033isValidLocalIdentifier ident = do
1035 liftIO . withMVar (localState node) $ \nSt ->
1037 NodeIdentifier nid ->
1038 return $ nid == localNodeId node
1039 ProcessIdentifier pid -> do
1040 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1041 return $ isJust mProc
1042 SendPortIdentifier cid -> do
1043 let pid = sendPortProcessId cid
1044 mProc = nSt ^. localProcessWithId (processLocalId pid)
1046 Nothing -> return False
1047 Just proc -> withMVar (processState proc) $ \pSt -> do
1048 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1051--------------------------------------------------------------------------------
1052-- Messages to local processes --
1053--------------------------------------------------------------------------------
1055postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1056postAsMessage pid = postMessage pid . createUnencodedMessage
1058postMessage :: ProcessId -> Message -> NC ()
1059postMessage pid msg = do
1061 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1063throwException :: Exception e => ProcessId -> e -> NC ()
1064throwException pid e = do
1066 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1068withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1069withLocalProc node pid p =
1070 -- By [Unified: table 6, rule missing_process] messages to dead processes
1071 -- can silently be dropped
1072 let lpid = processLocalId pid in do
1073 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1076--------------------------------------------------------------------------------
1078--------------------------------------------------------------------------------
1080links :: Accessor NCState (Map Identifier (Set ProcessId))
1081links = accessor _links (\ls st -> st { _links = ls })
1083monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1084monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1086registeredHere :: Accessor NCState (Map String ProcessId)
1087registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1089registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1090registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1092linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1093linksFor ident = links >>> DAC.mapDefault Set.empty ident
1095monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1096monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1098registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1099registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1101registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1102registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1104-- | @splitNotif ident@ splits a notifications map into those
1105-- notifications that should trigger when 'ident' fails and those links that
1108-- There is a hierarchy between identifiers: failure of a node implies failure
1109-- of all processes on that node, and failure of a process implies failure of
1110-- all typed channels to that process. In other words, if 'ident' refers to a
1111-- node, then the /should trigger/ set will include
1113-- * the notifications for the node specifically
1114-- * the notifications for processes on that node, and
1115-- * the notifications for typed channels to processes on that node.
1117-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1120-- * the notifications for that process specifically and
1121-- * the notifications for typed channels to that process.
1123-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1124splitNotif :: Identifier
1126 -> (Map Identifier a, Map Identifier a)
1127splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1129--------------------------------------------------------------------------------
1131--------------------------------------------------------------------------------
1133-- | Modify and evaluate the state
1134modify' :: MonadState s m => (s -> s) -> m ()
1135modify' 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