@@ -21,6 +21,7 @@ module Control.Distributed.Process.Internal.Primitives
21 , matchUnknown
21 , matchUnknown
22 , AbstractMessage(..)
22 , AbstractMessage(..)
23 , matchAny
23 , matchAny
24 , matchChan
25 , matchChan
25 -- * Process management
26 -- * Process management
26 , terminate
27 , terminate
@@ -29,6 +30,9 @@ module Control.Distributed.Process.Internal.Primitives
31 , catchExit
32 , catchExit
34+ -- keep the exception constructor hidden, so that handling exit
35+ -- reasons must take place via the 'catchExit' family of primitives
32 , ProcessExitException()
36 , ProcessExitException()
33 , getSelfPid
37 , getSelfPid
34 , getSelfNode
38 , getSelfNode
@@ -311,24 +315,68 @@ matchIf c p = Match $ MatchMsg $ \msg ->
311 !decoded = decode (messageEncoding msg)
315 !decoded = decode (messageEncoding msg)
312 _ -> Nothing
316 _ -> Nothing
318+-- | Represents a received message and provides two basic operations on it.
314data AbstractMessage = AbstractMessage {
319data AbstractMessage = AbstractMessage {
315− forward :: ProcessId -> Process ()
320+ forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@
321+ , maybeHandleMessage :: forall a b. (Serializable a)
322+ => (a -> Process b) -> Process (Maybe b) {- ^ Handle the message.
323+ If the type of the message matches the type of the first argument to
324+ the supplied expression, then the expression will be evaluated against
325+ it. If this runtime type checking fails, then @Nothing@ will be returned
326+ to indicate the fact. If the check succeeds and evaluation proceeds
327+ however, the resulting value with be wrapped with @Just@.
318−-- | Match against an arbitrary message
331+-- | Match against an arbitrary message. 'matchAny' removes the first available
332+-- message from the process mailbox, and via the 'AbstractMessage' type,
333+-- supports forwarding /or/ handling the message /if/ it is of the correct
334+-- type. If /not/ of the right type, then the 'AbstractMessage'
335+-- @maybeHandleMessage@ function will not evaluate the supplied expression,
336+-- /but/ the message will still have been removed from the process mailbox!
319matchAny :: forall b. (AbstractMessage -> Process b) -> Match b
338matchAny :: forall b. (AbstractMessage -> Process b) -> Match b
320−matchAny p = Match $ MatchMsg $ Just . p . abstract
339+matchAny p = Match $ MatchMsg $ Just . p . abstract
341+-- | Match against an arbitrary message. 'matchAnyIf' will /only/ remove the
342+-- message from the process mailbox, /if/ the supplied condition matches. The
343+-- success (or failure) of runtime type checks in @maybeHandleMessage@ does not
344+-- count here, i.e., if the condition evaluates to @True@ then the message will
345+-- be removed from the process mailbox and decoded, but that does /not/
346+-- guarantee that an expression passed to @maybeHandleMessage@ will pass the
347+-- runtime type checks and therefore be evaluated. If the types do not match
348+-- up, then @maybeHandleMessage@ returns 'Nothing'.
349+matchAnyIf :: forall a b. (Serializable a)
351+ -> (AbstractMessage -> Process b)
353+matchAnyIf c p = Match $ MatchMsg $ \msg ->
354+ case messageFingerprint msg == fingerprint (undefined :: a) of
355+ True | c decoded -> Just (p (abstract msg))
358+ -- Make sure the value is fully decoded so that we don't hang to
359+ -- bytestrings when the calling process doesn't evaluate immediately
360+ !decoded = decode (messageEncoding msg)
322− abstract :: Message -> AbstractMessage
363+abstract :: Message -> AbstractMessage
323− abstract msg = AbstractMessage {
364+abstract msg = AbstractMessage {
324− forward = \them -> do
365+ forward = \them -> do
325− proc <- ask
366+ proc <- ask
326− liftIO $ sendPayload (processNode proc)
367+ liftIO $ sendPayload (processNode proc)
327− (ProcessIdentifier (processId proc))
368+ (ProcessIdentifier (processId proc))
328− (ProcessIdentifier them)
369+ (ProcessIdentifier them)
329− NoImplicitReconnect
370+ NoImplicitReconnect
330− (messageToPayload msg)
371+ (messageToPayload msg)
372+ , maybeHandleMessage = \(proc :: (a -> Process b)) -> do
373+ case messageFingerprint msg == fingerprint (undefined :: a) of
374+ True -> do { r <- proc (decoded :: a); return (Just r) }
377+ !decoded = decode (messageEncoding msg)
333-- | Remove any message from the queue
381-- | Remove any message from the queue
334matchUnknown :: Process b -> Match b
382matchUnknown :: Process b -> Match b
@@ -349,13 +397,15 @@ terminate :: Process a
349terminate = liftIO $ throwIO ProcessTerminationException
397terminate = liftIO $ throwIO ProcessTerminationException
351-- [Issue #110]
399-- [Issue #110]
352−-- | Die immediately - throws a 'ProcessExitException' with the given @reason@
400+-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
353die :: Serializable a => a -> Process b
401die :: Serializable a => a -> Process b
354die reason = do
402die reason = do
355 pid <- getSelfPid
403 pid <- getSelfPid
356 liftIO $ throwIO (ProcessExitException pid (createMessage reason))
404 liftIO $ throwIO (ProcessExitException pid (createMessage reason))
358−-- | Forceful request to kill a process
406+-- | Forceful request to kill a process. Where 'exit' provides an exception
407+-- that can be caught and handled, 'kill' throws an unexposed exception type
408+-- which cannot be handled explicitly (by type).
359kill :: ProcessId -> String -> Process ()
409kill :: ProcessId -> String -> Process ()
360-- NOTE: We send the message to our local node controller, which will then
410-- NOTE: We send the message to our local node controller, which will then
361-- forward it to a remote node controller (if applicable). Sending it directly
411-- forward it to a remote node controller (if applicable). Sending it directly
@@ -363,7 +413,9 @@ kill :: ProcessId -> String -> Process ()
363-- 'monitor' or 'link' request.
413-- 'monitor' or 'link' request.
364kill them reason = sendCtrlMsg Nothing (Kill them reason)
414kill them reason = sendCtrlMsg Nothing (Kill them reason)
366−-- | Graceful request to exit a process
416+-- | Graceful request to exit a process. Throws 'ProcessExitException' with the
417+-- supplied @reason@ encoded as a message. Any /exit signal/ raised in this
418+-- manner can be handled using the 'catchExit' family of functions.
367exit :: Serializable a => ProcessId -> a -> Process ()
419exit :: Serializable a => ProcessId -> a -> Process ()
368-- NOTE: We send the message to our local node controller, which will then
420-- NOTE: We send the message to our local node controller, which will then
369-- forward it to a remote node controller (if applicable). Sending it directly
421-- forward it to a remote node controller (if applicable). Sending it directly
@@ -371,7 +423,13 @@ exit :: Serializable a => ProcessId -> a -> Process ()
371-- 'monitor' or 'link' request.
423-- 'monitor' or 'link' request.
372exit them reason = sendCtrlMsg Nothing (Exit them (createMessage reason))
424exit them reason = sendCtrlMsg Nothing (Exit them (createMessage reason))
374−-- | Catches ProcessExitException
426+-- | Catches 'ProcessExitException'. The handler will not be applied unless its
427+-- type matches the encoded data stored in the exception (see the /reason/
428+-- argument given to the 'exit' primitive). If the handler cannot be applied,
429+-- the exception will be re-thrown.
431+-- To handle 'ProcessExitException' without regard for /reason/, see 'catch'.
432+-- To handle multiple /reasons/ of differing types, see 'catchesExit'.
375catchExit :: forall a b . (Show a, Serializable a)
433catchExit :: forall a b . (Show a, Serializable a)
376 => Process b
434 => Process b
377 -> (ProcessId -> a -> Process b)
435 -> (ProcessId -> a -> Process b)
@@ -388,6 +446,29 @@ catchExit act exitHandler = catch act handleExit
388 -- bytestrings if the caller doesn't use the value immediately
446 -- bytestrings if the caller doesn't use the value immediately
389 !decoded = decode (messageEncoding msg)
447 !decoded = decode (messageEncoding msg)
449+-- | As 'Control.Exception.catches' but allows for multiple handlers. Because
450+-- 'ProcessExitException' stores the exit @reason@ as a typed, encoded message,
451+-- a handler must accept an input of the expected type. In order to handle
452+-- a list of potentially different handlers (and therefore input types), a
453+-- handler passed to 'catchesExit' must accept 'AbstractMessage' and return
454+-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).
456+-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.
457+catchesExit :: Process b
458+ -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))]
460+catchesExit act handlers = catch act ((flip handleExit) handlers)
462+ handleExit :: ProcessExitException
463+ -> [(ProcessId -> AbstractMessage -> Process (Maybe b))]
465+ handleExit ex [] = liftIO $ throwIO ex
466+ handleExit ex@(ProcessExitException from msg) (h:hs) = do
467+ r <- h from (abstract msg)
469+ Nothing -> handleExit ex hs
391-- | Our own process ID
472-- | Our own process ID
392getSelfPid :: Process ProcessId
473getSelfPid :: Process ProcessId
393getSelfPid = processId <$> ask
474getSelfPid = processId <$> ask