@@ -1,12 +1,23 @@
1−{-# LANGUAGE DeriveGeneric #-}
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ParallelListComp #-}
2module Control.Distributed.Process.Tests.Mx (tests) where
4module Control.Distributed.Process.Tests.Mx (tests) where
4import Control.Distributed.Process.Tests.Internal.Utils
6import Control.Distributed.Process.Tests.Internal.Utils
5import Network.Transport.Test (TestTransport(..))
7import Network.Transport.Test (TestTransport(..))
6−
8+import Control.Distributed.Process hiding (bracket, finally, try)
7−import Control.Concurrent (threadDelay)
8−import Control.Distributed.Process
9+import Control.Distributed.Process.Internal.Types
10+ ( ProcessExitException(..)
11+ , unsafeCreateUnencodedMessage
9import Control.Distributed.Process.Node
13import Control.Distributed.Process.Node
14+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
10import Control.Distributed.Process.Management
21import Control.Distributed.Process.Management
11 ( MxEvent(..)
22 ( MxEvent(..)
12 , MxAgentId(..)
23 , MxAgentId(..)
@@ -21,14 +32,15 @@ import Control.Distributed.Process.Management
21 , mxUpdateLocal
32 , mxUpdateLocal
22 , mxNotify
33 , mxNotify
23 , mxBroadcast
34 , mxBroadcast
26−import Control.Monad (void)
36+import Control.Monad (void, unless)
37+import Control.Monad.Catch(finally, bracket, try)
38+import Control.Rematch (equalTo)
27import Data.Binary
39import Data.Binary
28−import Data.List (find, sort)
40+import Data.List (find, sort, intercalate)
29−import Data.Maybe (isJust)
41+import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
30import Data.Typeable
42import Data.Typeable
31−import GHC.Generics
43+import GHC.Generics hiding (from)
32#if ! MIN_VERSION_base(4,6,0)
44#if ! MIN_VERSION_base(4,6,0)
33import Prelude hiding (catch, log)
45import Prelude hiding (catch, log)
@@ -44,7 +56,17 @@ data Publish = Publish
45instance Binary Publish where
57instance Binary Publish where
59+awaitExit :: ProcessId -> Process ()
61+ withMonitorRef pid $ \ref -> do
63+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == ref)
67+ withMonitorRef p = bracket (monitor p) unmonitor
47−testAgentBroadcast :: TestResult () -> Process ()
69+testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
48testAgentBroadcast result = do
70testAgentBroadcast result = do
49 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
71 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
@@ -59,7 +81,7 @@ testAgentBroadcast result = do
59 mxNotify ()
81 mxNotify ()
60 -- Once the publisher has seen our message, it will broadcast the Publish
82 -- Once the publisher has seen our message, it will broadcast the Publish
61 -- and the consumer will see that and send the result to our typed channel.
83 -- and the consumer will see that and send the result to our typed channel.
62− stash result =<< receiveChan resultRP
84+ stash result =<< receiveChanTimeout 10000000 resultRP
64 kill publisher "finished"
86 kill publisher "finished"
65 kill consumer "finished"
87 kill consumer "finished"
@@ -67,7 +89,7 @@ testAgentBroadcast result = do
67testAgentDualInput :: TestResult (Maybe Int) -> Process ()
89testAgentDualInput :: TestResult (Maybe Int) -> Process ()
68testAgentDualInput result = do
90testAgentDualInput result = do
69 (sp, rp) <- newChan
91 (sp, rp) <- newChan
70− _ <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
92+ s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
71 mxSink $ (\(i :: Int) -> do
93 mxSink $ (\(i :: Int) -> do
72 mxSetLocal . (+i) =<< mxGetLocal
94 mxSetLocal . (+i) =<< mxGetLocal
73 i' <- mxGetLocal
95 i' <- mxGetLocal
@@ -77,6 +99,8 @@ testAgentDualInput result = do
77 else mxReady)
99 else mxReady)
80 mxNotify (1 :: Int)
104 mxNotify (1 :: Int)
81 nsend "sum-agent" (3 :: Int)
105 nsend "sum-agent" (3 :: Int)
82 mxNotify (2 :: Int)
106 mxNotify (2 :: Int)
@@ -84,6 +108,10 @@ testAgentDualInput result = do
84 mxNotify (5 :: Int)
108 mxNotify (5 :: Int)
86 stash result =<< receiveChanTimeout 10000000 rp
110 stash result =<< receiveChanTimeout 10000000 rp
111+ died <- receiveTimeout 10000000 [
112+ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRef) (const $ return True)
114+ died `shouldBe` equalTo (Just True)
88testAgentPrioritisation :: TestResult [String] -> Process ()
116testAgentPrioritisation :: TestResult [String] -> Process ()
89testAgentPrioritisation result = do
117testAgentPrioritisation result = do
@@ -95,12 +123,12 @@ testAgentPrioritisation result = do
96 let name = "prioritising-agent"
124 let name = "prioritising-agent"
97 (sp, rp) <- newChan
125 (sp, rp) <- newChan
98− void $ mxAgent (MxAgentId name) ["first"] [
126+ s <- mxAgent (MxAgentId name) ["first"] [
99− mxSink (\(s :: String) -> do
127+ mxSink (\(s :: String) -> do
100− mxUpdateLocal ((s:))
128+ mxUpdateLocal (s:)
101 st <- mxGetLocal
129 st <- mxGetLocal
102 case length st of
130 case length st of
103− n | n == 5 -> do liftMX $ sendChan sp st
131+ n | n == 5 -> do liftMX $ sendChan sp (sort st)
104 mxDeactivate "finished"
132 mxDeactivate "finished"
105 _ -> mxReceive -- go to the mailbox
133 _ -> mxReceive -- go to the mailbox
@@ -111,23 +139,33 @@ testAgentPrioritisation result = do
111 mxNotify "fourth"
139 mxNotify "fourth"
112 nsend name "fifth"
140 nsend name "fifth"
114− stash result . sort =<< receiveChan rp
142+ stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
116testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
145testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
117testAgentMailboxHandling result = do
146testAgentMailboxHandling result = do
118 (sp, rp) <- newChan
147 (sp, rp) <- newChan
119− agent <- mxAgent (MxAgentId "listener-agent") () [
148+ agent <- mxAgent (MxAgentId "mailbox-agent") () [
120 mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
149 mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
123− nsend "listener-agent" ()
152+ nsend "mailbox-agent" ()
125 stash result =<< receiveChanTimeout 1000000 rp
154 stash result =<< receiveChanTimeout 1000000 rp
126 kill agent "finished"
155 kill agent "finished"
128testAgentEventHandling :: TestResult Bool -> Process ()
157testAgentEventHandling :: TestResult Bool -> Process ()
129testAgentEventHandling result = do
158testAgentEventHandling result = do
160+ -- because this test is a bit racy, let's ensure it can't run indefinitely
161+ timer <- spawnLocal $ do
162+ pause (1000000 * 60 * 5)
163+ -- okay we've waited 5 mins, let's kill the test off if it's stuck...
165+ kill us "Test Timed Out"
130 let initState = [] :: [MxEvent]
167 let initState = [] :: [MxEvent]
131 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
169 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
132 (mxSink $ \ev -> do
170 (mxSink $ \ev -> do
133 st <- mxGetLocal
171 st <- mxGetLocal
@@ -136,7 +174,7 @@ testAgentEventHandling result = do
136 (MxSpawned _) -> mxSetLocal (ev:st)
174 (MxSpawned _) -> mxSetLocal (ev:st)
137 (MxProcessDied _ _) -> mxSetLocal (ev:st)
175 (MxProcessDied _ _) -> mxSetLocal (ev:st)
138 _ -> return ()
176 _ -> return ()
139− act >> mxReady),
177+ act >> (liftMX $ sendChan rc ()) >> mxReady),
140 (mxSink $ \(ev, sp :: SendPort Bool) -> do
178 (mxSink $ \(ev, sp :: SendPort Bool) -> do
141 st <- mxGetLocal
179 st <- mxGetLocal
142 let found =
180 let found =
@@ -156,50 +194,349 @@ testAgentEventHandling result = do
159− _ <- monitor agentPid
197+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
160− (sp, rp) <- newChan
200+ (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
161− pid <- spawnLocal $ sendChan sp ()
202+ pid <- spawnLocal $ expect >>= sendChan sp
162− () <- receiveChan rp
164 -- By waiting for a monitor notification, we have a
204 -- By waiting for a monitor notification, we have a
165− -- higher probably that the agent has seen
205+ -- higher probably that the agent has seen the spawn and died events
166 monitor pid
206 monitor pid
209+ rct <- receiveChanTimeout 10000000 rp
210+ unless (isJust rct) $ die "No response on channel"
167− receiveWait [ match (\(ProcessMonitorNotification _ _ _) -> return ()) ]
212+ pmn <- receiveTimeout 2000000 [ match (\ProcessMonitorNotification{} -> return ()) ]
213+ unless (isJust pmn) $ die "No monitor notification arrived"
215+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
169 (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
218 (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
170 mxNotify (MxSpawned pid, replyTo)
219 mxNotify (MxSpawned pid, replyTo)
171 mxNotify (MxProcessDied pid DiedNormal, replyTo)
220 mxNotify (MxProcessDied pid DiedNormal, replyTo)
173− seenAlive <- receiveChan reply
222+ seenAlive <- receiveChanTimeout 10000000 reply
174− seenDead <- receiveChan reply
223+ seenDead <- receiveChanTimeout 10000000 reply
225+ stash result $ foldr (&&) True $ catMaybes [seenAlive, seenDead]
226+ kill timer "test-complete"
227+ kill agentPid "test-complete"
229+ faff delay port = do
230+ res <- receiveChanTimeout delay port
231+ unless (isNothing res) $ pause delay
233+testMxRegEvents :: TestResult () -> Process ()
234+testMxRegEvents result = do
235+ {- This test only deals with the local case, to ensure that we are being
236+ notified in the expected order - the remote cases related to the
237+ behaviour of the node controller are contained in the CH test suite. -}
238+ ensure (stash result ()) $ do
239+ let label = "testMxRegEvents"
240+ let agentLabel = "mxRegEvents-agent"
242+ (regChan, regSink) <- newChan
243+ (unRegChan, unRegSink) <- newChan
244+ agent <- mxAgent (MxAgentId agentLabel) () [
247+ MxRegistered pid label'
248+ | label' == label -> liftMX $ sendChan regChan (label', pid)
249+ MxUnRegistered pid label'
250+ | label' == label -> liftMX $ sendChan unRegChan (label', pid)
255+ p1 <- spawnLocal expect
256+ p2 <- spawnLocal expect
259+ reg1 <- receiveChanTimeout delay regSink
260+ reg1 `shouldBe` equalTo (Just (label, p1))
263+ unreg1 <- receiveChanTimeout delay unRegSink
264+ unreg1 `shouldBe` equalTo (Just (label, p1))
267+ reg2 <- receiveChanTimeout delay regSink
268+ reg2 `shouldBe` equalTo (Just (label, p2))
271+ unreg2 <- receiveChanTimeout delay unRegSink
272+ unreg2 `shouldBe` equalTo (Just (label, p2))
274+ reg3 <- receiveChanTimeout delay regSink
275+ reg3 `shouldBe` equalTo (Just (label, p1))
277+ mapM_ (flip kill $ "test-complete") [agent, p1, p2]
279+testMxRegMon :: LocalNode -> TestResult () -> Process ()
280+testMxRegMon remoteNode result = do
281+ ensure (stash result ()) $ do
282+ -- ensure that when a registered process dies, we get a notification that
283+ -- it has been unregistered as well as seeing the name get removed
284+ let label1 = "aaaaa"
285+ let label2 = "bbbbb"
286+ let isValid l = l == label1 || l == label2
287+ let agentLabel = "mxRegMon-agent"
289+ (regChan, regSink) <- newChan
290+ (unRegChan, unRegSink) <- newChan
291+ agent <- mxAgent (MxAgentId agentLabel) () [
294+ MxRegistered pid label
295+ | isValid label -> liftMX $ sendChan regChan (label, pid)
296+ MxUnRegistered pid label
297+ | isValid label -> liftMX $ sendChan unRegChan (label, pid)
303+ liftIO $ forkProcess remoteNode $ do
304+ getSelfPid >>= sendChan sp
305+ expect :: Process ()
307+ p1 <- receiveChan rp
310+ reg1 <- receiveChanTimeout delay regSink
311+ reg1 `shouldBe` equalTo (Just (label1, p1))
314+ reg2 <- receiveChanTimeout delay regSink
315+ reg2 `shouldBe` equalTo (Just (label2, p1))
317+ n1 <- whereis label1
318+ n1 `shouldBe` equalTo (Just p1)
320+ n2 <- whereis label2
321+ n2 `shouldBe` equalTo (Just p1)
176− stash result $ seenAlive && seenDead
325+ unreg1 <- receiveChanTimeout delay unRegSink
326+ unreg2 <- receiveChanTimeout delay unRegSink
328+ let evts = [unreg1, unreg2]
329+ -- we can't rely on the order of the values in the node controller's
330+ -- map (it's either racy to do so, or no such guarantee exists for Data.Map),
331+ -- so we simply verify that we received the un-registration events we expect
332+ evts `shouldContain` (Just (label1, p1))
333+ evts `shouldContain` (Just (label2, p1))
335+ kill agent "test-complete"
337+ensure :: Process () -> Process () -> Process ()
338+ensure = flip finally
340+testNSend :: (String -> () -> Process ())
346+ let label = "testMxSend" ++ (show us)
347+ let isValid = isValidLabel n label
349+ testMxSend n label $ \p1 sink -> do
351+ reg1 <- receiveChanTimeout delay sink
353+ Just (MxRegistered pd lb)
354+ | pd == p1 && isValid lb -> return ()
355+ _ -> die $ "Reg-Failed: " ++ show reg1
359+ sent <- receiveChanTimeout delay sink
361+ Just (MxSentToName lb by _)
362+ | by == us && isValid lb -> return True
363+ _ -> die $ "Send-Failed: " ++ show sent
366+ isValidLabel nd l1 l2
368+ | isJust nd = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust nd)
371+testSend :: (ProcessId -> () -> Process ())
376+ let label = "testMxSend" ++ (show us)
377+ testMxSend n label $ \p1 sink -> do
381+ -- verify the management event
382+ sent <- receiveChanTimeout 5000000 sink
384+ Just (MxSent pidTo pidFrom _)
385+ | pidTo == p1 && pidFrom == us -> return True
388+testChan :: (SendPort () -> () -> Process ())
391+testChan op n = testMxSend n "" $ \p1 sink -> do
396+ cleared <- receiveChanTimeout 2000000 sink
398+ Just (MxSent pidTo pidFrom _)
399+ | pidTo == p1 && pidFrom == us -> return ()
400+ _ -> die "Received uncleared Mx Event"
402+ chan <- receiveTimeout 5000000 [ match return ]
403+ let ch' = fromMaybe (error "No reply chan received") chan
408+ -- verify the management event
409+ sent <- receiveChanTimeout 5000000 sink
411+ Just (MxSentToPort sId spId _)
412+ | sId == us && spId == sendPortId ch' -> return True
415+type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
417+testMxSend :: Maybe LocalNode -> String -> SendTest -> Process ()
418+testMxSend mNode label test = do
421+ (chan, sink) <- newChan
422+ agent <- mxAgent (MxAgentId $ agentLabel us) () [
425+ m@(MxSentToPort _ cid _)
426+ | cid /= sendPortId chan
427+ && cid /= sendPortId sp -> liftMX $ sendChan chan m
428+ m@(MxSent _ fromPid _)
429+ | fromPid == us -> liftMX $ sendChan chan m
430+ m@(MxSentToName _ fromPid _)
431+ | fromPid == us -> liftMX $ sendChan chan m
432+ m@(MxRegistered _ name)
433+ | name == label -> liftMX $ sendChan chan m
439+ Nothing -> void $ spawnLocal (proc sp)
440+ Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
442+ p1 <- receiveChanTimeout 2000000 rp
443+ unless (isJust p1) $ die "Timed out waiting for ProcessId"
444+ res <- try $ test (fromJust p1) sink
447+ kill (fromJust p1) "bye"
450+ Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
451+ Right tr -> tr `shouldBe` equalTo True
455+ agentLabel s = "mx-unsafe-check-agent-" ++ show s
456+ proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
458+ go :: Maybe (ReceivePort ()) -> Process ()
459+ go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
460+ go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
462+ replyChannel p' = do
178tests :: TestTransport -> IO [Test]
467tests :: TestTransport -> IO [Test]
179tests TestTransport{..} = do
468tests TestTransport{..} = do
180 node1 <- newLocalNode testTransport initRemoteTable
469 node1 <- newLocalNode testTransport initRemoteTable
470+ node2 <- newLocalNode testTransport initRemoteTable
182− testGroup "Mx Agents" [
472+ testGroup "MxAgents" [
183− testCase "Event Handling"
473+ testCase "EventHandling"
184− (delayedAssertion
474+ (delayedAssertion
185− "expected True, but events where not as expected"
475+ "expected True, but events where not as expected"
186− node1 True testAgentEventHandling)
476+ node1 True testAgentEventHandling)
187− , testCase "Inter-Agent Broadcast"
477+ , testCase "InterAgentBroadcast"
188− (delayedAssertion
478+ (delayedAssertion
189− "expected (), but no broadcast was received"
479+ "expected (), but no broadcast was received"
190− node1 () testAgentBroadcast)
480+ node1 (Just ()) testAgentBroadcast)
191− , testCase "Agent Mailbox Handling"
481+ , testCase "AgentMailboxHandling"
192− (delayedAssertion
482+ (delayedAssertion
193− "expected (Just ()), but no regular (mailbox) input was handled"
483+ "expected (Just ()), but no regular (mailbox) input was handled"
194− node1 (Just ()) testAgentMailboxHandling)
484+ node1 (Just ()) testAgentMailboxHandling)
195− , testCase "Agent Dual Input Handling"
485+ , testCase "AgentDualInputHandling"
196− (delayedAssertion
486+ (delayedAssertion
197− "expected sum = 15, but the result was Nothing"
487+ "expected sum = 15, but the result was Nothing"
198− node1 (Just 15 :: Maybe Int) testAgentDualInput)
488+ node1 (Just 15 :: Maybe Int) testAgentDualInput)
199− , testCase "Agent Input Prioritisation"
489+ , testCase "AgentInputPrioritisation"
200− (delayedAssertion
490+ (delayedAssertion
201− "expected [first, second, third, fourth, fifth], but result diverged"
491+ "expected [first, second, third, fourth, fifth], but result diverged"
202− node1 (sort ["first", "second",
492+ node1 (sort ["first", "second",
203− "third", "fourth",
493+ "third", "fourth",
204− "fifth"]) testAgentPrioritisation)
494+ "fifth"]) testAgentPrioritisation)
496+ , testGroup "MxEvents" [
497+ testCase "NameRegistrationEvents"
499+ "expected registration events to map to the correct ProcessId"
500+ node1 () testMxRegEvents)
501+ , testCase "PostDeathNameUnRegistrationEvents"
503+ "expected process deaths to result in unregistration events"
504+ node1 () (testMxRegMon node2))
505+ , testGroup "SendEvents" $ buildTestCases node1 node2
509+ buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
511+ ("nsend", testNSend nsend)
512+ , ("Unsafe.nsend", testNSend Unsafe.nsend)
513+ , ("nsendRemote", testNSend (nsendRemote nid))
514+ , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
517+ ("send", testSend send)
518+ , ("Unsafe.send", testSend Unsafe.send)
519+ , ("usend", testSend usend)
520+ , ("Unsafe.usend", testSend Unsafe.usend)
521+ , ("sendChan", testChan sendChan)
522+ , ("Unsafe.sendChan", testChan Unsafe.sendChan)
525+ ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526+ , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
532+ -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
535+ [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536+ testCase (intercalate "-" [caseName, caseSuffix])
537+ (runProcess n (caseImpl caseNode))
538+ | (caseName, caseImpl) <- groupCases
540+ | (groupName, groupCases) <- specs
541+ , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]