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 [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
419 liftIO $ threadDelay 10000
420 mpinfo <- getProcessInfo child
422 Nothing -> waitForMOrL
424 unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
426 liftIO $ putMVar linkUp ()
427 -- because monitor message was sent before message to process
428 -- we hope that it will be processed before
431 (ProcessMonitorNotification ref' pid' (DiedException e)) ->
432 if (ref' == ref && pid' == child &&
433 e == show (ProcessLinkException super
434 (DiedException (show supervisorDeath))))
435 then liftIO $ putMVar thirdProcessDone ()
436 else error "Something went horribly wrong"
437 _ -> error "Something went horribly wrong"
438 takeMVar thirdProcessDone
440 supervisorDeath :: IOException
441 supervisorDeath = userError "Supervisor died"
443testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
444testSpawnInvalid TestTransport{..} rtable = do
445 node <- newLocalNode testTransport rtable
447 forkProcess node $ do
448 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
449 ProcessMonitorNotification ref' pid' _reason <- expect
450 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
451 res <- return $ ref' == ref && pid == pid'
453 then liftIO $ putMVar done ()
454 else error "Something went horribly wrong"
457testClosureExpect :: TestTransport -> RemoteTable -> Assertion
458testClosureExpect TestTransport{..} rtable = do
459 node <- newLocalNode testTransport rtable
462 nodeId <- getSelfNode
464 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
465 send them (1234 :: Int)
466 (res :: Int) <- expect
468 then liftIO $ putMVar done ()
469 else error "Something went horribly wrong"
472testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
473testSpawnChannel TestTransport{..} rtable = do
475 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
477 forkProcess node1 $ do
478 pingServer <- spawnChannel
479 (sdictSendPort sdictUnit)
481 ($(mkClosure 'typedPingServer) ())
482 (sendReply, receiveReply) <- newChan
483 sendChan pingServer sendReply
484 receiveChan receiveReply
485 liftIO $ putMVar done ()
489testTDict :: TestTransport -> RemoteTable -> Assertion
490testTDict TestTransport{..} rtable = do
492 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
493 forkProcess node1 $ do
494 res <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
496 then liftIO $ putMVar done ()
497 else error "Something went horribly wrong..."
500testFib :: TestTransport -> RemoteTable -> Assertion
501testFib TestTransport{..} rtable = do
502 nodes <- replicateM 4 $ newLocalNode testTransport rtable
505 forkProcess (head nodes) $ do
506 (sport, rport) <- newChan
507 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
508 55 <- receiveChan rport :: Process Integer
509 liftIO $ putMVar done ()
513testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
514testSpawnReconnect testtrans@TestTransport{..} rtable = do
515 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
516 let nid1 = localNodeId node1
517 nid2 = localNodeId node2
519 iv <- newMVar (0 :: Int)
521 incr <- forkProcess node1 $ forever $ do
523 liftIO $ modifyMVar_ iv (return . (+ 1))
525 forkProcess node2 $ do
526 _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
527 simulateNetworkFailure testtrans node2 node1
528 _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
529 _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
531 liftIO $ threadDelay 100000
533 count <- liftIO $ takeMVar iv
534 res <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
536 liftIO $ putMVar done ()
538 then error "Something went horribly wrong"
543-- | 'spawn' used to ave a race condition which would be triggered if the
544-- spawning process terminates immediately after spawning
545testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
546testSpawnTerminate TestTransport{..} rtable = do
547 slave <- newLocalNode testTransport rtable
548 master <- newLocalNode testTransport rtable
549 masterDone <- newEmptyMVar
551 runProcess master $ do
553 replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
554 replicateM_ 1000 $ (expect :: Process ())
555 liftIO $ putMVar masterDone ()
559tests :: TestTransport -> IO [Test]
561 let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
563 [ testCase "Unclosure" (testUnclosure testtrans rtable)
564 , testCase "Bind" (testBind testtrans rtable)
565 , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
566 , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
567 , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
568 , testCase "Spawn" (testSpawn testtrans rtable)
569 , testCase "SpawnRace" (testSpawnRace testtrans rtable)
570 , testCase "Call" (testCall testtrans rtable)
571 , testCase "CallBind" (testCallBind testtrans rtable)
572 , testCase "Seq" (testSeq testtrans rtable)
573 , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
574 , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
575 , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
576 , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
577 , testCase "TDict" (testTDict testtrans rtable)
578 , testCase "Fib" (testFib testtrans rtable)
579 , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
580 , 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