@@ -25,13 +25,18 @@ import Control.Concurrent.MVar
26import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
26import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
27import Control.Exception (SomeException, throwIO, ErrorCall(..))
27import Control.Exception (SomeException, throwIO, ErrorCall(..))
28−import qualified Control.Exception as Ex (catch)
28+import Control.Monad.Catch (try, catch, finally, mask, onException)
29−import Control.Applicative ((<$>), (<*>), pure, (<|>))
29+import Control.Applicative ((<|>))
30import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
30import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
31−import Control.Distributed.Process
31+import Control.Distributed.Process hiding
32import Control.Distributed.Process.Internal.Types
38import Control.Distributed.Process.Internal.Types
33− ( NodeId(nodeAddress)
34− , LocalNode(localEndPoint)
39+ ( LocalNode(localEndPoint)
35 , ProcessExitException(..)
40 , ProcessExitException(..)
36 , nullProcessId
41 , nullProcessId
37 , createUnencodedMessage
42 , createUnencodedMessage
@@ -39,13 +44,13 @@ import Control.Distributed.Process.Internal.Types
39import Control.Distributed.Process.Node
44import Control.Distributed.Process.Node
40import Control.Distributed.Process.Debug
45import Control.Distributed.Process.Debug
41import Control.Distributed.Process.Management.Internal.Types
46import Control.Distributed.Process.Management.Internal.Types
42−import Control.Distributed.Process.Tests.Internal.Utils (shouldBe)
47+import Control.Distributed.Process.Tests.Internal.Utils (shouldBe, pause)
43import Control.Distributed.Process.Serializable (Serializable)
48import Control.Distributed.Process.Serializable (Serializable)
44−
49+import Data.Maybe (isNothing, isJust)
45import Test.HUnit (Assertion, assertBool, assertFailure)
50import Test.HUnit (Assertion, assertBool, assertFailure)
46import Test.Framework (Test, testGroup)
51import Test.Framework (Test, testGroup)
47import Test.Framework.Providers.HUnit (testCase)
52import Test.Framework.Providers.HUnit (testCase)
48−import Control.Rematch hiding (match)
53+import Control.Rematch hiding (match, isNothing, isJust)
49import Control.Rematch.Run (Match(..))
54import Control.Rematch.Run (Match(..))
51newtype Ping = Ping ProcessId
56newtype Ping = Ping ProcessId
@@ -68,7 +73,7 @@ expectThat a matcher = case res of
68forkTry :: IO () -> IO ThreadId
73forkTry :: IO () -> IO ThreadId
69forkTry p = do
74forkTry p = do
70 tid <- myThreadId
75 tid <- myThreadId
71− forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
76+ forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
73-- | The ping server from the paper
78-- | The ping server from the paper
74ping :: Process ()
79ping :: Process ()
@@ -82,10 +87,9 @@ verifyClient :: String -> MVar Bool -> IO ()
82verifyClient s b = takeMVar b >>= assertBool s
87verifyClient s b = takeMVar b >>= assertBool s
84expectPing :: MVar Bool -> Process ()
89expectPing :: MVar Bool -> Process ()
85−expectPing mv = expect >>= liftIO . putMVar mv . checkPing
90+expectPing mv = expect >>= liftIO . putMVar mv . checkPing
87 checkPing (Ping _) = True
92 checkPing (Ping _) = True
90-- | Quick and dirty synchronous version of whereisRemoteAsync
94-- | Quick and dirty synchronous version of whereisRemoteAsync
91whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
95whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
@@ -235,10 +239,10 @@ testPing TestTransport{..} = do
235 pid <- getSelfPid
239 pid <- getSelfPid
236 replicateM_ numPings $ do
240 replicateM_ numPings $ do
237 send pingServer (Pong pid)
241 send pingServer (Pong pid)
238− p <- expect
242+ p <- expectTimeout 3000000
239 case p of
243 case p of
240− Ping _ -> return ()
244+ Just (Ping _) -> return ()
241− _ -> die "Unexpected message"
245+ Nothing -> die "Failed to receive Ping"
243 putMVar clientDone ()
247 putMVar clientDone ()
@@ -555,7 +559,7 @@ testMergeChannels TestTransport{..} = do
555 ss <- liftIO $ mapM readMVar vs
559 ss <- liftIO $ mapM readMVar vs
556 case ss of
560 case ss of
557 [sa, sb, sc] ->
561 [sa, sb, sc] ->
558− mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
562+ mapM_ ((>> pause 10000) . uncurry sendChan)
559 [ -- a, b, c
563 [ -- a, b, c
560 (sa, 'a')
564 (sa, 'a')
561 , (sb, 'b')
565 , (sb, 'b')
@@ -731,7 +735,6 @@ testRemoteRegistry :: TestTransport -> Assertion
731testRemoteRegistry TestTransport{..} = do
735testRemoteRegistry TestTransport{..} = do
732 node1 <- newLocalNode testTransport initRemoteTable
736 node1 <- newLocalNode testTransport initRemoteTable
733 node2 <- newLocalNode testTransport initRemoteTable
737 node2 <- newLocalNode testTransport initRemoteTable
734− done <- newEmptyMVar
736 pingServer <- forkProcess node1 ping
739 pingServer <- forkProcess node1 ping
737 deadProcess <- forkProcess node1 (return ())
740 deadProcess <- forkProcess node1 (return ())
@@ -746,6 +749,7 @@ testRemoteRegistry TestTransport{..} = do
747 pid <- verifyWhereIsRemote nid1 "ping"
750 pid <- verifyWhereIsRemote nid1 "ping"
748 liftIO $ assertBool "Expected pindServer to match pid" $ pingServer == pid
751 liftIO $ assertBool "Expected pindServer to match pid" $ pingServer == pid
749 us <- getSelfPid
753 us <- getSelfPid
750 nsendRemote nid1 "ping" (Pong us)
754 nsendRemote nid1 "ping" (Pong us)
751 receiveWait [
755 receiveWait [
@@ -755,22 +759,22 @@ testRemoteRegistry TestTransport{..} = do
755 -- test that if process was not registered Nothing is returned
759 -- test that if process was not registered Nothing is returned
756 -- in owner field.
760 -- in owner field.
757 registerRemoteAsync nid1 "dead" deadProcess
761 registerRemoteAsync nid1 "dead" deadProcess
759− matchIf (\(RegisterReply label' False Nothing) -> "dead" == label')
762+ receiveWait [ matchIf (\(RegisterReply label' f mPid) -> "dead" == label')
760− (\(RegisterReply _ _ _) -> return ()) ]
763+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
764+ ] >>= liftIO . assertBool "Expected False Nothing in RegisterReply"
761 registerRemoteAsync nid1 "ping" deadProcess
766 registerRemoteAsync nid1 "ping" deadProcess
762 receiveWait [
767 receiveWait [
763− matchIf (\(RegisterReply label' False (Just pid)) ->
768+ matchIf (\(RegisterReply label' False mPid) ->
764− "ping" == label' && pid == pingServer)
769+ "ping" == label' && isJust mPid)
765− (\(RegisterReply _ _ _) -> return ()) ]
770+ (\(RegisterReply _ f (Just pid'')) -> return (not f && pid'' == pingServer))
771+ ] >>= liftIO . assertBool "Expected False and (Just alreadyRegisteredPid) in RegisterReply"
766 unregisterRemoteAsync nid1 "dead"
773 unregisterRemoteAsync nid1 "dead"
767 receiveWait [
774 receiveWait [
768− matchIf (\(RegisterReply label' False Nothing) ->
775+ matchIf (\(RegisterReply label' _ _) -> "dead" == label')
769− "dead" == label' && pid == pingServer)
776+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
770− (\(RegisterReply _ _ _) -> return ()) ]
777+ ] >>= liftIO . assertBool "Expected False and Nothing in RegisterReply"
771− liftIO $ putMVar done ()
775testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
779testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
776testRemoteRegistryRemoteProcess TestTransport{..} = do
780testRemoteRegistryRemoteProcess TestTransport{..} = do
@@ -942,9 +946,7 @@ testUSend usendPrim TestTransport{..} numMessages = do
942 isSorted _ = True
946 isSorted _ = True
943 -- The list can't be null since there are no failures after sending
947 -- The list can't be null since there are no failures after sending
944 -- the last message.
948 -- the last message.
945− liftIO $ do
949+ liftIO $ putMVar usendTestOk $ isSorted msgs && not (null msgs)
946− assertBool "Unexpected failure after sending last msg" $ isSorted msgs && not (null msgs)
947− putMVar usendTestOk ()
949 forkProcess node2 $ do
951 forkProcess node2 $ do
950 them <- liftIO $ readMVar processA
952 them <- liftIO $ readMVar processA
@@ -955,7 +957,8 @@ testUSend usendPrim TestTransport{..} numMessages = do
955 usendPrim them i
957 usendPrim them i
956 liftIO (threadDelay 30000)
958 liftIO (threadDelay 30000)
958− takeMVar usendTestOk
960+ res <- takeMVar usendTestOk
961+ assertBool "Unexpected failure after sending last msg" res
960-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
963-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
961-- in between
964-- in between
@@ -1269,6 +1272,76 @@ testReceiveChanFeatures TestTransport{..} = do
1270 takeMVar done
1273 takeMVar done
1275+testChanLifecycle :: TestTransport -> Assertion
1276+testChanLifecycle TestTransport{..} = let delay = 3000000 in do
1277+ result <- newEmptyMVar
1278+ tchMV <- newEmptyMVar
1279+ localNode <- newLocalNode testTransport initRemoteTable
1280+ runProcess localNode $ do
1282+ pid <- spawnLocal $ do tCh <- newChan :: Process (SendPort (), ReceivePort ())
1283+ liftIO $ putMVar tchMV tCh
1284+ expect :: Process ()
1287+ expect :: Process ()
1289+ mRefPid <- monitor pid
1291+ cPid <- spawnLocal $ do
1292+ (sp', rp) <- liftIO $ takeMVar tchMV
1293+ -- say "picked up our test channel"
1295+ -- say "told pid to continue"
1296+ res <- receiveChanTimeout delay rp
1298+ Nothing -> say "initial chan () missing!" >> (liftIO $ putMVar result False)
1299+ Just () -> do mr <- monitor pid
1301+ -- say "sending pid a second () will cause it to exit"
1304+ -- say "make sure we see a DOWN notification for pid having stopped"
1305+ receiveWait [ match (\(_ :: ProcessMonitorNotification) -> return ()) ]
1307+ -- now that pid has died, the send port should be useless...
1308+ liftIO $ putMVar tchMV (sp', rp)
1310+ -- let's verify that we do not see the message from our
1311+ -- parent process on the channel, once pid has died...
1312+ recv <- receiveChanTimeout delay rp
1313+ -- say $ "finished waiting for second (), writing result" ++ (show recv)
1314+ liftIO $ putMVar result $ isNothing recv
1316+ mRefCPid <- monitor cPid
1319+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefPid)
1323+ -- say "seen first pid die..."
1325+ (sendPort, _) <- liftIO $ takeMVar tchMV
1326+ sendChan sendPort ()
1327+ -- say "sent () after owning pid died"
1329+ -- let cPid know we've written to the channel...
1333+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefCPid)
1337+ -- say "seen both pids die now..."
1339+ -- and wait on the result back in IO land...
1340+ testRes <- takeMVar result
1341+ -- runProcess localNode $ say "got result..."
1342+ assertBool "Expected sending on the channel to fail, but received data!" testRes
1272testKillLocal :: TestTransport -> Assertion
1345testKillLocal :: TestTransport -> Assertion
1273testKillLocal TestTransport{..} = do
1346testKillLocal TestTransport{..} = do
1274 localNode <- newLocalNode testTransport initRemoteTable
1347 localNode <- newLocalNode testTransport initRemoteTable
@@ -1481,113 +1554,73 @@ testExitRemote TestTransport{..} = do
1482testRegistryMonitoring :: TestTransport -> Assertion
1555testRegistryMonitoring :: TestTransport -> Assertion
1483testRegistryMonitoring TestTransport{..} = do
1556testRegistryMonitoring TestTransport{..} = do
1484− localNode <- newLocalNode testTransport initRemoteTable
1557+ node1 <- newLocalNode testTransport initRemoteTable
1485− remoteNode <- newLocalNode testTransport initRemoteTable
1558+ node2 <- newLocalNode testTransport initRemoteTable
1488− -- Local process. Test if local process will be removed from
1560+ let nid = localNodeId node2
1489− -- registry when it dies.
1561+ pid <- forkProcess node1 $ do
1490− box <- newEmptyMVar
1562+ self <- getSelfPid
1491− runProcess localNode $ do
1563+ runUntilRegistered nid self
1492− pid <- spawnLocal $ do
1564+ say $ (show self) ++ " registered as " ++ regName
1493− expect
1565+ expect :: Process ()
1494− register "test" pid
1566+ say $ (show self) ++ " exiting normally"
1495− tpid <- whereis "test"
1496− if tpid == Just pid
1497− then do _ <- monitor pid
1499− ProcessMonitorNotification{} <- expect
1500− tpid1 <- whereis "test"
1501− liftIO $ putMVar box (Nothing == tpid1)
1502− else liftIO $ putMVar box False
1504− takeMVar box >>= assertBool "expected local process to not be registered"
1507− -- Remote process. Test if remote process entry is removed
1508− -- from registry when process dies.
1509− remote1 <- testRemote remoteNode
1510− runProcess localNode $
1512− w <- whereis "test" :: Process (Maybe ProcessId)
1513− forM_ w (const waitpoll)
1514− in do register "test" remote1
1515− -- [race-condition] invalid test if we force exit before waitpoll runs
1516− registered <- whereis "test"
1517− registered `shouldBe` (equalTo (Just remote1))
1523− -- Many labels. Test if all labels associated with process
1524− -- are removed from registry when it dies.
1525− remote2 <- testRemote remoteNode
1526− runProcess localNode $
1528− w1 <- whereis "test-3" :: Process (Maybe ProcessId)
1529− w2 <- whereis "test-4" :: Process (Maybe ProcessId)
1530− forM_ (w1 <|> w2) (const waitpoll)
1531− in do register "test-3" remote2
1532− register "test-4" remote2
1537−{- XXX: waiting including patch for nsend for remote process
1538− remote3 <- testRemote remoteNode
1539− remote4 <- testRemote remoteNode
1540− -- test many labels
1541− runProcess localNode $ do
1542− register "test-3" remote3
1543− reregister "test-3" remote4
1545− liftIO $ threadDelay 50000 -- XXX: racy
1548− ProcessMonitorNotification{} <- expect
1552− -- Test registerRemoteAsync properties. Add a local process to
1553− -- remote registry and checks that it is removed
1554− -- when the process dies.
1555− remote5 <- testRemote remoteNode
1556− runProcess localNode $ do
1557− registerRemoteAsync (localNodeId remoteNode) "test" remote5
1559− match (\(RegisterReply _ True _) -> return ())
1562− whereisRemoteAsync (localNodeId remoteNode) "test"
1564− match (\(WhereIsReply _ mr) -> forM_ mr (const waitpoll))
1568− -- Add remote process to remote registry and checks if
1568+ runProcess node2 $ do
1569− -- entry is removed then process is dead.
1569+ register regName pid
1570− remote6 <- testRemote localNode
1570+ say $ regName ++ " registered to " ++ show pid
1571− runProcess localNode $ do
1571+ res <- whereis regName
1572− registerRemoteAsync (localNodeId remoteNode) "test" remote6
1572+ send pid ()
1573− receiveWait [
1573+ say $ " sent finish signal to " ++ show pid
1574− match (\(RegisterReply _ True _) -> return ())
1574+ us <- getSelfPid
1575− ] >>= send remote6
1575+ liftIO $ assertBool "expected (Just pid)" $ res == (Just pid)
1576− let waitpoll = do
1576+
1577− whereisRemoteAsync (localNodeId remoteNode) "test"
1577+
1578− receiveWait [
1578+ -- This delay isn't essential!
1579− match (\(WhereIsReply _ mr) -> forM_ mr (const waitpoll))
1579+ -- The test case passes perfectly fine without it (feel free to comment out
1580− ]
1580+ -- and see), however waiting a few seconds here, makes it much more likely
1581− waitpoll
1581+ -- that in delayUntilMaybeUnregistered we will hit the match case right
1582− where
1582+ -- away, and thus not be subjected to a 20 second delay. The value of 4
1583− testRemote node = do
1583+ -- seconds appears to work optimally on osx and across several linux distros
1584− -- test many labels
1584+ -- running in virtual machines (which is essentially what we do in CI)
1585− pidBox <- newEmptyMVar
1585+ void $ receiveTimeout 4000000 [ matchAny return ]
1586− forkProcess node $ do
1586+
1587− us <- getSelfPid
1587+ -- This delay doesn't serve much purpose in the happy path, however if some
1588− liftIO $ putMVar pidBox us
1588+ -- future patch breaks the cooperative behaviour of node controllers viz
1589− expect :: Process ()
1589+ -- remote process registration and notification taking place via ncEffectDied,
1590+ -- there would be the possibility of a race in the test case should we attempt
1591+ -- to evaluate `whereis regName` on node2 right away. In case the name is still
1592+ -- erroneously registered, observing the 20 second delay (or lack of), could at
1593+ -- least give a hint that something is wrong, and we give up our time slice
1594+ -- so that there's a higher change the registrations have been cleaned up
1596+ runProcess node2 $ delayUntilMaybeUnregistered nid pid
1598+ regHere <- newEmptyMVar
1599+ runProcess node2 $ whereis regName >>= liftIO . putMVar regHere
1590− takeMVar pidBox
1600+ res <- takeMVar regHere
1602+ Nothing -> return ()
1603+ Just pid -> assertBool ("expected Nothing, but got " ++ show pid) False
1606+ runUntilRegistered nid us = do
1607+ whereisRemoteAsync nid regName
1609+ matchIf (\(WhereIsReply n (Just p)) -> n == regName && p == us)
1610+ (const $ return ())
1613+ delayUntilMaybeUnregistered nid p = do
1614+ whereisRemoteAsync nid regName
1615+ res <- receiveTimeout 20000000 {- 20 sec delay -} [
1616+ matchIf (\(WhereIsReply n p) -> n == regName && isNothing p)
1617+ (const $ return ())
1620+ Just () -> return ()
1621+ Nothing -> delayUntilMaybeUnregistered nid p
1623+ regName = "testRegisterRemote"
1592testUnsafeSend :: TestTransport -> Assertion
1625testUnsafeSend :: TestTransport -> Assertion
1593testUnsafeSend TestTransport{..} = do
1626testUnsafeSend TestTransport{..} = do
@@ -1781,6 +1814,7 @@ tests testtrans = return [
1781 , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1814 , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1782 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1815 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1783 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1816 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1817+ , testCase "ChanLifecycle" (testChanLifecycle testtrans)
1784 , testCase "KillLocal" (testKillLocal testtrans)
1818 , testCase "KillLocal" (testKillLocal testtrans)
1785 , testCase "KillRemote" (testKillRemote testtrans)
1819 , testCase "KillRemote" (testKillRemote testtrans)
1786 , testCase "Die" (testDie testtrans)
1820 , testCase "Die" (testDie testtrans)
@@ -1790,7 +1824,7 @@ tests testtrans = return [
1790 , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1824 , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1791 , testCase "ExitLocal" (testExitLocal testtrans)
1825 , testCase "ExitLocal" (testExitLocal testtrans)
1792 , testCase "ExitRemote" (testExitRemote testtrans)
1826 , testCase "ExitRemote" (testExitRemote testtrans)
1793− , testCase "TestRegistryMonitor" (testRegistryMonitoring testtrans)
1827+ , testCase "RegistryMonitoring" (testRegistryMonitoring testtrans)
1794 , testCase "TextCallLocal" (testCallLocal testtrans)
1828 , testCase "TextCallLocal" (testCallLocal testtrans)
1795 -- Unsafe Primitives
1829 -- Unsafe Primitives
1796 , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1830 , testCase "TestUnsafeSend" (testUnsafeSend testtrans)