git.iko.soy/distributed-process
on 33bf088 help sign in
1287 lines 52 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE BangPatterns #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE RecordWildCards #-}
8{-# LANGUAGE MagicHash #-}
10-- | Local nodes
11--
12module Control.Distributed.Process.Node
13 ( LocalNode
14 , newLocalNode
15 , closeLocalNode
16 , forkProcess
17 , runProcess
18 , initRemoteTable
19 , localNodeId
20 ) where
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)
28import Data.Map (Map)
29import qualified Data.Map as Map
30 ( empty
31 , toList
32 , fromList
33 , filter
34 , partitionWithKey
35 , elems
36 , size
37 , filterWithKey
38 , foldlWithKey
39 )
40import qualified Data.Set as Set
41import Data.Time.Format (formatTime)
42#if MIN_VERSION_time(1,5,0)
43import Data.Time.Format (defaultTimeLocale)
44#else
45import System.Locale (defaultTimeLocale)
46#endif
47import Data.Set (Set)
48import qualified Data.Set as Set
49 ( empty
50 , insert
51 , delete
52 , map
53 , member
54 , toList
55 , union
56 )
57import Data.Foldable (forM_)
58import Data.List (foldl')
59import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
60import Data.Typeable (Typeable)
61import Control.Category ((>>>))
62import Control.Applicative
63import Control.Monad (void, when, join)
64import Control.Monad.IO.Class (MonadIO, liftIO)
65import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
66import qualified Control.Monad.State.Strict as StateT (get, put)
67import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
68import Control.Exception
69 ( throwIO
70 , SomeException
71 , Exception
72 , throwTo
73 , uninterruptibleMask_
74 , getMaskingState
75 , MaskingState(..)
76 )
77import qualified Control.Exception as Exception
78 ( Handler(..)
79 , catch
80 , catches
81 , finally
82 )
83import Control.Concurrent (forkIO, killThread)
84import Control.Distributed.Process.Internal.BiMultiMap (BiMultiMap)
85import qualified Control.Distributed.Process.Internal.BiMultiMap as BiMultiMap
86import Control.Distributed.Process.Internal.StrictMVar
87 ( newMVar
88 , withMVar
89 , modifyMVarMasked
90 , modifyMVar
91 , newEmptyMVar
92 , putMVar
93 , takeMVar
94 )
95import Control.Concurrent.Chan (newChan, writeChan, readChan)
96import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
97import Control.Concurrent.STM
98 ( atomically
99 )
100import Control.Distributed.Process.Internal.CQueue
101 ( CQueue
102 , enqueue
103 , newCQueue
104 , mkWeakCQueue
105 , queueSize
106 )
107import qualified Network.Transport as NT
108 ( Transport
109 , EndPoint
110 , newEndPoint
111 , receive
112 , Event(..)
113 , EventErrorCode(..)
114 , TransportError(..)
115 , address
116 , closeEndPoint
117 , Connection
118 , ConnectionId
119 , close
120 , EndPointAddress
121 , Reliability(ReliableOrdered)
122 )
123import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
124import System.Random (randomIO)
125import Control.Distributed.Static (RemoteTable, Closure)
126import qualified Control.Distributed.Static as Static
127 ( unclosure
128 , initRemoteTable
129 )
130import Control.Distributed.Process.Internal.Types
131 ( NodeId(..)
132 , LocalProcessId(..)
133 , ProcessId(..)
134 , LocalNode(..)
135 , MxEventBus(..)
136 , LocalNodeState(..)
137 , ValidLocalNodeState(..)
138 , withValidLocalState
139 , modifyValidLocalState
140 , LocalProcess(..)
141 , LocalProcessState(..)
142 , Process(..)
143 , DiedReason(..)
144 , NCMsg(..)
145 , ProcessSignal(..)
146 , localPidCounter
147 , localPidUnique
148 , localProcessWithId
149 , localProcesses
150 , localConnections
151 , forever'
152 , MonitorRef(..)
153 , NodeClosedException(..)
154 , ProcessMonitorNotification(..)
155 , NodeMonitorNotification(..)
156 , PortMonitorNotification(..)
157 , ProcessExitException(..)
158 , ProcessLinkException(..)
159 , NodeLinkException(..)
160 , PortLinkException(..)
161 , DidUnmonitor(..)
162 , DidUnlinkProcess(..)
163 , DidUnlinkNode(..)
164 , DidUnlinkPort(..)
165 , SpawnRef
166 , DidSpawn(..)
167 , Message(..)
168 , TypedChannel(..)
169 , Identifier(..)
170 , nodeOf
171 , ProcessInfo(..)
172 , ProcessInfoNone(..)
173 , NodeStats(..)
174 , SendPortId(..)
175 , typedChannelWithId
176 , RegisterReply(..)
177 , WhereIsReply(..)
178 , payloadToMessage
179 , createUnencodedMessage
180 , unsafeCreateUnencodedMessage
181 , runLocalProcess
182 , firstNonReservedProcessId
183 , ImplicitReconnect(WithImplicitReconnect)
184 )
185import Control.Distributed.Process.Management.Internal.Agent
186 ( mxAgentController
187 )
188import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
189 ( remoteTable
190 )
191import Control.Distributed.Process.Management.Internal.Trace.Tracer
192 ( defaultTracer
193 )
194import Control.Distributed.Process.Management.Internal.Trace.Types
195 ( TraceArg(..)
196 , traceEvent
197 , traceLogFmt
198 , enableTrace
199 )
200import Control.Distributed.Process.Management.Internal.Types
201 ( MxEvent(..)
202 )
203import Control.Distributed.Process.Serializable (Serializable)
204import Control.Distributed.Process.Internal.Messaging
205 ( sendBinary
206 , closeImplicitReconnections
207 , impliesDeathOf
208 )
209import Control.Distributed.Process.Internal.Primitives
210 ( register
211 , receiveWait
212 , match
213 , sendChan
214 , unwrapMessage
215 , SayMessage(..)
216 )
217import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
218import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
219import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
220import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
221 ( mapMaybe
222 , mapDefault
223 )
224import Control.Monad.Catch (try)
225import GHC.IO (IO(..), unsafeUnmask)
226import GHC.Base ( maskAsyncExceptions# )
228import Unsafe.Coerce
229import Prelude
231-- Remove these definitions when the fix for
232-- https://ghc.haskell.org/trac/ghc/ticket/10149
233-- is included in all supported compilers:
234block :: IO a -> IO a
235block (IO io) = IO $ maskAsyncExceptions# io
236unblock :: IO a -> IO a
237unblock = unsafeUnmask
239--------------------------------------------------------------------------------
240-- Initialization --
241--------------------------------------------------------------------------------
243initRemoteTable :: RemoteTable
244initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
246-- | Initialize a new local node.
247newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
248newLocalNode transport rtable = do
249 mEndPoint <- NT.newEndPoint transport
250 case mEndPoint of
251 Left ex -> throwIO ex
252 Right endPoint -> do
253 localNode <- createBareLocalNode endPoint rtable
254 startServiceProcesses localNode
255 return localNode
257-- | Create a new local node (without any service processes running)
258createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
259createBareLocalNode endPoint rtable = do
260 unq <- randomIO
261 state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
262 { _localProcesses = Map.empty
263 , _localPidCounter = firstNonReservedProcessId
264 , _localPidUnique = unq
265 , _localConnections = Map.empty
266 }
267 ctrlChan <- newChan
268 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
269 , localEndPoint = endPoint
270 , localState = state
271 , localCtrlChan = ctrlChan
272 , localEventBus = MxEventBusInitialising
273 , remoteTable = rtable
274 }
275 tracedNode <- startMxAgent node
277 -- Once the NC terminates, the endpoint isn't much use,
278 void $ forkIO $ Exception.finally (runNodeController tracedNode)
279 (NT.closeEndPoint (localEndPoint node))
281 -- whilst a closed/failing endpoint will terminate the NC
282 void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
283 (stopNC node)
285 return tracedNode
286 where
287 stopNC node =
288 writeChan (localCtrlChan node) NCMsg
289 { ctrlMsgSender = NodeIdentifier (localNodeId node)
290 , ctrlMsgSignal = SigShutdown
291 }
293startMxAgent :: LocalNode -> IO LocalNode
294startMxAgent node = do
295 -- see note [tracer/forkProcess races]
296 let fork = forkProcess node
297 mv <- MVar.newEmptyMVar
298 pid <- fork $ mxAgentController fork mv
299 (tracer', wqRef, mxNew') <- MVar.takeMVar mv
300 return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
302startDefaultTracer :: LocalNode -> IO ()
303startDefaultTracer node' = do
304 let t = localEventBus node'
305 case t of
306 MxEventBus _ (Tracer pid _) _ _ -> do
307 runProcess node' $ register "trace.controller" pid
308 pid' <- forkProcess node' defaultTracer
309 enableTrace (localEventBus node') pid'
310 runProcess node' $ register "tracer.initial" pid'
311 _ -> return ()
313-- TODO: we need a better mechanism for defining and registering services
315-- | Start and register the service processes on a node
316startServiceProcesses :: LocalNode -> IO ()
317startServiceProcesses node = do
318 -- tracing /spawns/ relies on the tracer being enabled, but we start
319 -- the default tracer first, even though it might @nsend@ to the logger
320 -- before /that/ process has started - this is a totally harmless race
321 -- however, so we deliberably ignore it
322 startDefaultTracer node
323 logger <- forkProcess node loop
324 runProcess node $ do
325 register "logger" logger
326 -- The trace.logger is used for tracing to the console to avoid feedback
327 -- loops during tracing if the user reregisters the "logger" with a custom
328 -- process which uses 'send' or other primitives which are traced.
329 register "trace.logger" logger
330 where
331 loop = do
332 receiveWait
333 [ match $ \(SayMessage time pid string) -> do
334 let time' = formatTime defaultTimeLocale "%c" time
335 liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
336 loop
337 , match $ \((time, string) :: (String, String)) -> do
338 -- this is a 'trace' message from the local node tracer
339 liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
340 loop
341 , match $ \(ch :: SendPort ()) -> -- a shutdown request
342 sendChan ch ()
343 ]
345-- | Force-close a local node, killing all processes on that node.
346closeLocalNode :: LocalNode -> IO ()
347closeLocalNode node = do
348 -- Kill processes after refilling the mvar. Otherwise, there is potential for
349 -- deadlock as a dying process tries to get the mvar while masking exceptions
350 -- uninterruptibly.
351 join $ modifyMVar (localState node) $ \st -> case st of
352 LocalNodeValid vst -> do
353 return ( LocalNodeClosed
354 , forM_ (vst ^. localProcesses) $ \lproc ->
355 -- Semantics of 'throwTo' guarantee that target thread will get
356 -- delivered an exception. Therefore, target thread will be
357 -- killed eventually and that's as good as we can do. No need
358 -- to wait for thread to actually finish dying.
359 killThread (processThread lproc)
360 )
361 LocalNodeClosed -> return (LocalNodeClosed, return ())
362 -- This call will have the effect of shutting down the NC as well (see
363 -- 'createBareLocalNode').
364 NT.closeEndPoint (localEndPoint node)
366-- | Run a process on a local node and wait for it to finish
367runProcess :: LocalNode -> Process () -> IO ()
368runProcess node proc = do
369 done <- newEmptyMVar
370 -- TODO; When forkProcess inherits the masking state, protect the forked
371 -- thread against async exceptions that could occur before 'try' is evaluated.
372 void $ forkProcess node $ try proc >>= liftIO . putMVar done
373 takeMVar done >>= either (throwIO :: SomeException -> IO a) return
375-- | Spawn a new process on a local node
376forkProcess :: LocalNode -> Process () -> IO ProcessId
377forkProcess node proc = do
378 ms <- getMaskingState
379 modifyMVarMasked (localState node) (startProcess ms)
380 where
381 startProcess :: MaskingState
382 -> LocalNodeState
383 -> IO (LocalNodeState, ProcessId)
384 startProcess ms (LocalNodeValid vst) = do
385 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
386 , lpidUnique = vst ^. localPidUnique
387 }
388 let pid = ProcessId { processNodeId = localNodeId node
389 , processLocalId = lpid
390 }
391 pst <- newMVar LocalProcessState { _monitorCounter = 0
392 , _spawnCounter = 0
393 , _channelCounter = 0
394 , _typedChannels = Map.empty
395 }
396 queue <- newCQueue
397 weakQueue <- mkWeakCQueue queue (return ())
398 (_, lproc) <- fixIO $ \ ~(tid, _) -> do
399 let lproc = LocalProcess { processQueue = queue
400 , processWeakQ = weakQueue
401 , processId = pid
402 , processState = pst
403 , processThread = tid
404 , processNode = node
405 }
406 -- Rewrite this code when this is fixed:
407 -- https://ghc.haskell.org/trac/ghc/ticket/10149
408 let unmask = case ms of
409 Unmasked -> unblock
410 MaskedInterruptible -> block
411 MaskedUninterruptible -> id
412 tid' <- uninterruptibleMask_ $ forkIO $ do
413 reason <- Exception.catches
414 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
415 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
416 mMsg <- unwrapMessage msg :: IO (Maybe String)
417 case mMsg of
418 Nothing -> return $ DiedException $ show ex
419 Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
420 , (Exception.Handler
421 (return . DiedException . (show :: SomeException -> String)))]
423 -- [Unified: Table 4, rules termination and exiting]
424 mconns <- modifyValidLocalState node (cleanupProcess pid)
425 -- XXX: Revisit after agreeing on the bigger picture for the semantics
426 -- of transport operations.
427 -- https://github.com/haskell-distributed/distributed-process/issues/204
428 forM_ mconns $ forkIO . mapM_ NT.close
430 writeChan (localCtrlChan node) NCMsg
431 { ctrlMsgSender = ProcessIdentifier pid
432 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
433 }
434 return (tid', lproc)
436 -- see note [tracer/forkProcess races]
437 trace node (MxSpawned pid)
439 if lpidCounter lpid == maxBound
440 then do
441 -- TODO: this doesn't look right at all - how do we know
442 -- that newUnique represents a process id that is available!?
443 newUnique <- randomIO
444 return ( LocalNodeValid
445 $ (localProcessWithId lpid ^= Just lproc)
446 . (localPidCounter ^= firstNonReservedProcessId)
447 . (localPidUnique ^= newUnique)
448 $ vst
449 , pid
450 )
451 else
452 return ( LocalNodeValid
453 $ (localProcessWithId lpid ^= Just lproc)
454 . (localPidCounter ^: (+ 1))
455 $ vst
456 , pid
457 )
458 startProcess _ LocalNodeClosed =
459 throwIO $ NodeClosedException $ localNodeId node
461 cleanupProcess :: ProcessId
462 -> ValidLocalNodeState
463 -> IO (ValidLocalNodeState, [NT.Connection])
464 cleanupProcess pid vst = do
465 let pid' = ProcessIdentifier pid
466 let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
467 return ( (localProcessWithId (processLocalId pid) ^= Nothing)
468 . (localConnections ^= unaffected)
469 $ vst
470 , map fst $ Map.elems affected
471 )
473-- note [tracer/forkProcess races]
474--
475-- Our startTracing function uses forkProcess to start the trace controller
476-- process, and of course forkProcess attempts to call traceEvent once the
477-- process has started. This is harmless, as the localEventBus is not updated
478-- until /after/ the initial forkProcess completes, so the first call to
479-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
480--
482--------------------------------------------------------------------------------
483-- Handle incoming messages --
484--------------------------------------------------------------------------------
486type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
488data IncomingTarget =
489 Uninit
490 | ToProc ProcessId (Weak (CQueue Message))
491 | ToChan TypedChannel
492 | ToNode
494data ConnectionState = ConnectionState {
495 _incoming :: !(Map NT.ConnectionId IncomingConnection)
496 , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
497 }
499initConnectionState :: ConnectionState
500initConnectionState = ConnectionState {
501 _incoming = Map.empty
502 , _incomingFrom = Map.empty
503 }
505incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
506incoming = accessor _incoming (\conns st -> st { _incoming = conns })
508incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
509incomingAt cid = incoming >>> DAC.mapMaybe cid
511incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
512incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
513 where
514 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
516handleIncomingMessages :: LocalNode -> IO ()
517handleIncomingMessages node = go initConnectionState
518 `Exception.catch` \(NodeClosedException _) -> return ()
519 where
520 go :: ConnectionState -> IO ()
521 go !st = do
522 event <- NT.receive endpoint
523 case event of
524 NT.ConnectionOpened cid rel theirAddr ->
525 if rel == NT.ReliableOrdered
526 then
527 trace node (MxConnected cid theirAddr)
528 >> go (
529 (incomingAt cid ^= Just (theirAddr, Uninit))
530 . (incomingFrom theirAddr ^: Set.insert cid)
531 $ st
532 )
533 else invalidRequest cid st $
534 "attempt to connect with unsupported reliability " ++ show rel
535 NT.Received cid payload ->
536 case st ^. incomingAt cid of
537 Just (_, ToProc pid weakQueue) -> do
538 mQueue <- deRefWeak weakQueue
539 forM_ mQueue $ \queue -> do
540 -- TODO: if we find that the queue is Nothing, should we remove
541 -- it from the NC state? (and same for channels, below)
542 let msg = payloadToMessage payload
543 enqueue queue msg -- 'enqueue' is strict
544 trace node (MxReceived pid msg)
545 go st
546 Just (_, ToChan (TypedChannel chan')) -> do
547 mChan <- deRefWeak chan'
548 -- If mChan is Nothing, the process has given up the read end of
549 -- the channel and we simply ignore the incoming message
550 forM_ mChan $ \chan -> atomically $
551 -- We make sure the message is fully decoded when it is enqueued
552 writeTQueue chan $! decode (BSL.fromChunks payload)
553 go st
554 Just (_, ToNode) -> do
555 let ctrlMsg = decode . BSL.fromChunks $ payload
556 writeChan ctrlChan $! ctrlMsg
557 go st
558 Just (src, Uninit) ->
559 case decode (BSL.fromChunks payload) of
560 ProcessIdentifier pid -> do
561 let lpid = processLocalId pid
562 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
563 case mProc of
564 Just proc ->
565 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
566 Nothing ->
567 -- incoming attempt to connect to unknown process - might
568 -- be dead already
569 go (incomingAt cid ^= Nothing $ st)
570 SendPortIdentifier chId -> do
571 let lcid = sendPortLocalId chId
572 lpid = processLocalId (sendPortProcessId chId)
573 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
574 case mProc of
575 Just proc -> do
576 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
577 case mChannel of
578 Just channel ->
579 go (incomingAt cid ^= Just (src, ToChan channel) $ st)
580 Nothing ->
581 invalidRequest cid st $
582 "incoming attempt to connect to unknown channel of"
583 ++ " process " ++ show (sendPortProcessId chId)
584 Nothing ->
585 -- incoming attempt to connect to channel of unknown
586 -- process - might be dead already
587 go (incomingAt cid ^= Nothing $ st)
588 NodeIdentifier nid ->
589 if nid == localNodeId node
590 then go (incomingAt cid ^= Just (src, ToNode) $ st)
591 else invalidRequest cid st $
592 "incoming attempt to connect to a different node -"
593 ++ " I'm " ++ show (localNodeId node)
594 ++ " but the remote peer wants to connect to "
595 ++ show nid
596 Nothing ->
597 invalidRequest cid st
598 "message received from an unknown connection"
599 NT.ConnectionClosed cid ->
600 case st ^. incomingAt cid of
601 Nothing ->
602 invalidRequest cid st "closed unknown connection"
603 Just (src, _) -> do
604 trace node (MxDisconnected cid src)
605 go ( (incomingAt cid ^= Nothing)
606 . (incomingFrom src ^: Set.delete cid)
607 $ st
608 )
609 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
610 -- [Unified table 9, rule node_disconnect]
611 let nid = NodeIdentifier $ NodeId theirAddr
612 writeChan ctrlChan NCMsg
613 { ctrlMsgSender = nid
614 , ctrlMsgSignal = Died nid DiedDisconnect
615 }
616 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
617 closeImplicitReconnections node nid
618 go ( (incomingFrom theirAddr ^= Set.empty)
619 . (incoming ^: Map.filterWithKey (const . notLost))
620 $ st
621 )
622 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
623 fail $ "Cloud Haskell fatal error: end point failed: " ++ str
624 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
625 fail $ "Cloud Haskell fatal error: transport failed: " ++ str
626 NT.EndPointClosed ->
627 return ()
628 NT.ReceivedMulticast _ _ ->
629 -- If we received a multicast message, something went horribly wrong
630 -- and we just give up
631 fail "Cloud Haskell fatal error: received unexpected multicast"
633 invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
634 invalidRequest cid st msg = do
635 -- TODO: We should treat this as a fatal error on the part of the remote
636 -- node. That is, we should report the remote node as having died, and we
637 -- should close incoming connections (this requires a Transport layer
638 -- extension).
639 traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
640 ++ " (" ++ msg ++ "): "
641 , (Trace cid)
642 ]
643 go ( incomingAt cid ^= Nothing
644 $ st
645 )
647 endpoint = localEndPoint node
648 ctrlChan = localCtrlChan node
650--------------------------------------------------------------------------------
651-- Top-level access to the node controller --
652--------------------------------------------------------------------------------
654runNodeController :: LocalNode -> IO ()
655runNodeController node =
656 runReaderT (evalStateT (unNC nodeController) initNCState) node
657 `Exception.catch` \(NodeClosedException _) -> return ()
659--------------------------------------------------------------------------------
660-- Internal data types --
661--------------------------------------------------------------------------------
663data NCState = NCState
664 { -- Mapping from remote processes to linked local processes
665 _links :: !(BiMultiMap Identifier ProcessId ())
666 -- Mapping from remote processes to monitoring local processes
667 , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
668 -- Process registry: names and where they live, mapped to the PIDs
669 , _registeredHere :: !(Map String ProcessId)
670 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
671 }
673newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
674 deriving ( Applicative
675 , Functor
676 , Monad
677 , MonadIO
678 , MonadState NCState
679 , MonadReader LocalNode
680 )
682initNCState :: NCState
683initNCState = NCState { _links = BiMultiMap.empty
684 , _monitors = BiMultiMap.empty
685 , _registeredHere = Map.empty
686 , _registeredOnNodes = Map.empty
687 }
689-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
690-- type is deliberately not exported so it cannot be caught explicitly.
691data ProcessKillException =
692 ProcessKillException !ProcessId !String
693 deriving (Typeable)
695instance Exception ProcessKillException
696instance Show ProcessKillException where
697 show (ProcessKillException pid reason) =
698 "killed-by=" ++ show pid ++ ",reason=" ++ reason
700ncSendToProcess :: ProcessId -> Message -> NC ()
701ncSendToProcess = ncSendToProcessAndTrace True
703ncSendToProcessAndTrace :: Bool -> ProcessId -> Message -> NC ()
704ncSendToProcessAndTrace shouldTrace pid msg = do
705 node <- ask
706 if processNodeId pid == localNodeId node
707 then ncEffectLocalSendAndTrace shouldTrace node pid msg
708 else liftIO $ sendBinary node
709 (NodeIdentifier $ localNodeId node)
710 (NodeIdentifier $ processNodeId pid)
711 WithImplicitReconnect
712 NCMsg { ctrlMsgSender = NodeIdentifier $ localNodeId node
713 , ctrlMsgSignal = UnreliableSend (processLocalId pid) msg
714 }
716ncSendToNode :: NodeId -> NCMsg -> NC ()
717ncSendToNode to msg = do
718 node <- ask
719 liftIO $ if to == localNodeId node
720 then writeChan (localCtrlChan node) $! msg
721 else sendBinary node
722 (NodeIdentifier $ localNodeId node)
723 (NodeIdentifier to)
724 WithImplicitReconnect
725 msg
727--------------------------------------------------------------------------------
728-- Tracing/Debugging --
729--------------------------------------------------------------------------------
731-- [Issue #104 / DP-13]
733traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
734traceNotifyDied node ident reason =
735 -- TODO: sendPortDied notifications
736 liftIO $ withLocalTracer node $ \t ->
737 case ident of
738 (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
739 (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
740 _ -> return ()
742traceEventFmtIO :: LocalNode
743 -> String
744 -> [TraceArg]
745 -> IO ()
746traceEventFmtIO node fmt args =
747 withLocalTracer node $ \t -> traceLogFmt t fmt args
749trace :: LocalNode -> MxEvent -> IO ()
750trace node ev = withLocalTracer node $ \t -> traceEvent t ev
752withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
753withLocalTracer node act = act (localEventBus node)
755--------------------------------------------------------------------------------
756-- Core functionality --
757--------------------------------------------------------------------------------
759-- [Unified: Table 7]
760nodeController :: NC ()
761nodeController = do
762 node <- ask
763 forever' $ do
764 msg <- liftIO $ readChan (localCtrlChan node)
766 -- [Unified: Table 7, rule nc_forward]
767 case destNid (ctrlMsgSignal msg) of
768 Just nid' | nid' /= localNodeId node ->
769 ncSendToNode nid' msg
770 _ ->
771 return ()
773 case msg of
774 NCMsg (ProcessIdentifier from) (Link them) ->
775 ncEffectMonitor from them Nothing
776 NCMsg (ProcessIdentifier from) (Monitor ref) ->
777 ncEffectMonitor from (monitorRefIdent ref) (Just ref)
778 NCMsg (ProcessIdentifier from) (Unlink them) ->
779 ncEffectUnlink from them
780 NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
781 ncEffectUnmonitor from ref
782 NCMsg _from (Died ident reason) ->
783 ncEffectDied ident reason
784 NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
785 ncEffectSpawn from proc ref
786 NCMsg (ProcessIdentifier from) (Register label atnode pid force) ->
787 ncEffectRegister from label atnode pid force
788 NCMsg (ProcessIdentifier from) (WhereIs label) ->
789 ncEffectWhereIs from label
790 NCMsg _ (NamedSend label msg') ->
791 ncEffectNamedSend label msg'
792 NCMsg _ (UnreliableSend lpid msg') ->
793 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
794 NCMsg _ (LocalSend to msg') ->
795 ncEffectLocalSend node to msg'
796 NCMsg _ (LocalPortSend to msg') ->
797 ncEffectLocalPortSend to msg'
798 NCMsg (ProcessIdentifier from) (Kill to reason) ->
799 ncEffectKill from to reason
800 NCMsg (ProcessIdentifier from) (Exit to reason) ->
801 ncEffectExit from to reason
802 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
803 ncEffectGetInfo from pid
804 NCMsg _ SigShutdown ->
805 liftIO $ do
806 NT.closeEndPoint (localEndPoint node)
807 `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
808 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
809 ncEffectGetNodeStats from nid
810 unexpected ->
811 error $ "nodeController: unexpected message " ++ show unexpected
813-- [Unified: Table 10]
814ncEffectMonitor :: ProcessId -- ^ Who's watching?
815 -> Identifier -- ^ Who's being watched?
816 -> Maybe MonitorRef -- ^ 'Nothing' to link
817 -> NC ()
818ncEffectMonitor from them mRef = do
819 node <- ask
820 shouldLink <-
821 if not (isLocal node them)
822 then return True
823 else isValidLocalIdentifier them
824 case (shouldLink, isLocal node (ProcessIdentifier from)) of
825 (True, _) -> -- [Unified: first rule]
826 case mRef of
827 Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
828 Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
829 (False, True) -> -- [Unified: second rule]
830 notifyDied from them DiedUnknownId mRef
831 (False, False) -> -- [Unified: third rule]
832 -- TODO: this is the right sender according to the Unified semantics,
833 -- but perhaps having 'them' as the sender would make more sense
834 -- (see also: notifyDied)
835 ncSendToNode (processNodeId from) $ NCMsg
836 { ctrlMsgSender = NodeIdentifier (localNodeId node)
837 , ctrlMsgSignal = Died them DiedUnknownId
838 }
840-- [Unified: Table 11]
841ncEffectUnlink :: ProcessId -> Identifier -> NC ()
842ncEffectUnlink from them = do
843 node <- ask
844 when (isLocal node (ProcessIdentifier from)) $
845 case them of
846 ProcessIdentifier pid ->
847 postAsMessage from $ DidUnlinkProcess pid
848 NodeIdentifier nid ->
849 postAsMessage from $ DidUnlinkNode nid
850 SendPortIdentifier cid ->
851 postAsMessage from $ DidUnlinkPort cid
852 modify' $ links ^: BiMultiMap.delete them from ()
854-- [Unified: Table 11]
855ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
856ncEffectUnmonitor from ref = do
857 node <- ask
858 when (isLocal node (ProcessIdentifier from)) $
859 postAsMessage from $ DidUnmonitor ref
860 modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
862-- [Unified: Table 12]
863ncEffectDied :: Identifier -> DiedReason -> NC ()
864ncEffectDied ident reason = do
865 node <- ask
866 traceNotifyDied node ident reason
867 (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
868 (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
870-- _registry :: !(Map (String,NodeId) ProcessId)
872 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
874 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
875 forM_ uss $ \(us, _) ->
876 when (localOnly <= isLocal node (ProcessIdentifier us)) $
877 notifyDied us them reason Nothing
879 forM_ (Map.toList affectedMons) $ \(them, refs) ->
880 forM_ refs $ \(us, ref) ->
881 when (localOnly <= isLocal node (ProcessIdentifier us)) $
882 notifyDied us them reason (Just ref)
884 -- Notify remote nodes that the process died so it can be removed from monitor
885 -- lists.
886 mapM_ (forwardDeath node) $
887 [ nid | ProcessIdentifier pid <- [ident]
888 , i <- Set.toList $ Set.union
889 (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedLinks)
890 (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedMons)
891 , let nid = nodeOf i
892 , nid /= localNodeId node
893 ]
895 -- Delete monitors in the local node.
896 let deleteDeads :: (Ord a, Ord v)
897 => BiMultiMap a ProcessId v -> BiMultiMap a ProcessId v
898 deleteDeads = case ident of
899 -- deleteAllBy2nd is faster than partitionWithKeyBy2nd
900 ProcessIdentifier pid -> BiMultiMap.deleteAllBy2nd pid
901 _ -> snd . BiMultiMap.partitionWithKeyBy2nd
902 (\pid _ -> ident `impliesDeathOf` ProcessIdentifier pid)
903 unaffectedLinks' = deleteDeads unaffectedLinks
904 unaffectedMons' = deleteDeads unaffectedMons
906 modify' $ (links ^= unaffectedLinks') . (monitors ^= unaffectedMons')
908 modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
910 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
911 mapM (\(pid,nidlist) ->
912 case ident `impliesDeathOf` ProcessIdentifier pid of
913 True ->
914 do forM_ nidlist $ \(nid,_) ->
915 when (not $ isLocal node (NodeIdentifier nid))
916 (forwardDeath node nid)
917 return Nothing
918 False -> return $ Just (pid,nidlist) )
919 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
920 where
921 forwardDeath node nid = ncSendToNode nid
922 NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
923 , ctrlMsgSignal = Died ident reason
924 }
926-- [Unified: Table 13]
927ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
928ncEffectSpawn pid cProc ref = do
929 mProc <- unClosure cProc
930 -- If the closure does not exist, we spawn a process that throws an exception
931 -- This allows the remote node to find out what's happening
932 -- TODO:
933 let proc = case mProc of
934 Left err -> fail $ "Error: Could not resolve closure: " ++ err
935 Right p -> p
936 node <- ask
937 pid' <- liftIO $ forkProcess node proc
938 ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
940-- Unified semantics does not explicitly describe how to implement 'register',
941-- but mentions it's "very similar to nsend" (Table 14)
942-- We send a response indicated if the operation is invalid
943ncEffectRegister :: ProcessId -> String -> NodeId -> Maybe ProcessId -> Bool -> NC ()
944ncEffectRegister from label atnode mPid reregistration = do
945 node <- ask
946 currentVal <- gets (^. registeredHereFor label)
947 isOk <-
948 case mPid of
949 Nothing -> -- unregister request
950 return $ isJust currentVal
951 Just thepid -> -- register request
952 do isvalidlocal <- isValidLocalIdentifier (ProcessIdentifier thepid)
953 return $ (isNothing currentVal /= reregistration) &&
954 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
955 if isLocal node (NodeIdentifier atnode)
956 then do when isOk $
957 do modify' $ registeredHereFor label ^= mPid
958 updateRemote node currentVal mPid
959 case mPid of
960 (Just p) -> liftIO $ trace node (MxRegistered p label)
961 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
962 newVal <- gets (^. registeredHereFor label)
963 ncSendToProcess from $ unsafeCreateUnencodedMessage $
964 RegisterReply label isOk newVal
965 else let operation =
966 case reregistration of
967 True -> flip decList
968 False -> flip incList
969 in case mPid of
970 Nothing -> return ()
971 Just pid -> modify' $ registeredOnNodesFor pid ^: (maybeify $ operation atnode)
972 where updateRemote node (Just oldval) (Just newval) | processNodeId oldval /= processNodeId newval =
973 do forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
974 forward node (processNodeId newval) (Register label atnode (Just newval) False)
975 updateRemote node Nothing (Just newval) =
976 forward node (processNodeId newval) (Register label atnode (Just newval) False)
977 updateRemote node (Just oldval) Nothing =
978 forward node (processNodeId oldval) (Register label atnode (Just oldval) True)
979 updateRemote _ _ _ = return ()
980 maybeify f Nothing = unmaybeify $ f []
981 maybeify f (Just x) = unmaybeify $ f x
983 unmaybeify [] = Nothing
984 unmaybeify x = Just x
985 incList [] tag = [(tag,1)]
986 incList ((atag,acount):xs) tag | tag==atag = (atag,acount+1) : xs
987 incList (x:xs) tag = x : incList xs tag
988 decList [] _ = []
989 decList ((atag,1):xs) tag | atag == tag = xs
990 decList ((atag,n):xs) tag | atag == tag = (atag,n-1):xs
991 decList (x:xs) tag = x:decList xs tag
992 forward node to reg =
993 when (not $ isLocal node (NodeIdentifier to)) $
994 ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
995 , ctrlMsgSignal = reg
996 }
999-- Unified semantics does not explicitly describe 'whereis'
1000ncEffectWhereIs :: ProcessId -> String -> NC ()
1001ncEffectWhereIs from label = do
1002 mPid <- gets (^. registeredHereFor label)
1003 ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
1005-- [Unified: Table 14]
1006ncEffectNamedSend :: String -> Message -> NC ()
1007ncEffectNamedSend label msg = do
1008 mPid <- gets (^. registeredHereFor label)
1009 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
1010 forM_ mPid $ \to ->
1011 -- If this is a trace message we don't trace it to avoid entering a loop
1012 -- where trace messages produce more trace messages.
1013 ncSendToProcessAndTrace (label /= "trace.logger") to msg
1015-- [Issue #DP-20]
1016ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
1017ncEffectLocalSend = ncEffectLocalSendAndTrace True
1019ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1020ncEffectLocalSendAndTrace shouldTrace node to msg =
1021 liftIO $ withLocalProc node to $ \p -> do
1022 enqueue (processQueue p) msg
1023 when shouldTrace $ trace node (MxReceived to msg)
1025-- [Issue #DP-20]
1026ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
1027ncEffectLocalPortSend from msg = do
1028 node <- ask
1029 let pid = sendPortProcessId from
1030 cid = sendPortLocalId from
1031 liftIO $ withLocalProc node pid $ \proc -> do
1032 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
1033 case mChan of
1034 -- in the unlikely event we know nothing about this channel id,
1035 -- there's little to be done - perhaps some logging/tracing though...
1036 Nothing -> return ()
1037 Just (TypedChannel chan') -> do
1038 -- If ch is Nothing, the process has given up the read end of
1039 -- the channel and we simply ignore the incoming message - this
1040 ch <- deRefWeak chan'
1041 forM_ ch $ \chan -> deliverChan msg chan
1042 where deliverChan :: forall a . Message -> TQueue a -> IO ()
1043 deliverChan (UnencodedMessage _ raw) chan' =
1044 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1045 deliverChan (EncodedMessage _ _) _ =
1046 -- this will not happen unless someone screws with Primitives.hs
1047 error "invalid local channel delivery"
1049-- [Issue #69]
1050ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
1051ncEffectKill from to reason = do
1052 node <- ask
1053 when (isLocal node (ProcessIdentifier to)) $
1054 throwException to $ ProcessKillException from reason
1056-- [Issue #69]
1057ncEffectExit :: ProcessId -> ProcessId -> Message -> NC ()
1058ncEffectExit from to reason = do
1059 node <- ask
1060 when (isLocal node (ProcessIdentifier to)) $
1061 throwException to $ ProcessExitException from reason
1063-- [Issue #89]
1064ncEffectGetInfo :: ProcessId -> ProcessId -> NC ()
1065ncEffectGetInfo from pid =
1066 let lpid = processLocalId pid
1067 them = (ProcessIdentifier pid)
1068 in do
1069 node <- ask
1070 mProc <- liftIO $ withValidLocalState node
1071 $ return . (^. localProcessWithId lpid)
1072 case mProc of
1073 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1074 from (ProcessInfoNone DiedUnknownId)
1075 Just proc -> do
1076 itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
1077 gets (^. links)
1078 itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
1079 registered <- gets (^. registeredHere)
1080 size <- liftIO $ queueSize $ processQueue $ proc
1082 let reg = registeredNames registered
1083 dispatch (isLocal node (ProcessIdentifier from))
1084 from
1085 ProcessInfo {
1086 infoNode = (processNodeId pid)
1087 , infoRegisteredNames = reg
1088 , infoMessageQueueLength = size
1089 , infoMonitors = Set.toList itsMons
1090 , infoLinks = Set.toList itsLinks
1092 where dispatch :: (Serializable a)
1093 => Bool
1094 -> ProcessId
1095 -> a
1096 -> NC ()
1097 dispatch True dest pInfo = postAsMessage dest $ pInfo
1098 dispatch False dest pInfo =
1099 ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
1101 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1102 then (k:ks)
1103 else ks) []
1105ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1106ncEffectGetNodeStats from _nid = do
1107 node <- ask
1108 ncState <- StateT.get
1109 nodeState <- liftIO $ withValidLocalState node return
1110 let stats =
1111 NodeStats {
1112 nodeStatsNode = localNodeId node
1113 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1114 , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1115 , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1116 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1118 postAsMessage from stats
1120--------------------------------------------------------------------------------
1121-- Auxiliary --
1122--------------------------------------------------------------------------------
1124notifyDied :: ProcessId -- ^ Who to notify?
1125 -> Identifier -- ^ Who died?
1126 -> DiedReason -- ^ How did they die?
1127 -> Maybe MonitorRef -- ^ 'Nothing' for linking
1128 -> NC ()
1129notifyDied dest src reason mRef = do
1130 node <- ask
1131 case (isLocal node (ProcessIdentifier dest), mRef, src) of
1132 (True, Just ref, ProcessIdentifier pid) ->
1133 postAsMessage dest $ ProcessMonitorNotification ref pid reason
1134 (True, Just ref, NodeIdentifier nid) ->
1135 postAsMessage dest $ NodeMonitorNotification ref nid reason
1136 (True, Just ref, SendPortIdentifier cid) ->
1137 postAsMessage dest $ PortMonitorNotification ref cid reason
1138 (True, Nothing, ProcessIdentifier pid) ->
1139 throwException dest $ ProcessLinkException pid reason
1140 (True, Nothing, NodeIdentifier pid) ->
1141 throwException dest $ NodeLinkException pid reason
1142 (True, Nothing, SendPortIdentifier pid) ->
1143 throwException dest $ PortLinkException pid reason
1144 (False, _, _) ->
1145 -- The change in sender comes from [Unified: Table 10]
1146 ncSendToNode (processNodeId dest) $ NCMsg
1147 { ctrlMsgSender = NodeIdentifier (localNodeId node)
1148 , ctrlMsgSignal = Died src reason
1151-- | [Unified: Table 8]
1152destNid :: ProcessSignal -> Maybe NodeId
1153destNid (Link ident) = Just $ nodeOf ident
1154destNid (Unlink ident) = Just $ nodeOf ident
1155destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1156destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1157destNid (Spawn _ _) = Nothing
1158destNid (Register _ _ _ _) = Nothing
1159destNid (WhereIs _) = Nothing
1160destNid (NamedSend _ _) = Nothing
1161destNid (UnreliableSend _ _) = Nothing
1162-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1163-- then when a local process dies the monitoring/linking machinery will take
1164-- care of notifying remote nodes
1165destNid (Died _ _) = Nothing
1166destNid (Kill pid _) = Just $ processNodeId pid
1167destNid (Exit pid _) = Just $ processNodeId pid
1168destNid (GetInfo pid) = Just $ processNodeId pid
1169destNid (GetNodeStats nid) = Just nid
1170destNid (LocalSend pid _) = Just $ processNodeId pid
1171destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1172destNid (SigShutdown) = Nothing
1174-- | Check if a process is local to our own node
1175isLocal :: LocalNode -> Identifier -> Bool
1176isLocal nid ident = nodeOf ident == localNodeId nid
1178-- | Lookup a local closure
1179unClosure :: Typeable a => Closure a -> NC (Either String a)
1180unClosure closure = do
1181 rtable <- remoteTable <$> ask
1182 return (Static.unclosure rtable closure)
1184-- | Check if an identifier refers to a valid local object
1185isValidLocalIdentifier :: Identifier -> NC Bool
1186isValidLocalIdentifier ident = do
1187 node <- ask
1188 liftIO . withValidLocalState node $ \nSt ->
1189 case ident of
1190 NodeIdentifier nid ->
1191 return $ nid == localNodeId node
1192 ProcessIdentifier pid -> do
1193 let mProc = nSt ^. localProcessWithId (processLocalId pid)
1194 return $ isJust mProc
1195 SendPortIdentifier cid -> do
1196 let pid = sendPortProcessId cid
1197 mProc = nSt ^. localProcessWithId (processLocalId pid)
1198 case mProc of
1199 Nothing -> return False
1200 Just proc -> withMVar (processState proc) $ \pSt -> do
1201 let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
1202 return $ isJust mCh
1204--------------------------------------------------------------------------------
1205-- Messages to local processes --
1206--------------------------------------------------------------------------------
1208postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1209postAsMessage pid = postMessage pid . createUnencodedMessage
1211postMessage :: ProcessId -> Message -> NC ()
1212postMessage pid msg = do
1213 node <- ask
1214 liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
1216throwException :: Exception e => ProcessId -> e -> NC ()
1217throwException pid e = do
1218 node <- ask
1219 -- throwTo blocks until the exception is received by the target thread.
1220 -- We cannot easily make it happen asynchronpusly because then 'unlink'
1221 -- semantics would break.
1222 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1224withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1225withLocalProc node pid p =
1226 -- By [Unified: table 6, rule missing_process] messages to dead processes
1227 -- can silently be dropped
1228 let lpid = processLocalId pid in do
1229 join $ withValidLocalState node $ \vst ->
1230 return $ forM_ (vst ^. localProcessWithId lpid) p
1232--------------------------------------------------------------------------------
1233-- Accessors --
1234--------------------------------------------------------------------------------
1236links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
1237links = accessor _links (\ls st -> st { _links = ls })
1239monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
1240monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1242registeredHere :: Accessor NCState (Map String ProcessId)
1243registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry })
1245registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1246registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1248registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1249registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1251registeredOnNodesFor :: ProcessId -> Accessor NCState (Maybe [(NodeId,Int)])
1252registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1254-- | @splitNotif ident@ splits a notifications map into those
1255-- notifications that should trigger when 'ident' fails and those links that
1256-- should not.
1258-- There is a hierarchy between identifiers: failure of a node implies failure
1259-- of all processes on that node, and failure of a process implies failure of
1260-- all typed channels to that process. In other words, if 'ident' refers to a
1261-- node, then the /should trigger/ set will include
1263-- * the notifications for the node specifically
1264-- * the notifications for processes on that node, and
1265-- * the notifications for typed channels to processes on that node.
1267-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
1268-- include
1270-- * the notifications for that process specifically and
1271-- * the notifications for typed channels to that process.
1273-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1274splitNotif :: (Ord a, Ord v)
1275 => Identifier
1276 -> BiMultiMap Identifier a v
1277 -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1278splitNotif ident =
1279 BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
1281--------------------------------------------------------------------------------
1282-- Auxiliary --
1283--------------------------------------------------------------------------------
1285-- | Modify and evaluate the state
1286modify' :: MonadState s m => (s -> s) -> m ()
1287modify' f = StateT.get >>= \s -> StateT.put $! f s