3module Control.Distributed.Process.Node
4 ( LocalNode(localEndPoint)
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, 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)
60import Control.Concurrent (forkIO)
61import Control.Distributed.Process.Internal.StrictMVar
70import Control.Concurrent.Chan (newChan, writeChan, readChan)
71import Control.Concurrent.STM
74import Control.Distributed.Process.Internal.CQueue
80import qualified Network.Transport as NT
94 , Reliability(ReliableOrdered)
96import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
97import System.Random (randomIO)
98import Control.Distributed.Static (RemoteTable, Closure)
99import qualified Control.Distributed.Static as Static
103import Control.Distributed.Process.Internal.Types
108 , Tracer(InactiveTracer)
111 , LocalProcessState(..)
122 , ProcessMonitorNotification(..)
123 , NodeMonitorNotification(..)
124 , PortMonitorNotification(..)
125 , ProcessExitException(..)
126 , ProcessLinkException(..)
127 , NodeLinkException(..)
128 , PortLinkException(..)
130 , DidUnlinkProcess(..)
140 , ProcessInfoNone(..)
147 , createUnencodedMessage
149 , firstNonReservedProcessId
150 , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
152import Control.Distributed.Process.Internal.Trace
158import Control.Distributed.Process.Serializable (Serializable)
159import Control.Distributed.Process.Internal.Messaging
163 , closeImplicitReconnections
166import Control.Distributed.Process.Internal.Primitives
173import Control.Distributed.Process.Internal.Types (SendPort)
174import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
175import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
176import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
183--------------------------------------------------------------------------------
185--------------------------------------------------------------------------------
187initRemoteTable :: RemoteTable
188initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
190-- | Initialize a new local node.
191newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
192newLocalNode transport rtable = do
193 mEndPoint <- NT.newEndPoint transport
195 Left ex -> throwIO ex
197 localNode <- createBareLocalNode endPoint rtable
198 startServiceProcesses localNode
201-- | Create a new local node (without any service processes running)
202createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
203createBareLocalNode endPoint rtable = do
205 state <- newMVar LocalNodeState
206 { _localProcesses = Map.empty
207 , _localPidCounter = firstNonReservedProcessId
208 , _localPidUnique = unq
209 , _localConnections = Map.empty
212 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
213 , localEndPoint = endPoint
215 , localCtrlChan = ctrlChan
216 , localTracer = InactiveTracer
217 , remoteTable = rtable
219 tracedNode <- startTracing node
224 (runNodeController tracedNode
225 `Exception.catch` \(e :: SomeException) -> throwTo tid e)
228 (handleIncomingMessages tracedNode >> (stopNC node))
229 `Exception.catch` \(e :: SomeException) -> throwTo ncTid e
235 writeChan (localCtrlChan node) NCMsg
236 { ctrlMsgSender = NodeIdentifier (localNodeId node)
237 , ctrlMsgSignal = SigShutdown
240-- | Start and register the service processes on a node
241-- (for now, this is only the logger)
242startServiceProcesses :: LocalNode -> IO ()
243startServiceProcesses node = do
244 logger <- forkProcess node loop
245 runProcess node $ register "logger" logger
249 [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
250 liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
252 , match $ \((time, string) :: (String, String)) -> do
253 -- this is a 'trace' message from the local node tracer
254 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
256 , match $ \(ch :: SendPort ()) -> -- a shutdown request
260-- | Force-close a local node
262-- TODO: for now we just close the associated endpoint
263closeLocalNode :: LocalNode -> IO ()
265 NT.closeEndPoint (localEndPoint node)
267-- | Run a process on a local node and wait for it to finish
268runProcess :: LocalNode -> Process () -> IO ()
269runProcess node proc = do
271 void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
274-- | Spawn a new process on a local node
275forkProcess :: LocalNode -> Process () -> IO ProcessId
276forkProcess node proc = modifyMVar (localState node) startProcess
278 startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
280 let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
281 , lpidUnique = st ^. localPidUnique
283 let pid = ProcessId { processNodeId = localNodeId node
284 , processLocalId = lpid
286 pst <- newMVar LocalProcessState { _monitorCounter = 0
288 , _channelCounter = 0
289 , _typedChannels = Map.empty
292 weakQueue <- mkWeakCQueue queue (return ())
293 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
294 let lproc = LocalProcess { processQueue = queue
295 , processWeakQ = weakQueue
298 , processThread = tid
302 reason <- Exception.catch
303 (runLocalProcess lproc proc >> return DiedNormal)
304 (return . DiedException . (show :: SomeException -> String))
306 -- [Unified: Table 4, rules termination and exiting]
307 modifyMVar_ (localState node) (cleanupProcess pid)
308 writeChan (localCtrlChan node) NCMsg
309 { ctrlMsgSender = ProcessIdentifier pid
310 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
314 if lpidCounter lpid == maxBound
316 newUnique <- randomIO
317 return ( (localProcessWithId lpid ^= Just lproc)
318 . (localPidCounter ^= firstNonReservedProcessId)
319 . (localPidUnique ^= newUnique)
324 return ( (localProcessWithId lpid ^= Just lproc)
325 . (localPidCounter ^: (+ 1))
330 cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
331 cleanupProcess pid st = do
332 let pid' = ProcessIdentifier pid
333 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
334 mapM_ (NT.close . fst) (Map.elems affected)
335 return $ (localProcessWithId (processLocalId pid) ^= Nothing)
336 . (localConnections ^= unaffected)
339--------------------------------------------------------------------------------
340-- Handle incoming messages --
341--------------------------------------------------------------------------------
343type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
347 | ToProc (Weak (CQueue Message))
348 | ToChan TypedChannel
351data ConnectionState = ConnectionState {
352 _incoming :: !(Map NT.ConnectionId IncomingConnection)
353 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
356initConnectionState :: ConnectionState
357initConnectionState = ConnectionState {
358 _incoming = Map.empty
359 , _incomingFrom = Map.empty
362incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
363incoming = accessor _incoming (\conns st -> st { _incoming = conns })
365incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
366incomingAt cid = incoming >>> DAC.mapMaybe cid
368incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
369incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
371 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
373handleIncomingMessages :: LocalNode -> IO ()
374handleIncomingMessages node = go initConnectionState
376 go :: ConnectionState -> IO ()
378 event <- NT.receive endpoint
380 NT.ConnectionOpened cid rel theirAddr ->
381 if rel == NT.ReliableOrdered
382 then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
383 . (incomingFrom theirAddr ^: Set.insert cid)
386 else invalidRequest cid st
387 NT.Received cid payload ->
388 case st ^. incomingAt cid of
389 Just (_, ToProc weakQueue) -> do
390 mQueue <- deRefWeak weakQueue
391 forM_ mQueue $ \queue -> do
392 -- TODO: if we find that the queue is Nothing, should we remove
393 -- it from the NC state? (and same for channels, below)
394 let msg = payloadToMessage payload
395 enqueue queue msg -- 'enqueue' is strict
397 Just (_, ToChan (TypedChannel chan')) -> do
398 mChan <- deRefWeak chan'
399 -- If mChan is Nothing, the process has given up the read end of
400 -- the channel and we simply ignore the incoming message
401 forM_ mChan $ \chan -> atomically $
402 -- We make sure the message is fully decoded when it is enqueued
403 writeTQueue chan $! decode (BSL.fromChunks payload)
405 Just (_, ToNode) -> do
406 let ctrlMsg = decode . BSL.fromChunks $ payload
407 writeChan ctrlChan $! ctrlMsg
409 Just (src, Uninit) ->
410 case decode (BSL.fromChunks payload) of
411 ProcessIdentifier pid -> do
412 let lpid = processLocalId pid
413 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
416 go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
418 invalidRequest cid st
419 SendPortIdentifier chId -> do
420 let lcid = sendPortLocalId chId
421 lpid = processLocalId (sendPortProcessId chId)
422 mProc <- withMVar state $ return . (^. localProcessWithId lpid)
425 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
428 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
430 invalidRequest cid st
432 invalidRequest cid st
433 NodeIdentifier nid ->
434 if nid == localNodeId node
435 then go (incomingAt cid ^= Just (src, ToNode) $ st)
436 else invalidRequest cid st
438 invalidRequest cid st
439 NT.ConnectionClosed cid ->
440 case st ^. incomingAt cid of
442 invalidRequest cid st
444 go ( (incomingAt cid ^= Nothing)
445 . (incomingFrom src ^: Set.delete cid)
448 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
449 -- [Unified table 9, rule node_disconnect]
450 let nid = NodeIdentifier $ NodeId theirAddr
451 writeChan ctrlChan NCMsg
452 { ctrlMsgSender = nid
453 , ctrlMsgSignal = Died nid DiedDisconnect
455 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
456 closeImplicitReconnections node nid
457 go ( (incomingFrom theirAddr ^= Set.empty)
458 . (incoming ^: Map.filterWithKey (const . notLost))
461 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
462 fatal $ "Cloud Haskell fatal error: end point failed: " ++ str
463 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
464 fatal $ "Cloud Haskell fatal error: transport failed: " ++ str
466 stopTracer (localTracer node) >> return ()
467 NT.ReceivedMulticast _ _ ->
468 -- If we received a multicast message, something went horribly wrong
469 -- and we just give up
470 fatal "Cloud Haskell fatal error: received unexpected multicast"
472 fatal :: String -> IO ()
473 fatal msg = stopTracer (localTracer node) >> fail msg
475 invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
476 invalidRequest cid st = do
477 -- TODO: We should treat this as a fatal error on the part of the remote
478 -- node. That is, we should report the remote node as having died, and we
479 -- should close incoming connections (this requires a Transport layer
481 traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),
483 go ( incomingAt cid ^= Nothing
487 state = localState node
488 endpoint = localEndPoint node
489 ctrlChan = localCtrlChan node
491--------------------------------------------------------------------------------
492-- Top-level access to the node controller --
493--------------------------------------------------------------------------------
495runNodeController :: LocalNode -> IO ()
497 runReaderT (evalStateT (unNC nodeController) initNCState)
499--------------------------------------------------------------------------------
500-- Internal data types --
501--------------------------------------------------------------------------------
503data NCState = NCState
504 { -- Mapping from remote processes to linked local processes
505 _links :: !(Map Identifier (Set ProcessId))
506 -- Mapping from remote processes to monitoring local processes
507 , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
508 -- Process registry: names and where they live, mapped to the PIDs
509 , _registeredHere :: !(Map String ProcessId)
510 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
513newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
514 deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
516initNCState :: NCState
517initNCState = NCState { _links = Map.empty
518 , _monitors = Map.empty
519 , _registeredHere = Map.empty
520 , _registeredOnNodes = Map.empty
523-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
524-- type is deliberately not exported so it cannot be caught explicitly.
525data ProcessKillException =
526 ProcessKillException !ProcessId !String
529instance Exception ProcessKillException
530instance Show ProcessKillException where
531 show (ProcessKillException pid reason) =
532 "killed-by=" ++ show pid ++ ",reason=" ++ reason
534--------------------------------------------------------------------------------
535-- Tracing/Debugging --
536--------------------------------------------------------------------------------
540traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
541traceNotifyDied node ident reason =
543 DiedNormal -> return ()
544 _ -> traceNcEventFmt node " "
545 [(TraceStr "[node-controller]"),
549traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()
550traceNcEventFmt node fmt args =
551 liftIO $ traceEventFmtIO node fmt args
553traceEventFmtIO :: LocalNode
557traceEventFmtIO node fmt args =
558 withLocalTracer node $ \t -> traceFormat t fmt args
560withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
561withLocalTracer node act = act (localTracer node)
563--------------------------------------------------------------------------------
564-- Core functionality --
565--------------------------------------------------------------------------------
568nodeController :: NC ()
572 msg <- liftIO $ readChan (localCtrlChan node)
574 -- [Unified: Table 7, rule nc_forward]
575 case destNid (ctrlMsgSignal msg) of
576 Just nid' | nid' /= localNodeId node ->
577 liftIO $ sendBinary node
579 (NodeIdentifier nid')
580 WithImplicitReconnect
586 NCMsg (ProcessIdentifier from) (Link them) ->
587 ncEffectMonitor from them Nothing
588 NCMsg (ProcessIdentifier from) (Monitor ref) ->
589 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
590 NCMsg (ProcessIdentifier from) (Unlink them) ->
591 ncEffectUnlink from them
592 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
593 ncEffectUnmonitor from ref
594 NCMsg _from (Died ident reason) ->
595 ncEffectDied ident reason
596 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
597 ncEffectSpawn from proc ref
598 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
599 ncEffectRegister from label atnode pid force
600 NCMsg (ProcessIdentifier from) (WhereIs label) ->
601 ncEffectWhereIs from label
602 NCMsg from (NamedSend label msg') ->
603 ncEffectNamedSend from label msg'
604 NCMsg _ (LocalSend to msg') ->
605 ncEffectLocalSend to msg'
606 NCMsg _ (LocalPortSend to msg') ->
607 ncEffectLocalPortSend to msg'
608 NCMsg (ProcessIdentifier from) (Kill to reason) ->
609 ncEffectKill from to reason
610 NCMsg (ProcessIdentifier from) (Exit to reason) ->
611 ncEffectExit from to reason
612 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
613 ncEffectGetInfo from pid
614 NCMsg _ SigShutdown ->
615 liftIO $ throwIO ThreadKilled -- seems to make more sense than fail/error
617 error $ "nodeController: unexpected message " ++ show unexpected
619-- [Unified: Table 10]
620ncEffectMonitor :: ProcessId -- ^ Who's watching?
621 -> Identifier -- ^ Who's being watched?
622 -> Maybe MonitorRef -- ^ 'Nothing' to link
624ncEffectMonitor from them mRef = do
627 if not (isLocal node them)
629 else isValidLocalIdentifier them
630 case (shouldLink, isLocal node (ProcessIdentifier from)) of
631 (True, _) -> -- [Unified: first rule]
633 Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
634 Nothing -> modify' $ linksFor them ^: Set.insert from
635 (False, True) -> -- [Unified: second rule]
636 notifyDied from them DiedUnknownId mRef
637 (False, False) -> -- [Unified: third rule]
638 -- TODO: this is the right sender according to the Unified semantics,
639 -- but perhaps having 'them' as the sender would make more sense
640 -- (see also: notifyDied)
641 liftIO $ sendBinary node
642 (NodeIdentifier $ localNodeId node)
643 (NodeIdentifier $ processNodeId from)
644 WithImplicitReconnect
646 { ctrlMsgSender = NodeIdentifier (localNodeId node)
647 , ctrlMsgSignal = Died them DiedUnknownId
650-- [Unified: Table 11]
651ncEffectUnlink :: ProcessId -> Identifier -> NC ()
652ncEffectUnlink from them = do
654 when (isLocal node (ProcessIdentifier from)) $
656 ProcessIdentifier pid ->
657 postAsMessage from $ DidUnlinkProcess pid
658 NodeIdentifier nid ->
659 postAsMessage from $ DidUnlinkNode nid
660 SendPortIdentifier cid ->
661 postAsMessage from $ DidUnlinkPort cid
662 modify' $ linksFor them ^: Set.delete from
664-- [Unified: Table 11]
665ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
666ncEffectUnmonitor from ref = do
668 when (isLocal node (ProcessIdentifier from)) $
669 postAsMessage from $ DidUnmonitor ref
670 modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
672-- [Unified: Table 12]
673ncEffectDied :: Identifier -> DiedReason -> NC ()
674ncEffectDied ident reason = do
676 traceNotifyDied node ident reason
677 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
678 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
680-- _registry :: !(Map (String,NodeId) ProcessId)
682 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
684 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
686 when (localOnly <= isLocal node (ProcessIdentifier us)) $
687 notifyDied us them reason Nothing
689 forM_ (Map.toList affectedMons) $ \(them, refs) ->
690 forM_ refs $ \(us, ref) ->
691 when (localOnly <= isLocal node (ProcessIdentifier us)) $
692 notifyDied us them reason (Just ref)
694 modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
696 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
698 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
699 mapM (\(pid,nidlist) ->
700 case ident `impliesDeathOf` ProcessIdentifier pid of
702 do forM_ nidlist $ \(nid,_) ->
703 when (not $ isLocal node (NodeIdentifier nid))
704 (forwardNameDeath node nid)
706 False -> return $ Just (pid,nidlist) )
707 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
709 forwardNameDeath node nid =
710 liftIO $ sendBinary node
711 (NodeIdentifier $ localNodeId node)
712 (NodeIdentifier $ nid)
713 WithImplicitReconnect
715 { ctrlMsgSender = NodeIdentifier (localNodeId node)
716 , ctrlMsgSignal = Died ident reason
719-- [Unified: Table 13]
720ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
721ncEffectSpawn pid cProc ref = do
722 mProc <- unClosure cProc
723 -- If the closure does not exist, we spawn a process that throws an exception
724 -- This allows the remote node to find out what's happening
725 let proc = case mProc of
726 Left err -> fail $ "Error: Could not resolve closure: " ++ err
729 pid' <- liftIO $ forkProcess node proc
730 liftIO $ sendMessage node
731 (NodeIdentifier (localNodeId node))
732 (ProcessIdentifier pid)
733 WithImplicitReconnect
736-- Unified semantics does not explicitly describe how to implement 'register',
737-- but mentions it's "very similar to nsend" (Table 14)
738-- We send a response indicated if the operation is invalid
739ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
740ncEffectRegister from label atnode mPid reregistration = do
742 currentVal <- gets (^. registeredHereFor label)
745 Nothing -> -- unregister request
746 return $ isJust currentVal
747 Just thepid -> -- register request
748 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
749 return $ (isNothing currentVal /= reregistration) &&
750 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
751 if isLocal node (NodeIdentifier atnode)
752 then do when (isOk) $
753 do modify' $ registeredHereFor label ^= mPid
754 updateRemote node currentVal mPid
755 liftIO $ sendMessage node
756 (NodeIdentifier (localNodeId node))
757 (ProcessIdentifier from)
758 WithImplicitReconnect
759 (RegisterReply label isOk)
761 case reregistration of
763 False -> flip incList
766 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
767 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
768 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
769 forward node (processNodeId newval) (Register label atnode (Just newval) False)
770 updateRemote node Nothing (Just newval) =
771 forward node (processNodeId newval) (Register label atnode (Just newval) False)
772 updateRemote node (Just oldval) Nothing =
773 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
774 updateRemote _ _ _ = return ()
775 maybeify f Nothing = unmaybeify $ f []
776 maybeify f (Just x) = unmaybeify $ f x
777 unmaybeify [] = Nothing
778 unmaybeify x = Just x
779 incList [] tag = [(tag,1)]
780 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
781 incList (x:xs) tag = x : incList xs tag
783 decList ((atag,1):xs) tag | atag == tag = xs
784 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
785 decList (x:xs) tag = x:decList xs tag
786 forward node to reg =
787 when (not $ isLocal node (NodeIdentifier to)) $
788 liftIO $ sendBinary node
789 (ProcessIdentifier from)
791 WithImplicitReconnect
793 { ctrlMsgSender = ProcessIdentifier from
794 , ctrlMsgSignal = reg
798-- Unified semantics does not explicitly describe 'whereis'
799ncEffectWhereIs :: ProcessId -> String -> NC ()
800ncEffectWhereIs from label = do
802 mPid <- gets (^. registeredHereFor label)
803 liftIO $ sendMessage node
804 (NodeIdentifier (localNodeId node))
805 (ProcessIdentifier from)
806 WithImplicitReconnect
807 (WhereIsReply label mPid)
809-- [Unified: Table 14]
810ncEffectNamedSend :: Identifier -> String -> Message -> NC ()
811ncEffectNamedSend from label msg = do
813 mPid <- gets (^. registeredHereFor label)
814 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
816 liftIO $ sendPayload node
818 (ProcessIdentifier pid)
820 (messageToPayload msg)
823ncEffectLocalSend :: ProcessId -> Message -> NC ()
824ncEffectLocalSend = postMessage
827ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
828ncEffectLocalPortSend from msg =
829 let pid = sendPortProcessId from
830 cid = sendPortLocalId from
832 withLocalProc pid $ \proc -> do
833 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
835 -- in the unlikely event we know nothing about this channel id,
836 -- there's little to be done - perhaps some logging/tracing though...
838 Just (TypedChannel chan') -> do
839 -- If ch is Nothing, the process has given up the read end of
840 -- the channel and we simply ignore the incoming message - this
841 ch <- deRefWeak chan'
842 forM_ ch $ \chan -> deliverChan msg chan
843 where deliverChan :: forall a . Message -> TQueue a -> IO ()
844 deliverChan (UnencodedMessage _ raw) chan' =
845 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
846 deliverChan (EncodedMessage _ _) _ =
847 -- this will not happen unless someone screws with Primitives.hs
848 error "invalid local channel delivery"
851ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
852ncEffectKill from to reason = do
854 when (isLocal node (ProcessIdentifier to)) $
855 throwException to $ ProcessKillException from reason
858ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
859ncEffectExit from to reason = do
861 when (isLocal node (ProcessIdentifier to)) $
862 throwException to $ ProcessExitException from reason
865ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
866ncEffectGetInfo from pid =
867 let lpid = processLocalId pid
868 them = (ProcessIdentifier pid)
872 withMVar (localState node) $ return . (^. localProcessWithId lpid)
874 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
875 from node (ProcessInfoNone DiedUnknownId)
877 itsLinks <- gets (^. linksFor them)
878 itsMons <- gets (^. monitorsFor them)
879 registered <- gets (^. registeredHere)
881 let reg = registeredNames registered
882 dispatch (isLocal node (ProcessIdentifier from))
886 infoNode = (processNodeId pid)
887 , infoRegisteredNames = reg
888 -- we cannot populate this field
889 , infoMessageQueueLength = Nothing
890 , infoMonitors = Set.toList itsMons
891 , infoLinks = Set.toList itsLinks
893 where dispatch :: (Serializable a, Show a)
899 dispatch True dest _ pInfo = postAsMessage dest $ pInfo
900 dispatch False dest node pInfo = do
901 liftIO $ sendMessage node
902 (NodeIdentifier (localNodeId node))
903 (ProcessIdentifier dest)
904 WithImplicitReconnect
907 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
911--------------------------------------------------------------------------------
913--------------------------------------------------------------------------------
915notifyDied :: ProcessId -- ^ Who to notify?
916 -> Identifier -- ^ Who died?
917 -> DiedReason -- ^ How did they die?
918 -> Maybe MonitorRef -- ^ 'Nothing' for linking
920notifyDied dest src reason mRef = do
922 case (isLocal node (ProcessIdentifier dest), mRef, src) of
923 (True, Just ref, ProcessIdentifier pid) ->
924 postAsMessage dest $ ProcessMonitorNotification ref pid reason
925 (True, Just ref, NodeIdentifier nid) ->
926 postAsMessage dest $ NodeMonitorNotification ref nid reason
927 (True, Just ref, SendPortIdentifier cid) ->
928 postAsMessage dest $ PortMonitorNotification ref cid reason
929 (True, Nothing, ProcessIdentifier pid) ->
930 throwException dest $ ProcessLinkException pid reason
931 (True, Nothing, NodeIdentifier pid) ->
932 throwException dest $ NodeLinkException pid reason
933 (True, Nothing, SendPortIdentifier pid) ->
934 throwException dest $ PortLinkException pid reason
936 -- The change in sender comes from [Unified: Table 10]
937 liftIO $ sendBinary node
938 (NodeIdentifier $ localNodeId node)
939 (NodeIdentifier $ processNodeId dest)
940 WithImplicitReconnect
942 { ctrlMsgSender = NodeIdentifier (localNodeId node)
943 , ctrlMsgSignal = Died src reason
946-- | [Unified: Table 8]
947destNid :: ProcessSignal -> Maybe NodeId
948destNid (Link ident) = Just $ nodeOf ident
949destNid (Unlink ident) = Just $ nodeOf ident
950destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
951destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
952destNid (Spawn _ _) = Nothing
953destNid (Register _ _ _ _) = Nothing
954destNid (WhereIs _) = Nothing
955destNid (NamedSend _ _) = Nothing
956-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
957-- then when a local process dies the monitoring/linking machinery will take
958-- care of notifying remote nodes
959destNid (Died _ _) = Nothing
960destNid (Kill pid _) = Just $ processNodeId pid
961destNid (Exit pid _) = Just $ processNodeId pid
962destNid (GetInfo pid) = Just $ processNodeId pid
963destNid (LocalSend pid _) = Just $ processNodeId pid
964destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
965destNid (SigShutdown) = Nothing
967-- | Check if a process is local to our own node
968isLocal :: LocalNode -> Identifier -> Bool
969isLocal nid ident = nodeOf ident == localNodeId nid
971-- | Lookup a local closure
972unClosure :: Typeable a => Closure a -> NC (Either String a)
973unClosure closure = do
974 rtable <- remoteTable <$> ask
975 return (Static.unclosure rtable closure)
977-- | Check if an identifier refers to a valid local object
978isValidLocalIdentifier :: Identifier -> NC Bool
979isValidLocalIdentifier ident = do
981 liftIO . withMVar (localState node) $ \nSt ->
983 NodeIdentifier nid ->
984 return $ nid == localNodeId node
985 ProcessIdentifier pid -> do
986 let mProc = nSt ^. localProcessWithId (processLocalId pid)
987 return $ isJust mProc
988 SendPortIdentifier cid -> do
989 let pid = sendPortProcessId cid
990 mProc = nSt ^. localProcessWithId (processLocalId pid)
992 Nothing -> return False
993 Just proc -> withMVar (processState proc) $ \pSt -> do
994 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
997--------------------------------------------------------------------------------
998-- Messages to local processes --
999--------------------------------------------------------------------------------
1001postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1002postAsMessage pid = postMessage pid . createUnencodedMessage
1004postMessage :: ProcessId -> Message -> NC ()
1005postMessage pid msg = do
1006 withLocalProc pid $ \p -> enqueue (processQueue p) msg
1008throwException :: Exception e => ProcessId -> e -> NC ()
1009throwException pid e = withLocalProc pid $ \p ->
1010 throwTo (processThread p) e
1012withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
1013withLocalProc pid p = do
1016 -- By [Unified: table 6, rule missing_process] messages to dead processes
1017 -- can silently be dropped
1018 let lpid = processLocalId pid
1019 mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1022--------------------------------------------------------------------------------
1024--------------------------------------------------------------------------------
1026links :: Accessor NCState (Map Identifier (Set ProcessId))
1027links = accessor _links (\ls st -> st { _links = ls })
1029monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1030monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1032registeredHere :: Accessor NCState (Map String ProcessId)
1033registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1035registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1036registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1038linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1039linksFor ident = links >>> DAC.mapDefault Set.empty ident
1041monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1042monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1044registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1045registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1047registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1048registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1050-- | @splitNotif ident@ splits a notifications map into those
1051-- notifications that should trigger when 'ident' fails and those links that
1054-- There is a hierarchy between identifiers: failure of a node implies failure
1055-- of all processes on that node, and failure of a process implies failure of
1056-- all typed channels to that process. In other words, if 'ident' refers to a
1057-- node, then the /should trigger/ set will include
1059-- * the notifications for the node specifically
1060-- * the notifications for processes on that node, and
1061-- * the notifications for typed channels to processes on that node.
1063-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1066-- * the notifications for that process specifically and
1067-- * the notifications for typed channels to that process.
1069-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1070splitNotif :: Identifier
1072 -> (Map Identifier a, Map Identifier a)
1073splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1075--------------------------------------------------------------------------------
1077--------------------------------------------------------------------------------
1079-- | Modify and evaluate the state
1080modify' :: MonadState s m => (s -> s) -> m ()
1081modify' 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