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 i <- join . unClosure $ factorialClosure 5
173 liftIO $ putMVar done ()
176 else error "Something went horribly wrong"
179testBind :: TestTransport -> RemoteTable -> Assertion
180testBind TestTransport{..} rtable = do
181 node <- newLocalNode testTransport rtable
185 join . unClosure $ sendFac 6 us
187 liftIO $ putMVar done ()
190 else error "Something went horribly wrong"
193testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
194testSendPureClosure TestTransport{..} rtable = do
195 serverAddr <- newEmptyMVar
196 serverDone <- newEmptyMVar
199 node <- newLocalNode testTransport rtable
200 addr <- forkProcess node $ do
202 fn <- unClosure cl :: Process (Int -> Int)
203 (_ :: Int) <- return $ fn 6
204 liftIO $ putMVar serverDone ()
205 putMVar serverAddr addr
208 node <- newLocalNode testTransport rtable
209 theirAddr <- readMVar serverAddr
210 runProcess node $ send theirAddr (addIntClosure 7)
214testSendIOClosure :: TestTransport -> RemoteTable -> Assertion
215testSendIOClosure TestTransport{..} rtable = do
216 serverAddr <- newEmptyMVar
217 serverDone <- newEmptyMVar
220 node <- newLocalNode testTransport rtable
221 addr <- forkProcess node $ do
223 io <- unClosure cl :: Process (MVar Int -> IO ())
225 someMVar <- newEmptyMVar
227 i <- readMVar someMVar
228 putMVar serverDone ()
231 else error "Something went horribly wrong"
232 putMVar serverAddr addr
235 node <- newLocalNode testTransport rtable
236 theirAddr <- readMVar serverAddr
237 runProcess node $ send theirAddr (putIntClosure 5)
241testSendProcClosure :: TestTransport -> RemoteTable -> Assertion
242testSendProcClosure TestTransport{..} rtable = do
243 serverAddr <- newEmptyMVar
244 clientDone <- newEmptyMVar
247 node <- newLocalNode testTransport rtable
248 addr <- forkProcess node $ do
250 pr <- unClosure cl :: Process (Int -> Process ())
252 putMVar serverAddr addr
255 node <- newLocalNode testTransport rtable
256 theirAddr <- readMVar serverAddr
259 send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
260 i <- expect :: Process Int
262 then liftIO $ putMVar clientDone ()
263 else error "Something went horribly wrong"
267testSpawn :: TestTransport -> RemoteTable -> Assertion
268testSpawn TestTransport{..} rtable = do
269 serverNodeAddr <- newEmptyMVar
270 clientDone <- newEmptyMVar
273 node <- newLocalNode testTransport rtable
274 putMVar serverNodeAddr (localNodeId node)
277 node <- newLocalNode testTransport rtable
278 nid <- readMVar serverNodeAddr
281 pid' <- spawn nid (sendPidClosure pid)
284 then liftIO $ putMVar clientDone ()
285 else error "Something went horribly wrong"
289-- | Tests that spawn executes the supplied closure even if the caller dies
290-- immediately after calling spawn.
292-- This situation is of interest because the implementation of spawn has the
293-- remote peer monitor the caller. See DP-99.
295-- The condition is tested by using a transport which refuses to send to the
296-- remote peer the message that it is waiting to stop monitoring the caller,
299testSpawnRace :: TestTransport -> RemoteTable -> Assertion
300testSpawnRace TestTransport{..} rtable = do
301 node1 <- newLocalNode (wrapTransport testTransport) rtable
302 node2 <- newLocalNode testTransport rtable
304 runProcess node1 $ do
306 spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
307 pid' <- expect :: Process ProcessId
308 pid'' <- expect :: Process ProcessId
311 else error "Something went horribly wrong"
315 wrapTransport (NT.Transport ne ct) = NT.Transport (fmap (fmap wrapEP) ne) ct
317 wrapEP :: NT.EndPoint -> NT.EndPoint
319 e { NT.connect = \x y z -> do
320 healthy <- newIORef True
321 fmap (fmap $ wrapConnection healthy e x) $ NT.connect e x y z
324 wrapConnection :: IORef Bool -> NT.EndPoint -> NT.EndPointAddress
325 -> NT.Connection -> NT.Connection
326 wrapConnection healthy e remoteAddr (NT.Connection s closeC) =
327 flip NT.Connection closeC $ \msg -> do
328 when (msg == messageToPayload (createMessage ())) $ do
329 writeIORef healthy False
330 testBreakConnection (NT.address e) remoteAddr
331 isHealthy <- readIORef healthy
332 if isHealthy then s msg
333 else return $ Left $ NT.TransportError NT.SendFailed ""
335testCall :: TestTransport -> RemoteTable -> Assertion
336testCall TestTransport{..} rtable = do
337 serverNodeAddr <- newEmptyMVar
338 clientDone <- newEmptyMVar
341 node <- newLocalNode testTransport rtable
342 putMVar serverNodeAddr (localNodeId node)
345 node <- newLocalNode testTransport rtable
346 nid <- readMVar serverNodeAddr
348 (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
350 then liftIO $ putMVar clientDone ()
351 else error "something went horribly wrong"
355testCallBind :: TestTransport -> RemoteTable -> Assertion
356testCallBind TestTransport{..} rtable = do
357 serverNodeAddr <- newEmptyMVar
358 clientDone <- newEmptyMVar
361 node <- newLocalNode testTransport rtable
362 putMVar serverNodeAddr (localNodeId node)
365 node <- newLocalNode testTransport rtable
366 nid <- readMVar serverNodeAddr
368 (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
370 then liftIO $ putMVar clientDone ()
371 else error "Something went horribly wrong"
375testSeq :: TestTransport -> RemoteTable -> Assertion
376testSeq TestTransport{..} rtable = do
377 node <- newLocalNode testTransport rtable
381 join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
384 if a == 120 && b == 720
385 then liftIO $ putMVar done ()
386 else error "Something went horribly wrong"
389-- Test 'spawnSupervised'
391-- Set up a supervisor, spawn a child, then have a third process monitor the
392-- child. The supervisor then throws an exception, the child dies because it
393-- was linked to the supervisor, and the third process notices that the child
395testSpawnSupervised :: TestTransport -> RemoteTable -> Assertion
396testSpawnSupervised TestTransport{..} rtable = do
397 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
398 [superPid, childPid] <- replicateM 2 $ newEmptyMVar
399 thirdProcessDone <- newEmptyMVar
400 linkUp <- newEmptyMVar
402 forkProcess node1 $ do
404 liftIO $ putMVar superPid us
405 (child, _ref) <- spawnSupervised (localNodeId node2)
406 (sendPidClosure us `seqCP` $(mkStaticClosure 'expectUnit))
407 _ <- expect :: Process ProcessId
409 liftIO $ do putMVar childPid child
410 -- Give the child a chance to link to us
412 throw supervisorDeath
414 forkProcess node2 $ do
415 res <- liftIO $ mapM readMVar [superPid, childPid]
421 liftIO $ threadDelay 10000
422 mpinfo <- getProcessInfo child
424 Nothing -> waitForMOrL
426 unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
428 liftIO $ putMVar linkUp ()
429 -- because monitor message was sent before message to process
430 -- we hope that it will be processed before
433 (ProcessMonitorNotification ref' pid' (DiedException e)) ->
434 if (ref' == ref && pid' == child &&
435 e == show (ProcessLinkException super
436 (DiedException (show supervisorDeath))))
437 then liftIO $ putMVar thirdProcessDone ()
438 else error "Something went horribly wrong"
439 _ -> error "Something went horribly wrong"
441 _ -> die $ "Something went horribly wrong"
443 takeMVar thirdProcessDone
445 supervisorDeath :: IOException
446 supervisorDeath = userError "Supervisor died"
448testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
449testSpawnInvalid TestTransport{..} rtable = do
450 node <- newLocalNode testTransport rtable
452 forkProcess node $ do
453 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
454 ProcessMonitorNotification ref' pid' _reason <- expect
455 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
456 res <- return $ ref' == ref && pid == pid'
458 then liftIO $ putMVar done ()
459 else error "Something went horribly wrong"
462testClosureExpect :: TestTransport -> RemoteTable -> Assertion
463testClosureExpect TestTransport{..} rtable = do
464 node <- newLocalNode testTransport rtable
467 nodeId <- getSelfNode
469 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
470 send them (1234 :: Int)
471 (res :: Int) <- expect
473 then liftIO $ putMVar done ()
474 else error "Something went horribly wrong"
477testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
478testSpawnChannel TestTransport{..} rtable = do
480 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
482 forkProcess node1 $ do
483 pingServer <- spawnChannel
484 (sdictSendPort sdictUnit)
486 ($(mkClosure 'typedPingServer) ())
487 (sendReply, receiveReply) <- newChan
488 sendChan pingServer sendReply
489 receiveChan receiveReply
490 liftIO $ putMVar done ()
494testTDict :: TestTransport -> RemoteTable -> Assertion
495testTDict TestTransport{..} rtable = do
497 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
498 forkProcess node1 $ do
499 res <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
501 then liftIO $ putMVar done ()
502 else error "Something went horribly wrong..."
505testFib :: TestTransport -> RemoteTable -> Assertion
506testFib TestTransport{..} rtable = do
507 nodes <- replicateM 4 $ newLocalNode testTransport rtable
510 forkProcess (head nodes) $ do
511 (sport, rport) <- newChan
512 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
513 ff <- receiveChan rport :: Process Integer
514 liftIO $ putMVar done ()
516 then die $ "Something went horribly wrong"
521testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
522testSpawnReconnect testtrans@TestTransport{..} rtable = do
523 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
524 let nid1 = localNodeId node1
525 -- nid2 = localNodeId node2
527 iv <- newMVar (0 :: Int)
529 incr <- forkProcess node1 $ forever $ do
531 liftIO $ modifyMVar_ iv (return . (+ 1))
533 forkProcess node2 $ do
534 _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
535 simulateNetworkFailure testtrans node2 node1
536 _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
537 _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
539 liftIO $ threadDelay 100000
541 count <- liftIO $ takeMVar iv
542 res <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
544 liftIO $ putMVar done ()
546 then error "Something went horribly wrong"
551-- | 'spawn' used to ave a race condition which would be triggered if the
552-- spawning process terminates immediately after spawning
553testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
554testSpawnTerminate TestTransport{..} rtable = do
555 slave <- newLocalNode testTransport rtable
556 master <- newLocalNode testTransport rtable
557 masterDone <- newEmptyMVar
559 runProcess master $ do
561 replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
562 replicateM_ 1000 $ (expect :: Process ())
563 liftIO $ putMVar masterDone ()
567tests :: TestTransport -> IO [Test]
569 let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
571 [ testCase "Unclosure" (testUnclosure testtrans rtable)
572 , testCase "Bind" (testBind testtrans rtable)
573 , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
574 , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
575 , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
576 , testCase "Spawn" (testSpawn testtrans rtable)
577 , testCase "SpawnRace" (testSpawnRace testtrans rtable)
578 , testCase "Call" (testCall testtrans rtable)
579 , testCase "CallBind" (testCallBind testtrans rtable)
580 , testCase "Seq" (testSeq testtrans rtable)
581 , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
582 , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
583 , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
584 , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
585 , testCase "TDict" (testTDict testtrans rtable)
586 , testCase "Fib" (testFib testtrans rtable)
587 , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
588 , 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