git.iko.soy/distributed-process
on master help sign in
extend AbstractMessage with deferred type checking
commit8af66f4dcdd1d9e567ccc9febcbac9465759ce23 authorTim Watson <tim@rabbitmq.com> dateMon 14 Jan 2013, 14:45 (14 years ago) parentc88c5c0
3 files changed+173−14
distributed-process/src/Control/Distributed/Process.hs+2 distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+53−14 distributed-process/tests/TestCH.hs+118
distributed-process/src/Control/Distributed/Process.hs+2
@@ -39,6 +39,7 @@ module Control.Distributed.Process
3939 , matchUnknown
4040 , AbstractMessage(..)
4141 , matchAny
42+ , matchAnyIf
4243 , matchChan
4344 -- * Process management
4445 , spawn
@@ -192,6 +193,7 @@ import Control.Distributed.Process.Internal.Primitives
192193 , matchUnknown
193194 , AbstractMessage(..)
194195 , matchAny
196+ , matchAnyIf
195197 , matchChan
196198 -- Process management
197199 , terminate
distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+53−14
@@ -21,6 +21,7 @@ module Control.Distributed.Process.Internal.Primitives
2121 , matchUnknown
2222 , AbstractMessage(..)
2323 , matchAny
24+ , matchAnyIf
2425 , matchChan
2526 -- * Process management
2627 , terminate
@@ -311,24 +312,62 @@ matchIf c p = Match $ MatchMsg $ \msg ->
311312 !decoded = decode (messageEncoding msg)
312313 _ -> Nothing
313314
315+-- | Represents a received message and provides two operations on it.
314316data AbstractMessage = AbstractMessage {
315− forward :: ProcessId -> Process ()
317+ forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@
318+ , maybeHandleMessage :: forall a b. (Serializable a)
319+ => (a -> Process b) -> Process (Maybe b) -- ^ handle the message if it is of the given type
316320 }
317321
318−-- | Match against an arbitrary message
322+-- | Match against an arbitrary message. 'matchAny' removes the first available
323+-- message from the process mailbox, and via the 'AbstractMessage' type,
324+-- supports forwarding /or/ handling the message /if/ it is of the correct
325+-- type. If /not/ of the right type, then the 'AbstractMessage'
326+-- @maybeHandleMessage@ function will not evaluate the supplied expression,
327+-- /but/ the message will still have been removed from the process mailbox!
328+--
319329matchAny :: forall b. (AbstractMessage -> Process b) -> Match b
320−matchAny p = Match $ MatchMsg $ Just . p . abstract
321− where
322− abstract :: Message -> AbstractMessage
323− abstract msg = AbstractMessage {
324− forward = \them -> do
325− proc <- ask
326− liftIO $ sendPayload (processNode proc)
327− (ProcessIdentifier (processId proc))
328− (ProcessIdentifier them)
329− NoImplicitReconnect
330− (messageToPayload msg)
331− }
330+matchAny p = Match $ MatchMsg $ Just . p . abstract
331+
332+-- | Match against an arbitrary message. 'matchAnyIf' will /only/ remove the
333+-- message from the process mailbox, /if/ the supplied condition matches. The
334+-- success (or failure) of runtime type checks in @maybeHandleMessage@ does not
335+-- count here, i.e., if the condition evaluates to @True@ then the message will
336+-- be removed from the process mailbox and decoded, but that does /not/
337+-- guarantee that an expression passed to @maybeHandleMessage@ will pass the
338+-- runtime type checks and therefore be evaluated. If the types do not match
339+-- up, then @maybeHandleMessage@ returns 'Nothing'.
340+matchAnyIf :: forall a b. (Serializable a)
341+ => (a -> Bool)
342+ -> (AbstractMessage -> Process b)
343+ -> Match b
344+matchAnyIf c p = Match $ MatchMsg $ \msg ->
345+ case messageFingerprint msg == fingerprint (undefined :: a) of
346+ True | c decoded -> Just (p (abstract msg))
347+ where
348+ decoded :: a
349+ -- Make sure the value is fully decoded so that we don't hang to
350+ -- bytestrings when the calling process doesn't evaluate immediately
351+ !decoded = decode (messageEncoding msg)
352+ _ -> Nothing
353+
354+abstract :: Message -> AbstractMessage
355+abstract msg = AbstractMessage {
356+ forward = \them -> do
357+ proc <- ask
358+ liftIO $ sendPayload (processNode proc)
359+ (ProcessIdentifier (processId proc))
360+ (ProcessIdentifier them)
361+ NoImplicitReconnect
362+ (messageToPayload msg)
363+ , maybeHandleMessage = \(proc :: (a -> Process b)) -> do
364+ case messageFingerprint msg == fingerprint (undefined :: a) of
365+ True -> do { r <- proc (decoded :: a); return (Just r) }
366+ where
367+ decoded :: a
368+ !decoded = decode (messageEncoding msg)
369+ _ -> return Nothing
370+ }
332371
333372-- | Remove any message from the queue
334373matchUnknown :: Process b -> Match b
distributed-process/tests/TestCH.hs+118
@@ -757,6 +757,121 @@ testMatchAny transport = do
757757
758758 takeMVar clientDone
759759
760+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
761+-- in between, however we block 'Divide' requests ....
762+testMatchAnyHandle :: NT.Transport -> Assertion
763+testMatchAnyHandle transport = do
764+ proxyAddr <- newEmptyMVar
765+ clientDone <- newEmptyMVar
766+
767+ -- Math server
768+ forkIO $ do
769+ localNode <- newLocalNode transport initRemoteTable
770+ mathServer <- forkProcess localNode math
771+ proxyServer <- forkProcess localNode $ forever $ do
772+ receiveWait [
773+ matchAny (maybeForward mathServer)
774+ ]
775+ putMVar proxyAddr proxyServer
776+
777+ -- Client
778+ forkIO $ do
779+ localNode <- newLocalNode transport initRemoteTable
780+ mathServer <- readMVar proxyAddr
781+
782+ runProcess localNode $ do
783+ pid <- getSelfPid
784+ send mathServer (Add pid 1 2)
785+ 3 <- expect :: Process Double
786+ send mathServer (Divide pid 8 2)
787+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
788+ liftIO $ putMVar clientDone ()
789+
790+ takeMVar clientDone
791+ where maybeForward :: ProcessId -> AbstractMessage -> Process (Maybe ())
792+ maybeForward s msg =
793+ maybeHandleMessage msg (\m@(Add _ _ _) -> send s m)
794+
795+testMatchAnyNoHandle :: NT.Transport -> Assertion
796+testMatchAnyNoHandle transport = do
797+ addr <- newEmptyMVar
798+ clientDone <- newEmptyMVar
799+ serverDone <- newEmptyMVar
800+
801+ -- Math server
802+ forkIO $ do
803+ localNode <- newLocalNode transport initRemoteTable
804+ server <- forkProcess localNode $ forever $ do
805+ receiveWait [
806+ matchAnyIf
807+ -- the condition has type `Add -> Bool`
808+ (\(Add _ _ _) -> True)
809+ -- the match `AbstractMessage -> Process ()` will succeed!
810+ (\m -> do
811+ -- `String -> Process ()` does *not* match the input types however
812+ r <- (maybeHandleMessage m (\(_ :: String) -> die "NONSENSE" ))
813+ case r of
814+ Nothing -> return ()
815+ Just _ -> die "NONSENSE")
816+ ]
817+ -- we *must* have removed the message from our mailbox though!!!
818+ Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
819+ liftIO $ putMVar serverDone ()
820+ putMVar addr server
821+
822+ -- Client
823+ forkIO $ do
824+ localNode <- newLocalNode transport initRemoteTable
825+ server <- readMVar addr
826+
827+ runProcess localNode $ do
828+ pid <- getSelfPid
829+ send server (Add pid 1 2)
830+ -- we only care about the client having sent a message, so we're done
831+ liftIO $ putMVar clientDone ()
832+
833+ takeMVar clientDone
834+ takeMVar serverDone
835+
836+-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
837+-- unless the text body @/= "bar"@ - this case should time out rather than
838+-- removing the message from the process mailbox.
839+testMatchAnyIf :: NT.Transport -> Assertion
840+testMatchAnyIf transport = do
841+ echoAddr <- newEmptyMVar
842+ clientDone <- newEmptyMVar
843+
844+ -- echo server
845+ forkIO $ do
846+ localNode <- newLocalNode transport initRemoteTable
847+ echoServer <- forkProcess localNode $ forever $ do
848+ receiveWait [
849+ matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
850+ handleMessage
851+ ]
852+ putMVar echoAddr echoServer
853+
854+ -- Client
855+ forkIO $ do
856+ localNode <- newLocalNode transport initRemoteTable
857+ server <- readMVar echoAddr
858+
859+ runProcess localNode $ do
860+ pid <- getSelfPid
861+ send server (pid, "foo")
862+ "foo" <- expect
863+ send server (pid, "baz")
864+ "baz" <- expect
865+ send server (pid, "bar")
866+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
867+ liftIO $ putMVar clientDone ()
868+
869+ takeMVar clientDone
870+ where handleMessage :: AbstractMessage -> Process (Maybe ())
871+ handleMessage msg =
872+ maybeHandleMessage msg (\(pid :: ProcessId, (m :: String))
873+ -> do { send pid m; return () })
874+
760875-- Test 'receiveChanTimeout'
761876testReceiveChanTimeout :: NT.Transport -> Assertion
762877testReceiveChanTimeout transport = do
@@ -987,6 +1102,9 @@ tests (transport, transportInternals) = [
9871102 , testCase "RemoteRegistry" (testRemoteRegistry transport)
9881103 , testCase "SpawnLocal" (testSpawnLocal transport)
9891104 , testCase "MatchAny" (testMatchAny transport)
1105+ , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
1106+ , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
1107+ , testCase "MatchAnyIf" (testMatchAnyIf transport)
9901108 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
9911109 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
9921110 , testCase "KillLocal" (testKillLocal transport)