git.iko.soy/distributed-process
on master help sign in
Implement receiveWait and receiveTimeout
commitdda7fdade5c1096f212f02dceae45c2ff16127f6 authorEdsko de Vries <edsko@edsko.net> dateMon 18 Jun 2012, 18:35 (14 years ago) parent440f2bd
4 files changed+154−160
distributed-process/distributed-process.cabal+3−3 distributed-process/src/Control/Distributed/Process.hs+53−17 distributed-process/src/Control/Distributed/Process/Internal/CQueue.hs+34−25 distributed-process/tests/TestCH.hs+64−115
distributed-process/distributed-process.cabal+3−3
@@ -23,7 +23,7 @@ Library
2323 Exposed-modules: Control.Distributed.Process,
2424 Control.Distributed.Process.Serializable,
2525 Control.Distributed.Process.Closure
26− Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell
26+ Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell, ExistentialQuantification
2727 ghc-options: -Wall
2828 HS-Source-Dirs: src
2929
@@ -41,7 +41,7 @@ Test-Suite TestCH
4141 ansi-terminal,
4242 template-haskell,
4343 network-transport >= 0.1
44− Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell
44+ Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell, ExistentialQuantification
4545 ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
4646 HS-Source-Dirs: tests src
4747
@@ -61,6 +61,6 @@ Test-Suite TestClosure
6161 ansi-terminal,
6262 template-haskell,
6363 network-transport >= 0.1
64− Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell
64+ Extensions: ExplicitForAll, ScopedTypeVariables, GeneralizedNewtypeDeriving, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, DeriveDataTypeable, TemplateHaskell, ExistentialQuantification
6565 ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
6666 HS-Source-Dirs: tests src
distributed-process/src/Control/Distributed/Process.hs+53−17
@@ -65,6 +65,12 @@ module Control.Distributed.Process
6565 , getSelfPid
6666 , RemoteCallMetaData
6767 , initRemoteCallMetaData
68+ -- * Matching messages
69+ , Match
70+ , match
71+ , matchIf
72+ , receiveWait
73+ , receiveTimeout
6874 -- * Monitoring and linking
6975 , link
7076 , unlink
@@ -115,11 +121,11 @@ import Control.Concurrent.MVar ( newMVar
115121 , takeMVar
116122 )
117123import Control.Concurrent.Chan (newChan, writeChan)
118−import Control.Distributed.Process.Internal.CQueue ( dequeueMatching
119− , enqueue
120− , newCQueue
121− )
122−import Control.Distributed.Process.Serializable (Serializable, fingerprint)
124+import Control.Distributed.Process.Internal.CQueue (enqueue, dequeue, newCQueue)
125+import Control.Distributed.Process.Serializable ( Serializable
126+ , Fingerprint
127+ , fingerprint
128+ )
123129import qualified Network.Transport as NT ( Transport
124130 , EndPoint
125131 , Connection
@@ -180,12 +186,7 @@ newtype Process a = Process { unProcess :: ReaderT LocalProcess IO a }
180186
181187-- | Wait for a message of a specific type
182188expect :: forall a. Serializable a => Process a
183−expect = do
184− queue <- processQueue <$> ask
185− let fp = fingerprint (undefined :: a)
186− Just msg <- liftIO $
187− dequeueMatching queue Nothing ((== fp) . messageFingerprint)
188− return (decode . messageEncoding $ msg)
189+expect = receiveWait [match return]
189190
190191-- | Send a message
191192send :: Serializable a => ProcessId -> a -> Process ()
@@ -218,6 +219,46 @@ unmonitor = postCtrlMsg . Unmonitor
218219unlink :: ProcessId -> Process ()
219220unlink = postCtrlMsg . Unlink
220221
222+--------------------------------------------------------------------------------
223+-- Matching --
224+--------------------------------------------------------------------------------
225+
226+data Match b =
227+ forall a. Serializable a => Match Fingerprint (a -> Process b)
228+ | forall a. Serializable a => MatchIf Fingerprint (a -> Bool) (a -> Process b)
229+
230+match :: forall a b. Serializable a => (a -> Process b) -> Match b
231+match = Match (fingerprint (undefined :: a))
232+
233+matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
234+matchIf = MatchIf (fingerprint (undefined :: a))
235+
236+receiveWait :: [Match b] -> Process b
237+receiveWait ms = do
238+ queue <- processQueue <$> ask
239+ Just proc <- liftIO $ dequeue queue Nothing (map evalMatch ms)
240+ proc
241+
242+receiveTimeout :: Int -> [Match b] -> Process (Maybe b)
243+receiveTimeout t ms = do
244+ queue <- processQueue <$> ask
245+ mProc <- liftIO $ dequeue queue (Just t) (map evalMatch ms)
246+ case mProc of
247+ Nothing -> return Nothing
248+ Just proc -> Just <$> proc
249+
250+evalMatch :: Match b -> Message -> Maybe (Process b)
251+evalMatch (Match fp proc) msg =
252+ let decoded = decode . messageEncoding $ msg in
253+ if messageFingerprint msg == fp
254+ then Just . proc $ decoded
255+ else Nothing
256+evalMatch (MatchIf fp cond proc) msg =
257+ let decoded = decode . messageEncoding $ msg in
258+ if messageFingerprint msg == fp && cond decoded
259+ then Just . proc $ decoded
260+ else Nothing
261+
221262--------------------------------------------------------------------------------
222263-- Auxiliary API --
223264--------------------------------------------------------------------------------
@@ -236,12 +277,7 @@ catch p h = do
236277 liftIO $ Exception.catch (run p) (run . h)
237278
238279expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)
239−expectTimeout timeout = do
240− queue <- processQueue <$> ask
241− let fp = fingerprint (undefined :: a)
242− msg <- liftIO $
243− dequeueMatching queue (Just timeout) ((== fp) . messageFingerprint)
244− return $ fmap (decode . messageEncoding) msg
280+expectTimeout timeout = receiveTimeout timeout [match return]
245281
246282--------------------------------------------------------------------------------
247283-- Initialization --
distributed-process/src/Control/Distributed/Process/Internal/CQueue.hs+34−25
@@ -3,7 +3,7 @@ module Control.Distributed.Process.Internal.CQueue
33 ( CQueue
44 , newCQueue
55 , enqueue
6− , dequeueMatching
6+ , dequeue
77 ) where
88
99import Control.Concurrent.MVar (MVar, newMVar, modifyMVar)
@@ -23,29 +23,38 @@ newCQueue = do
2323enqueue :: CQueue a -> a -> IO ()
2424enqueue (CQueue _arrived incoming) = writeChan incoming
2525
26−-- TODO: subtract the timeout value if a non-matching messages arrives
27−dequeueMatching :: forall a.
28− CQueue a
29− -> Maybe Int
30− -> (a -> Bool)
31− -> IO (Maybe a)
32−dequeueMatching (CQueue arrived incoming) mTimeout matches =
26+dequeue :: forall a b.
27+ CQueue a -- ^ Queue
28+ -> Maybe Int -- ^ Timeout
29+ -> [a -> Maybe b] -- ^ List of matches
30+ -> IO (Maybe b) -- ^ Nothing only on timeout
31+dequeue (CQueue arrived incoming) mTimeout matches =
3332 modifyMVar arrived (checkArrived [])
3433 where
35− checkArrived :: [a] -> [a] -> IO ([a], Maybe a)
36− checkArrived xs' [] = checkIncoming xs'
37− checkArrived xs' (x:xs)
38− | matches x = return (reverse xs' ++ xs, Just x)
39− | otherwise = checkArrived (x:xs') xs
40−
41− checkIncoming :: [a] -> IO ([a], Maybe a)
42− checkIncoming xs' = do
43− mx <- case mTimeout of
44− Just 0 -> return Nothing
45− Just n -> timeout n $ readChan incoming
46− Nothing -> Just <$> readChan incoming
47− case mx of
48− Nothing -> return (reverse xs', Nothing)
49− Just x
50− | matches x -> return (reverse xs', Just x)
51− | otherwise -> checkIncoming (x:xs')
34+ checkArrived :: [a] -> [a] -> IO ([a], Maybe b)
35+ checkArrived acc (x:xs) =
36+ case check x of
37+ Just y -> return (reverse acc ++ xs, Just y)
38+ Nothing -> checkArrived (x:acc) xs
39+ checkArrived acc [] = do
40+ result <- case mTimeout of
41+ Nothing -> Just <$> checkIncoming acc
42+ Just t -> timeout t $ checkIncoming acc
43+ case result of
44+ Nothing -> return (reverse acc, Nothing)
45+ Just (acc', b) -> return (reverse acc', Just b)
46+
47+ checkIncoming :: [a] -> IO ([a], b)
48+ checkIncoming acc = do
49+ x <- readChan incoming
50+ case check x of
51+ Just y -> return (acc, y)
52+ Nothing -> checkIncoming (x:acc)
53+
54+ check :: a -> Maybe b
55+ check = checkMatches matches
56+
57+ checkMatches :: [a -> Maybe b] -> a -> Maybe b
58+ checkMatches [] _ = Nothing
59+ checkMatches (m:ms) a = case m a of Nothing -> checkMatches ms a
60+ Just b -> Just b
distributed-process/tests/TestCH.hs+64−115
@@ -1,10 +1,9 @@
11module Main where
22
33import Prelude hiding (catch)
4−import Data.Binary (Binary)
4+import Data.Binary (Binary(..))
55import Data.Typeable (Typeable)
66import Data.Foldable (forM_)
7−import qualified Data.Map as Map (empty)
87import Control.Concurrent (forkIO, threadDelay)
98import Control.Concurrent.MVar ( MVar
109 , newEmptyMVar
@@ -15,7 +14,7 @@ import Control.Concurrent.MVar ( MVar
1514import Control.Monad (replicateM_)
1615import Control.Monad.IO.Class (liftIO)
1716import Control.Exception (throwIO)
18−import Control.Applicative ((<$>))
17+import Control.Applicative ((<$>), (<*>))
1918import qualified Network.Transport as NT (Transport, closeEndPoint)
2019import Network.Transport.TCP (createTransport, defaultTCPParameters)
2120import Control.Distributed.Process
@@ -44,13 +43,13 @@ testPing transport = do
4443
4544 -- Server
4645 forkIO $ do
47− localNode <- newLocalNode transport Map.empty
46+ localNode <- newLocalNode transport initRemoteCallMetaData
4847 addr <- forkProcess localNode ping
4948 putMVar serverAddr addr
5049
5150 -- Client
5251 forkIO $ do
53− localNode <- newLocalNode transport Map.empty
52+ localNode <- newLocalNode transport initRemoteCallMetaData
5453 pingServer <- readMVar serverAddr
5554
5655 let numPings = 10000
@@ -118,13 +117,13 @@ testMonitorUnreachable transport mOrL un = do
118117 done <- newEmptyMVar
119118
120119 forkIO $ do
121− localNode <- newLocalNode transport Map.empty
120+ localNode <- newLocalNode transport initRemoteCallMetaData
122121 addr <- forkProcess localNode . liftIO $ threadDelay 1000000
123122 closeLocalNode localNode
124123 putMVar deadProcess addr
125124
126125 forkIO $ do
127− localNode <- newLocalNode transport Map.empty
126+ localNode <- newLocalNode transport initRemoteCallMetaData
128127 theirAddr <- readMVar deadProcess
129128 runProcess localNode $
130129 monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
@@ -139,13 +138,13 @@ testMonitorNormalTermination transport mOrL un = do
139138 done <- newEmptyMVar
140139
141140 forkIO $ do
142− localNode <- newLocalNode transport Map.empty
141+ localNode <- newLocalNode transport initRemoteCallMetaData
143142 addr <- forkProcess localNode $
144143 liftIO $ readMVar monitorSetup
145144 putMVar monitoredProcess addr
146145
147146 forkIO $ do
148− localNode <- newLocalNode transport Map.empty
147+ localNode <- newLocalNode transport initRemoteCallMetaData
149148 theirAddr <- readMVar monitoredProcess
150149 runProcess localNode $
151150 monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
@@ -162,14 +161,14 @@ testMonitorAbnormalTermination transport mOrL un = do
162161 let err = userError "Abnormal termination"
163162
164163 forkIO $ do
165− localNode <- newLocalNode transport Map.empty
164+ localNode <- newLocalNode transport initRemoteCallMetaData
166165 addr <- forkProcess localNode . liftIO $ do
167166 readMVar monitorSetup
168167 throwIO err
169168 putMVar monitoredProcess addr
170169
171170 forkIO $ do
172− localNode <- newLocalNode transport Map.empty
171+ localNode <- newLocalNode transport initRemoteCallMetaData
173172 theirAddr <- readMVar monitoredProcess
174173 runProcess localNode $
175174 monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
@@ -181,7 +180,7 @@ testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()
181180testMonitorLocalDeadProcess transport mOrL un = do
182181 processDead <- newEmptyMVar
183182 processAddr <- newEmptyMVar
184− localNode <- newLocalNode transport Map.empty
183+ localNode <- newLocalNode transport initRemoteCallMetaData
185184 done <- newEmptyMVar
186185
187186 forkIO $ do
@@ -204,12 +203,12 @@ testMonitorRemoteDeadProcess transport mOrL un = do
204203 done <- newEmptyMVar
205204
206205 forkIO $ do
207− localNode <- newLocalNode transport Map.empty
206+ localNode <- newLocalNode transport initRemoteCallMetaData
208207 addr <- forkProcess localNode . liftIO $ putMVar processDead ()
209208 putMVar processAddr addr
210209
211210 forkIO $ do
212− localNode <- newLocalNode transport Map.empty
211+ localNode <- newLocalNode transport initRemoteCallMetaData
213212 theirAddr <- readMVar processAddr
214213 readMVar processDead
215214 runProcess localNode $ do
@@ -225,128 +224,83 @@ testMonitorDisconnect transport mOrL un = do
225224 done <- newEmptyMVar
226225
227226 forkIO $ do
228− localNode <- newLocalNode transport Map.empty
227+ localNode <- newLocalNode transport initRemoteCallMetaData
229228 addr <- forkProcess localNode . liftIO $ threadDelay 1000000
230229 putMVar processAddr addr
231230 readMVar monitorSetup
232231 NT.closeEndPoint (localEndPoint localNode)
233232
234233 forkIO $ do
235− localNode <- newLocalNode transport Map.empty
234+ localNode <- newLocalNode transport initRemoteCallMetaData
236235 theirAddr <- readMVar processAddr
237236 runProcess localNode $ do
238237 monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
239238
240239 takeMVar done
241240
241+data Add = Add ProcessId Double Double deriving (Typeable)
242+data Divide = Divide ProcessId Double Double deriving (Typeable)
243+data DivByZero = DivByZero deriving (Typeable)
244+
245+instance Binary Add where
246+ put (Add pid x y) = put pid >> put x >> put y
247+ get = Add <$> get <*> get <*> get
248+
249+instance Binary Divide where
250+ put (Divide pid x y) = put pid >> put x >> put y
251+ get = Divide <$> get <*> get <*> get
252+
253+instance Binary DivByZero where
254+ put DivByZero = return ()
255+ get = return DivByZero
256+
257+math :: Process ()
258+math = do
259+ receiveWait
260+ [ match (\(Add pid x y) -> send pid (x + y))
261+ , matchIf (\(Divide _ _ y) -> y /= 0)
262+ (\(Divide pid x y) -> send pid (x / y))
263+ , match (\(Divide pid _ _) -> send pid DivByZero)
264+ ]
265+ math
242266
243−{-
244−-- Like 'testMonitor1', but throw an exception instead
245−testMonitor2 :: NT.Transport -> IO ()
246−testMonitor2 transport = do
247− deadProcess <- newEmptyMVar
248− done <- newEmptyMVar
249−
250− forkIO $ do
251− localNode <- newLocalNode transport Map.empty
252− addr <- forkProcess localNode $ return ()
253− closeLocalNode localNode
254− putMVar deadProcess addr
255−
256− forkIO $ do
257− localNode <- newLocalNode transport Map.empty
258− theirAddr <- readMVar deadProcess
259− runProcess localNode $ do
260− monitor theirAddr MaLink
261− pcatch (send theirAddr "Hi") $ \(ProcessMonitorException pid SrNoPing) -> do
262− True <- return $ pid == theirAddr
263− return ()
264− putMVar done ()
265−
266− takeMVar done
267−
268−-- The first send succeeds, connection is set up, but then the second send
269−-- fails.
270−--
271−-- TODO: should we specify that we receive exactly one notification per process
272−-- failure?
273−testMonitor3 :: NT.Transport -> IO ()
274−testMonitor3 transport = do
275− firstSend <- newEmptyMVar
276− serverAddr <- newEmptyMVar
277− serverDead <- newEmptyMVar
278− done <- newEmptyMVar
267+-- | Test the math server (i.e., receiveWait)
268+testMath :: NT.Transport -> IO ()
269+testMath transport = do
270+ serverAddr <- newEmptyMVar
271+ clientDone <- newEmptyMVar
279272
273+ -- Server
280274 forkIO $ do
281− localNode <- newLocalNode transport Map.empty
282− -- TODO: what happens when processes terminate?
283− addr <- forkProcess localNode $ return ()
275+ localNode <- newLocalNode transport initRemoteCallMetaData
276+ addr <- forkProcess localNode math
284277 putMVar serverAddr addr
285− readMVar firstSend
286− closeLocalNode localNode
287− threadDelay 10000 -- Give the TCP layer a chance to actually close the socket
288− putMVar serverDead ()
289278
279+ -- Client
290280 forkIO $ do
291− localNode <- newLocalNode transport Map.empty
292− theirAddr <- readMVar serverAddr
293− runProcess localNode $ do
294− monitor theirAddr MaMonitor
295− send theirAddr "Hi"
296− liftIO $ putMVar firstSend () >> readMVar serverDead
297− send theirAddr "Ho"
298− ProcessMonitorException pid SrNoPing <- expect
299− True <- return $ pid == theirAddr
300− -- We should not receive a second exception
301− send theirAddr "Hey"
302− Nothing <- expectTimeout 1000000 :: Process (Maybe ProcessMonitorException)
303− return ()
304− putMVar done ()
305−
306− takeMVar done
307−
308−
309−-- Like testMonitor3, except without the second send (so we must detect the
310−-- failure elsewhere)
311−testMonitor4 :: NT.Transport -> IO ()
312−testMonitor4 transport = do
313− firstSend <- newEmptyMVar
314− serverAddr <- newEmptyMVar
315− serverDead <- newEmptyMVar
316− done <- newEmptyMVar
281+ localNode <- newLocalNode transport initRemoteCallMetaData
282+ mathServer <- readMVar serverAddr
317283
318− forkIO $ do
319− localNode <- newLocalNode transport Map.empty
320− -- TODO: what happens when processes terminate?
321− addr <- forkProcess localNode $ return ()
322− putMVar serverAddr addr
323− readMVar firstSend
324− closeLocalNode localNode
325− threadDelay 10000 -- Give the TCP layer a chance to actually close the socket
326− putMVar serverDead ()
284+ runProcess localNode $ do
285+ pid <- getSelfPid
286+ send mathServer (Add pid 1 2)
287+ 3 <- expect :: Process Double
288+ send mathServer (Divide pid 8 2)
289+ 4 <- expect :: Process Double
290+ send mathServer (Divide pid 8 0)
291+ DivByZero <- expect
292+ liftIO $ putMVar clientDone ()
327293
328− forkIO $ do
329− localNode <- newLocalNode transport Map.empty
330− theirAddr <- readMVar serverAddr
331− runProcess localNode $ do
332− monitor theirAddr MaMonitor
333− send theirAddr "Hi"
334− liftIO $ putMVar firstSend () >> readMVar serverDead
335− ProcessMonitorException pid SrNoPing <- expect
336− True <- return $ pid == theirAddr
337− return ()
338− putMVar done ()
339−
340− takeMVar done
294+ takeMVar clientDone
341295
342−-- TODO: test/specify normal process termination
343−-}
296+-- TODO: test timeout
344297
345298main :: IO ()
346299main = do
347300 Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
348301 runTests
349− [ ("Ping", testPing transport)
302+ [ ("Ping", testPing transport)
303+ , ("Math", testMath transport)
350304 -- The "missing" combinations in the list below don't make much sense, as
351305 -- we cannot guarantee that the monitor reply or link exception will not
352306 -- happen before the unmonitor or unlink
@@ -368,9 +322,4 @@ main = do
368322 , ("UnlinkNormalTermination", testMonitorNormalTermination transport False True)
369323 , ("UnlinkAbnormalTermination", testMonitorAbnormalTermination transport False True)
370324 , ("UnlinkDisconnect", testMonitorDisconnect transport False True)
371− {-
372− , ("Monitor2", testMonitor2 transport)
373− , ("Monitor3", testMonitor3 transport)
374− , ("Monitor4", testMonitor4 transport)
375− -}
376325 ]