git.iko.soy/distributed-process
on master help sign in
introduce a 'catchesExit' primitive, document "exit signals", export API
e2cd2d5Tim Watson
distributed-process/src/Control/Distributed/Process.hs+8−2
@@ -5,7 +5,7 @@
5-- Peyton Jones
5-- Peyton Jones
6-- (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
6-- (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
7-- although some of the details are different. The precise message passing
7-- although some of the details are different. The precise message passing
8−-- semantics are based on /A unified semantics for future Erlang/ by Hans
8+-- semantics are based on /A unified semantics for future Erlang/ by Hans
9-- Svensson, Lars-Åke Fredlund and Clara Benac Earle.
9-- Svensson, Lars-Åke Fredlund and Clara Benac Earle.
10module Control.Distributed.Process
10module Control.Distributed.Process
11 ( -- * Basic types
11 ( -- * Basic types
@@ -49,6 +49,7 @@ module Control.Distributed.Process
49 , kill
49 , kill
50 , exit
50 , exit
51 , catchExit
51 , catchExit
52+ , catchesExit
52 , ProcessTerminationException(..)
53 , ProcessTerminationException(..)
53 , ProcessRegistrationException(..)
54 , ProcessRegistrationException(..)
54 , SpawnRef
55 , SpawnRef
@@ -201,6 +202,7 @@ import Control.Distributed.Process.Internal.Primitives
201 , die
202 , die
202 , exit
203 , exit
203 , catchExit
204 , catchExit
205+ , catchesExit
204 , kill
206 , kill
205 , getSelfPid
207 , getSelfPid
206 , getSelfNode
208 , getSelfNode
@@ -363,7 +365,11 @@ spawnMonitor nid proc = do
363-- "Control.Distributed.Process.Closure".
365-- "Control.Distributed.Process.Closure".
364--
366--
365-- See also 'spawn'.
367-- See also 'spawn'.
368+call :: Serializable a
366−call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
369+ => Static (SerializableDict a)
370+ -> NodeId
371+ -> Closure (Process a)
372+ -> Process a
367call dict nid proc = do
373call dict nid proc = do
368 us <- getSelfPid
374 us <- getSelfPid
369 (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
375 (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+48−6
@@ -30,6 +30,9 @@ module Control.Distributed.Process.Internal.Primitives
30 , kill
30 , kill
31 , exit
31 , exit
32 , catchExit
32 , catchExit
33+ , catchesExit
34+ -- keep the exception constructor hidden, so that handling exit
35+ -- reasons must take place via the 'catchExit' family of primitives
33 , ProcessExitException()
36 , ProcessExitException()
34 , getSelfPid
37 , getSelfPid
35 , getSelfNode
38 , getSelfNode
@@ -312,11 +315,17 @@ matchIf c p = Match $ MatchMsg $ \msg ->
312 !decoded = decode (messageEncoding msg)
315 !decoded = decode (messageEncoding msg)
313 _ -> Nothing
316 _ -> Nothing
314
317
315−-- | Represents a received message and provides two operations on it.
318+-- | Represents a received message and provides two basic operations on it.
316data AbstractMessage = AbstractMessage {
319data AbstractMessage = AbstractMessage {
317 forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@
320 forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@
318 , maybeHandleMessage :: forall a b. (Serializable a)
321 , maybeHandleMessage :: forall a b. (Serializable a)
319− => (a -> Process b) -> Process (Maybe b) -- ^ handle the message if it is of the given type
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@.
328+ -}
320 }
329 }
321
330
322-- | Match against an arbitrary message. 'matchAny' removes the first available
331-- | Match against an arbitrary message. 'matchAny' removes the first available
@@ -388,13 +397,15 @@ terminate :: Process a
388terminate = liftIO $ throwIO ProcessTerminationException
397terminate = liftIO $ throwIO ProcessTerminationException
389
398
390-- [Issue #110]
399-- [Issue #110]
391−-- | Die immediately - throws a 'ProcessExitException' with the given @reason@
400+-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
392die :: Serializable a => a -> Process b
401die :: Serializable a => a -> Process b
393die reason = do
402die reason = do
394 pid <- getSelfPid
403 pid <- getSelfPid
395 liftIO $ throwIO (ProcessExitException pid (createMessage reason))
404 liftIO $ throwIO (ProcessExitException pid (createMessage reason))
396
405
397−-- | 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).
398kill :: ProcessId -> String -> Process ()
409kill :: ProcessId -> String -> Process ()
399-- 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
400-- 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
@@ -402,7 +413,9 @@ kill :: ProcessId -> String -> Process ()
402-- 'monitor' or 'link' request.
413-- 'monitor' or 'link' request.
403kill them reason = sendCtrlMsg Nothing (Kill them reason)
414kill them reason = sendCtrlMsg Nothing (Kill them reason)
404
415
405−-- | 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.
406exit :: Serializable a => ProcessId -> a -> Process ()
419exit :: Serializable a => ProcessId -> a -> Process ()
407-- 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
408-- 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
@@ -410,7 +423,13 @@ exit :: Serializable a => ProcessId -> a -> Process ()
410-- 'monitor' or 'link' request.
423-- 'monitor' or 'link' request.
411exit them reason = sendCtrlMsg Nothing (Exit them (createMessage reason))
424exit them reason = sendCtrlMsg Nothing (Exit them (createMessage reason))
412
425
413−-- | 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.
430+--
431+-- To handle 'ProcessExitException' without regard for /reason/, see 'catch'.
432+-- To handle multiple /reasons/ of differing types, see 'catchesExit'.
414catchExit :: forall a b . (Show a, Serializable a)
433catchExit :: forall a b . (Show a, Serializable a)
415 => Process b
434 => Process b
416 -> (ProcessId -> a -> Process b)
435 -> (ProcessId -> a -> Process b)
@@ -427,6 +446,29 @@ catchExit act exitHandler = catch act handleExit
427 -- bytestrings if the caller doesn't use the value immediately
446 -- bytestrings if the caller doesn't use the value immediately
428 !decoded = decode (messageEncoding msg)
447 !decoded = decode (messageEncoding msg)
429
448
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@).
455+--
456+-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.
457+catchesExit :: Process b
458+ -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))]
459+ -> Process b
460+catchesExit act handlers = catch act ((flip handleExit) handlers)
461+ where
462+ handleExit :: ProcessExitException
463+ -> [(ProcessId -> AbstractMessage -> Process (Maybe b))]
464+ -> Process b
465+ handleExit ex [] = liftIO $ throwIO ex
466+ handleExit ex@(ProcessExitException from msg) (h:hs) = do
467+ r <- h from (abstract msg)
468+ case r of
469+ Nothing -> handleExit ex hs
470+ Just p -> return p
471+
430-- | Our own process ID
472-- | Our own process ID
431getSelfPid :: Process ProcessId
473getSelfPid :: Process ProcessId
432getSelfPid = processId <$> ask
474getSelfPid = processId <$> ask
distributed-process/tests/TestCH.hs+17
@@ -1009,6 +1009,22 @@ testKillRemote transport = do
1009
1009
1010 takeMVar done
1010 takeMVar done
1011
1011
1012+testCatchesExit :: NT.Transport -> Assertion
1013+testCatchesExit transport = do
1014+ localNode <- newLocalNode transport initRemoteTable
1015+ done <- newEmptyMVar
1016+
1017+ _ <- forkProcess localNode $ do
1018+ (die ("foobar", 123 :: Int))
1019+ `catchesExit` [
1020+ (\_ m -> maybeHandleMessage m (\(_ :: String) -> return ()))
1021+ , (\_ m -> maybeHandleMessage m (\(_ :: Maybe Int) -> return ()))
1022+ , (\_ m -> maybeHandleMessage m (\(_ :: String, _ :: Int)
1023+ -> (liftIO $ putMVar done ()) >> return ()))
1024+ ]
1025+
1026+ takeMVar done
1027+
1012testDie :: NT.Transport -> Assertion
1028testDie :: NT.Transport -> Assertion
1013testDie transport = do
1029testDie transport = do
1014 localNode <- newLocalNode transport initRemoteTable
1030 localNode <- newLocalNode transport initRemoteTable
@@ -1111,6 +1127,7 @@ tests (transport, transportInternals) = [
1111 , testCase "KillRemote" (testKillRemote transport)
1127 , testCase "KillRemote" (testKillRemote transport)
1112 , testCase "Die" (testDie transport)
1128 , testCase "Die" (testDie transport)
1113 , testCase "PrettyExit" (testPrettyExit transport)
1129 , testCase "PrettyExit" (testPrettyExit transport)
1130+ , testCase "CatchesExit" (testCatchesExit transport)
1114 , testCase "ExitLocal" (testExitLocal transport)
1131 , testCase "ExitLocal" (testExitLocal transport)
1115 , testCase "ExitRemote" (testExitRemote transport)
1132 , testCase "ExitRemote" (testExitRemote transport)
1116 ]
1133 ]