1{-# LANGUAGE TemplateHaskell, KindSignatures #-}
2module Control.Distributed.Process.Tests.Closure (tests) where
4import Network.Transport.Test (TestTransport(..))
6import Data.ByteString.Lazy (empty)
8import Data.Typeable (Typeable)
10import Control.Monad (join, replicateM, forever, replicateM_, void, when, unless)
11import Control.Exception (IOException, throw)
12import Control.Concurrent (forkIO, threadDelay)
13import Control.Concurrent.MVar
22import Control.Applicative ((<$>))
23import System.Random (randomIO)
24import Control.Distributed.Process
25import Control.Distributed.Process.Closure
26import Control.Distributed.Process.Node
27import Control.Distributed.Process.Internal.Types
32import Control.Distributed.Static (staticLabel, staticClosure)
33import qualified Network.Transport as NT
35import Test.HUnit (Assertion)
36import Test.Framework (Test)
37import Test.Framework.Providers.HUnit (testCase)
39--------------------------------------------------------------------------------
40-- Supporting definitions --
41--------------------------------------------------------------------------------
43quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
44quintuple a b c d e = (a, b, c, d, e)
46sdictInt :: SerializableDict Int
47sdictInt = SerializableDict
49factorial :: Int -> Process Int
51factorial n = (n *) <$> factorial (n - 1)
53addInt :: Int -> Int -> Int
56putInt :: Int -> MVar Int -> IO ()
59sendPid :: ProcessId -> Process ()
64wait :: Int -> Process ()
65wait = liftIO . threadDelay
67expectUnit :: Process ()
70isPrime :: Integer -> Process Bool
71isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
73 sieve :: [Integer] -> [Integer]
74 sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
75 sieve [] = error "Uh oh -- we've run out of primes"
77-- | First argument indicates empty closure environment
78typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
79typedPingServer () rport = forever $ do
80 sport <- receiveChan rport
83signal :: ProcessId -> Process ()
84signal pid = send pid ()
99randomElement :: [a] -> IO a
102 return (xs !! (ix `mod` length xs))
105 [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
106 dfib (_, reply, 0) = sendChan reply 0
107 dfib (_, reply, 1) = sendChan reply 1
108 dfib (nids, reply, n) = do
109 nid1 <- liftIO $ randomElement nids
110 nid2 <- liftIO $ randomElement nids
111 (sport, rport) <- newChan
112 spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
113 spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
114 n1 <- receiveChan rport
115 n2 <- receiveChan rport
116 sendChan reply $ n1 + n2
120-- Just try creating a static polymorphic value
121staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
122 => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
123staticQuintuple = $(mkStatic 'quintuple)
125factorialClosure :: Int -> Closure (Process Int)
126factorialClosure = $(mkClosure 'factorial)
128addIntClosure :: Int -> Closure (Int -> Int)
129addIntClosure = $(mkClosure 'addInt)
131putIntClosure :: Int -> Closure (MVar Int -> IO ())
132putIntClosure = $(mkClosure 'putInt)
134sendPidClosure :: ProcessId -> Closure (Process ())
135sendPidClosure = $(mkClosure 'sendPid)
137sendFac :: Int -> ProcessId -> Closure (Process ())
138sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
140factorialOf :: Closure (Int -> Process Int)
141factorialOf = staticClosure $(mkStatic 'factorial)
143factorial' :: Int -> Closure (Process Int)
144factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
146waitClosure :: Int -> Closure (Process ())
147waitClosure = $(mkClosure 'wait)
149simulateNetworkFailure :: TestTransport -> LocalNode -> LocalNode -> Process ()
150simulateNetworkFailure TestTransport{..} from to = liftIO $ do
152 _ <- forkProcess to $ getSelfPid >>= liftIO . putMVar m
154 them <- liftIO $ takeMVar m
155 pinger <- spawnLocal $ forever $ send them ()
156 _ <- monitorNode (localNodeId to)
157 liftIO $ testBreakConnection (nodeAddress $ localNodeId from)
158 (nodeAddress $ localNodeId to)
159 NodeMonitorNotification _ _ _ <- expect
160 kill pinger "finished"
163--------------------------------------------------------------------------------
164-- The tests proper --
165--------------------------------------------------------------------------------
167testUnclosure :: TestTransport -> RemoteTable -> Assertion
168testUnclosure TestTransport{..} rtable = do
169 node <- newLocalNode testTransport rtable
171 forkProcess node $ do
172 120 <- join . unClosure $ factorialClosure 5
173 liftIO $ putMVar done ()
176testBind :: TestTransport -> RemoteTable -> Assertion
177testBind TestTransport{..} rtable = do
178 node <- newLocalNode testTransport rtable
182 join . unClosure $ sendFac 6 us
183 (720 :: Int) <- expect
184 liftIO $ putMVar done ()
187testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
188testSendPureClosure TestTransport{..} rtable = do
189 serverAddr <- newEmptyMVar
190 serverDone <- newEmptyMVar
193 node <- newLocalNode testTransport rtable
194 addr <- forkProcess node $ do
196 fn <- unClosure cl :: Process (Int -> Int)
198 liftIO $ putMVar serverDone ()
199 putMVar serverAddr addr
202 node <- newLocalNode testTransport rtable
203 theirAddr <- readMVar serverAddr
204 runProcess node $ send theirAddr (addIntClosure 7)
208testSendIOClosure :: TestTransport -> RemoteTable -> Assertion
209testSendIOClosure TestTransport{..} rtable = do
210 serverAddr <- newEmptyMVar
211 serverDone <- newEmptyMVar
214 node <- newLocalNode testTransport rtable
215 addr <- forkProcess node $ do
217 io <- unClosure cl :: Process (MVar Int -> IO ())
219 someMVar <- newEmptyMVar
221 5 <- readMVar someMVar
222 putMVar serverDone ()
223 putMVar serverAddr addr
226 node <- newLocalNode testTransport rtable
227 theirAddr <- readMVar serverAddr
228 runProcess node $ send theirAddr (putIntClosure 5)
232testSendProcClosure :: TestTransport -> RemoteTable -> Assertion
233testSendProcClosure TestTransport{..} rtable = do
234 serverAddr <- newEmptyMVar
235 clientDone <- newEmptyMVar
238 node <- newLocalNode testTransport rtable
239 addr <- forkProcess node $ do
241 pr <- unClosure cl :: Process (Int -> Process ())
243 putMVar serverAddr addr
246 node <- newLocalNode testTransport rtable
247 theirAddr <- readMVar serverAddr
250 send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
251 5 <- expect :: Process Int
252 liftIO $ putMVar clientDone ()
256testSpawn :: TestTransport -> RemoteTable -> Assertion
257testSpawn TestTransport{..} rtable = do
258 serverNodeAddr <- newEmptyMVar
259 clientDone <- newEmptyMVar
262 node <- newLocalNode testTransport rtable
263 putMVar serverNodeAddr (localNodeId node)
266 node <- newLocalNode testTransport rtable
267 nid <- readMVar serverNodeAddr
270 pid' <- spawn nid (sendPidClosure pid)
272 True <- return $ pid' == pid''
273 liftIO $ putMVar clientDone ()
277-- | Tests that spawn executes the supplied closure even if the caller dies
278-- immediately after calling spawn.
280-- This situation is of interest because the implementation of spawn has the
281-- remote peer monitor the caller. See DP-99.
283-- The condition is tested by using a transport which refuses to send to the
284-- remote peer the message that it is waiting to stop monitoring the caller,
287testSpawnRace :: TestTransport -> RemoteTable -> Assertion
288testSpawnRace TestTransport{..} rtable = do
289 node1 <- newLocalNode (wrapTransport testTransport) rtable
290 node2 <- newLocalNode testTransport rtable
292 runProcess node1 $ do
294 spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
295 pid' <- expect :: Process ProcessId
296 pid'' <- expect :: Process ProcessId
297 True <- return $ pid' == pid''
302 wrapTransport (NT.Transport ne ct) = NT.Transport (fmap (fmap wrapEP) ne) ct
304 wrapEP :: NT.EndPoint -> NT.EndPoint
306 e { NT.connect = \x y z -> do
307 healthy <- newIORef True
308 fmap (fmap $ wrapConnection healthy) $ NT.connect e x y z
311 wrapConnection :: IORef Bool -> NT.Connection -> NT.Connection
312 wrapConnection healthy (NT.Connection s closeC) =
313 flip NT.Connection closeC $ \msg -> do
314 when (msg == messageToPayload (createMessage ())) $ do
315 writeIORef healthy False
316 isHealthy <- readIORef healthy
317 if isHealthy then s msg
318 else return $ Left $ NT.TransportError NT.SendFailed ""
320testCall :: TestTransport -> RemoteTable -> Assertion
321testCall TestTransport{..} rtable = do
322 serverNodeAddr <- newEmptyMVar
323 clientDone <- newEmptyMVar
326 node <- newLocalNode testTransport rtable
327 putMVar serverNodeAddr (localNodeId node)
330 node <- newLocalNode testTransport rtable
331 nid <- readMVar serverNodeAddr
333 (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
334 liftIO $ putMVar clientDone ()
338testCallBind :: TestTransport -> RemoteTable -> Assertion
339testCallBind TestTransport{..} rtable = do
340 serverNodeAddr <- newEmptyMVar
341 clientDone <- newEmptyMVar
344 node <- newLocalNode testTransport rtable
345 putMVar serverNodeAddr (localNodeId node)
348 node <- newLocalNode testTransport rtable
349 nid <- readMVar serverNodeAddr
351 (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
352 liftIO $ putMVar clientDone ()
356testSeq :: TestTransport -> RemoteTable -> Assertion
357testSeq TestTransport{..} rtable = do
358 node <- newLocalNode testTransport rtable
362 join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
365 liftIO $ putMVar done ()
368-- Test 'spawnSupervised'
370-- Set up a supervisor, spawn a child, then have a third process monitor the
371-- child. The supervisor then throws an exception, the child dies because it
372-- was linked to the supervisor, and the third process notices that the child
374testSpawnSupervised :: TestTransport -> RemoteTable -> Assertion
375testSpawnSupervised TestTransport{..} rtable = do
376 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
377 [superPid, childPid] <- replicateM 2 $ newEmptyMVar
378 thirdProcessDone <- newEmptyMVar
379 linkUp <- newEmptyMVar
381 forkProcess node1 $ do
383 liftIO $ putMVar superPid us
384 (child, _ref) <- spawnSupervised (localNodeId node2)
385 (sendPidClosure us `seqCP` $(mkStaticClosure 'expectUnit))
386 _ <- expect :: Process ProcessId
388 liftIO $ do putMVar childPid child
389 -- Give the child a chance to link to us
391 throw supervisorDeath
393 forkProcess node2 $ do
394 [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
398 liftIO $ threadDelay 10000
399 mpinfo <- getProcessInfo child
401 Nothing -> waitForMOrL
403 unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
405 liftIO $ putMVar linkUp ()
406 -- because monitor message was sent before message to process
407 -- we hope that it will be processed before
408 ProcessMonitorNotification ref' pid' (DiedException e) <- expect
409 True <- return $ ref' == ref
411 && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
412 liftIO $ putMVar thirdProcessDone ()
414 takeMVar thirdProcessDone
416 supervisorDeath :: IOException
417 supervisorDeath = userError "Supervisor died"
419testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
420testSpawnInvalid TestTransport{..} rtable = do
421 node <- newLocalNode testTransport rtable
423 forkProcess node $ do
424 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
425 ProcessMonitorNotification ref' pid' _reason <- expect
426 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
427 True <- return $ ref' == ref && pid == pid'
428 liftIO $ putMVar done ()
431testClosureExpect :: TestTransport -> RemoteTable -> Assertion
432testClosureExpect TestTransport{..} rtable = do
433 node <- newLocalNode testTransport rtable
436 nodeId <- getSelfNode
438 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
439 send them (1234 :: Int)
440 (1234 :: Int) <- expect
441 liftIO $ putMVar done ()
444testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
445testSpawnChannel TestTransport{..} rtable = do
447 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
449 forkProcess node1 $ do
450 pingServer <- spawnChannel
451 (sdictSendPort sdictUnit)
453 ($(mkClosure 'typedPingServer) ())
454 (sendReply, receiveReply) <- newChan
455 sendChan pingServer sendReply
456 receiveChan receiveReply
457 liftIO $ putMVar done ()
461testTDict :: TestTransport -> RemoteTable -> Assertion
462testTDict TestTransport{..} rtable = do
464 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
465 forkProcess node1 $ do
466 True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
467 liftIO $ putMVar done ()
470testFib :: TestTransport -> RemoteTable -> Assertion
471testFib TestTransport{..} rtable = do
472 nodes <- replicateM 4 $ newLocalNode testTransport rtable
475 forkProcess (head nodes) $ do
476 (sport, rport) <- newChan
477 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
478 55 <- receiveChan rport :: Process Integer
479 liftIO $ putMVar done ()
483testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
484testSpawnReconnect testtrans@TestTransport{..} rtable = do
485 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
486 let nid1 = localNodeId node1
487 nid2 = localNodeId node2
489 iv <- newMVar (0 :: Int)
491 incr <- forkProcess node1 $ forever $ do
493 liftIO $ modifyMVar_ iv (return . (+ 1))
495 forkProcess node2 $ do
496 _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
497 simulateNetworkFailure testtrans node2 node1
498 _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
499 _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
501 liftIO $ threadDelay 100000
503 count <- liftIO $ takeMVar iv
504 True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
506 liftIO $ putMVar done ()
510-- | 'spawn' used to ave a race condition which would be triggered if the
511-- spawning process terminates immediately after spawning
512testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
513testSpawnTerminate TestTransport{..} rtable = do
514 slave <- newLocalNode testTransport rtable
515 master <- newLocalNode testTransport rtable
516 masterDone <- newEmptyMVar
518 runProcess master $ do
520 replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
521 replicateM_ 1000 $ (expect :: Process ())
522 liftIO $ putMVar masterDone ()
526tests :: TestTransport -> IO [Test]
528 let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
530 [ testCase "Unclosure" (testUnclosure testtrans rtable)
531 , testCase "Bind" (testBind testtrans rtable)
532 , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
533 , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
534 , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
535 , testCase "Spawn" (testSpawn testtrans rtable)
536 , testCase "SpawnRace" (testSpawnRace testtrans rtable)
537 , testCase "Call" (testCall testtrans rtable)
538 , testCase "CallBind" (testCallBind testtrans rtable)
539 , testCase "Seq" (testSeq testtrans rtable)
540 , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
541 , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
542 , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
543 , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
544 , testCase "TDict" (testTDict testtrans rtable)
545 , testCase "Fib" (testFib testtrans rtable)
546 , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
547 , testCase "SpawnReconnect" (testSpawnReconnect testtrans rtable)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list