git.iko.soy/distributed-process
on master help sign in
Merge branch 'master' of github.com:haskell-distributed/distributed-process
commitc48602c755155e9c88d2e59a97c056cc07169b93 authorEdsko de Vries <edsko@well-typed.com> dateWed 7 Nov 2012, 16:46 (14 years ago) parent834e943314ab28
19 files changed+534−310
README.md+4−4 azure-service-api/azure-service-api.cabal+11−3 azure-service-api/src/Network/Azure/ServiceManagement.hs+46−18 distributed-process-azure/distributed-process-azure.cabal+10−2 distributed-process-azure/src/Control/Distributed/Process/Backend/Azure.hs+3−3 distributed-process-demos/distributed-process-demos.cabal+1−1 distributed-process-simplelocalnet/distributed-process-simplelocalnet.cabal+2−2 distributed-process/distributed-process.cabal+14−10 distributed-process/src/Control/Distributed/Process.hs+3−1 distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+58−19 distributed-process/src/Control/Distributed/Process/Internal/Types.hs+10−2 distributed-process/tests/TestAuxiliary.hs−75 distributed-process/tests/TestCH.hs+255−120 distributed-process/tests/TestClosure.hs+60−45 distributed-static/distributed-static.cabal+1−1 doc/semantics/CloudHaskellSemantics.pdf doc/semantics/CloudHaskellSemantics.tex+54−2 network-transport/network-transport.cabal+1−1 rank1dynamic/rank1dynamic.cabal+1−1
README.md+4−4
@@ -9,12 +9,12 @@ At present, this repository hosts
99* network-transport-tests: Test suite for Network.Transport instantiations
1010* network-transport-tcp: TCP instantiation of Network.Transport
1111* network-transport-inmemory: In-memory instantiation of Network.Transport (incomplete)
12−* network-transport-composed: Compose two transports (incomplete)
12+* network-transport-composed: Compose two transports (very preliminary)
1313* distributed-static: Support for static values
1414* distributed-process: The main Cloud Haskell package
1515* distributed-process-simplelocalnet: Simple backend for local networks
16−* distributed-process-azure: Azure backend for Cloud Haskell (work in progress)
17−* azure-service-api: Haskell bindings for the Azure service API (work in progress)
16+* distributed-process-azure: Azure backend for Cloud Haskell (proof of concept)
17+* azure-service-api: Haskell bindings for the Azure service API
1818* rank1dynamic: Like Data.Dynamic and Data.Typeable but with support for polymorphic values
1919
2020For more detailed information about the interfaces provided by these packages,
@@ -26,7 +26,7 @@ to the corresponding Haddock documentation ([Control.Distributed.Process][3],
2626[Control.Distributed.Process.Node][5], and
2727[Control.Distributed.Process.Backend.SimpleLocalnet][6]).
2828
29−[1]: http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/remote.pdf
29+[1]: http://www.haskell.org/haskellwiki/Cloud_Haskell
3030[2]: https://github.com/haskell-distributed/distributed-process/wiki
3131[3]: http://hackage.haskell.org/packages/archive/distributed-process/0.2.1.4/doc/html/Control-Distributed-Process.html
3232[4]: http://hackage.haskell.org/packages/archive/distributed-process/0.2.1.4/doc/html/Control-Distributed-Process-Closure.html
azure-service-api/azure-service-api.cabal+11−3
@@ -3,8 +3,16 @@
33
44Name: azure-service-api
55Version: 0.1.0.0
6−Synopsis: Haskell bindings for the Azure Service Management API
7−-- description:
6+Synopsis: Haskell bindings for the Microsoft Azure Service Management API
7+Description: At the moment, this module only provides minimal
8+ functionality, just enough to support the
9+ "distributed-process-azure" package (Azure backend for
10+ Cloud Haskell). However, the code is set up in such a way
11+ that adding additional functionality should be relatively
12+ straightforward; developers who wish to do will probably
13+ want to consult the Windows Azure Service Management REST
14+ API Reference
15+ (<http://msdn.microsoft.com/en-us/library/windowsazure/ee460799.aspx>).
816Homepage: github.com/haskell-distributed/azure-service-api
917License: BSD3
1018License-File: LICENSE
@@ -18,7 +26,7 @@ Cabal-Version: >=1.8
1826Library
1927 Exposed-Modules: Network.Azure.ServiceManagement
2028 Build-Depends: base >= 4.5 && < 5,
21− http-conduit >= 1.8 && < 1.9,
29+ http-conduit >= 1.8.1 && < 1.9,
2230 tls >= 1.0 && < 1.1,
2331 tls-extra >= 0.5 && < 0.6,
2432 bytestring >= 0.9 && < 0.11,
azure-service-api/src/Network/Azure/ServiceManagement.hs+46−18
@@ -1,17 +1,23 @@
11{-# LANGUAGE Arrows #-}
22module Network.Azure.ServiceManagement
33 ( -- * Data types
4− HostedService(..)
5− , CloudService(..)
6− , VirtualMachine(..)
7− , Endpoint(..)
4+ CloudService
5+ , cloudServiceName
6+ , cloudServiceVMs
7+ , VirtualMachine
8+ , vmName
9+ , vmIpAddress
10+ , vmInputEndpoints
11+ , Endpoint
12+ , endpointName
13+ , endpointPort
14+ , endpointVip
815 -- * Pure functions
916 , vmSshEndpoint
1017 -- * Setup
1118 , AzureSetup(..)
1219 , azureSetup
1320 -- * High-level API
14− , hostedServices
1521 , cloudServices
1622 ) where
1723
@@ -62,11 +68,7 @@ import Text.XML.HXT.Core
6268 , IOSArrow
6369 , ArrowXml
6470 , getText
65−-- , writeDocumentToString
66−-- , withIndent
67−-- , yes
6871 )
69−-- import Control.Arrow.ArrowIO (arrIO)
7072import Text.XML.HXT.XPath (getXPathTrees)
7173
7274--------------------------------------------------------------------------------
@@ -77,20 +79,39 @@ data HostedService = HostedService {
7779 hostedServiceName :: String
7880 }
7981
82+-- | A cloud service is a bunch of virtual machines that are part of the same
83+-- network (i.e., can talk to each other directly using standard TCP
84+-- connections).
8085data CloudService = CloudService {
86+ -- | Name of the service.
8187 cloudServiceName :: String
88+ -- | Virtual machines that are part of this cloud service.
8289 , cloudServiceVMs :: [VirtualMachine]
8390 }
8491
92+-- | Virtual machine
8593data VirtualMachine = VirtualMachine {
94+ -- | Name of the virtual machine.
8695 vmName :: String
96+ -- | The /internal/ IP address of the virtual machine (that is, the
97+ -- IP address on the Cloud Service). For the globally accessible IP
98+ -- address see 'vmInputEndpoints'.
8799 , vmIpAddress :: String
100+ -- | Globally accessible endpoints to the virtual machine
88101 , vmInputEndpoints :: [Endpoint]
89102 }
90103
104+-- | Globally accessible endpoint for a virtual machine
91105data Endpoint = Endpoint {
106+ -- | Name of the endpoint (typical example: @SSH@)
92107 endpointName :: String
108+ -- | Port number (typical examples are 22 or high numbered ports such as 53749)
93109 , endpointPort :: String
110+ -- | Virtual IP address (that is, globally accessible IP address).
111+ --
112+ -- This corresponds to the IP address associated with the Cloud Service
113+ -- (i.e., that would be returned by a DNS lookup for @name.cloudapp.net@,
114+ -- where @name@ is the name of the Cloud Service).
94115 , endpointVip :: String
95116 }
96117
@@ -148,6 +169,7 @@ hang2 d1 = hang d1 2
148169-- Pure operations --
149170--------------------------------------------------------------------------------
150171
172+-- | Find the endpoint with name @SSH@.
151173vmSshEndpoint :: VirtualMachine -> Maybe Endpoint
152174vmSshEndpoint vm = listToMaybe
153175 [ ep
@@ -160,11 +182,21 @@ vmSshEndpoint vm = listToMaybe
160182--------------------------------------------------------------------------------
161183
162184-- | Azure setup
185+--
186+-- The documentation of "distributed-process-azure" explains in detail how
187+-- to obtain the SSL client certificate and the private key for your Azure
188+-- account.
189+--
190+-- See also 'azureSetup'.
163191data AzureSetup = AzureSetup
164− { subscriptionId :: String
165− , certificate :: X509
166− , privateKey :: PrivateKey
167− , baseUrl :: String
192+ { -- | Azure subscription ID
193+ subscriptionId :: String
194+ -- | SSL client certificate
195+ , certificate :: X509
196+ -- | RSA private key
197+ , privateKey :: PrivateKey
198+ -- | Base URL (generally <https://management.core.windows.net>)
199+ , baseUrl :: String
168200 }
169201
170202-- TODO: it's dubious to be transferring private keys, but we transfer them
@@ -221,7 +253,7 @@ azureSetup sid certPath pkeyPath = do
221253-- High-level API --
222254--------------------------------------------------------------------------------
223255
224−-- | Get a list of virtual machines
256+-- | Find available cloud services
225257cloudServices :: AzureSetup -> IO [CloudService]
226258cloudServices setup = azureExecute setup $ \exec -> do
227259 services <- exec hostedServicesRequest
@@ -240,10 +272,6 @@ cloudServices setup = azureExecute setup $ \exec -> do
240272 }
241273 return $ CloudService (hostedServiceName service) roles
242274
243−-- | Get list of available hosted services
244−hostedServices :: AzureSetup -> IO [HostedService]
245−hostedServices setup = azureExecute setup (\exec -> exec hostedServicesRequest)
246−
247275hostedServicesRequest :: AzureRequest HostedService
248276hostedServicesRequest = AzureRequest
249277 { relativeUrl = "/services/hostedservices"
distributed-process-azure/distributed-process-azure.cabal+10−2
@@ -11,6 +11,14 @@ Stability: experimental
1111Homepage: http://github.com/haskell-distributed/distributed-process
1212Bug-Reports: mailto:edsko@well-typed.com
1313Synopsis: Microsoft Azure backend for Cloud Haskell
14+Description: This is a proof of concept Azure backend for Cloud Haskell. It
15+ provides just enough functionality to run Cloud Haskell
16+ applications on Azure virtual machines. You need to create your
17+ virtual machines in the Azure management portal; you can then
18+ use this backend to copy or verify your executable to the
19+ virtual machine, start or terminate Cloud Haskell nodes on those
20+ virtual machines, and communicate with those virtual machines
21+ from your local machine.
1422Category: Control
1523
1624Source-Repository head
@@ -68,7 +76,7 @@ Executable cloud-haskell-azure-ping
6876 distributed-process >= 0.3.2 && < 0.5,
6977 transformers >= 0.3 && < 0.4,
7078 bytestring >= 0.9 && < 0.11,
71− binary >= 0.5 && < 0.6,
79+ binary >= 0.5 && < 0.7,
7280 mtl,
7381 libssh2
7482 else
@@ -83,7 +91,7 @@ Executable cloud-haskell-azure-fib
8391 distributed-process >= 0.3.2 && < 0.5,
8492 transformers >= 0.3 && < 0.4,
8593 bytestring >= 0.9 && < 0.11,
86− binary >= 0.5 && < 0.6,
94+ binary >= 0.5 && < 0.7,
8795 binary-generic >= 0.2 && < 0.3,
8896 rank1dynamic >= 0.1 && < 0.2,
8997 distributed-static >= 0.2 && < 0.3,
distributed-process-azure/src/Control/Distributed/Process/Backend/Azure.hs+3−3
@@ -41,7 +41,7 @@
4141--
4242-- We have only tested Cloud Haskell with Linux based virtual machines;
4343-- Windows based virtual machines /might/ work, but you'll be entering
44−-- unchartered territory. Cloud Haskell assumes that all nodes run the same
44+-- uncharted territory. Cloud Haskell assumes that all nodes run the same
4545-- binary code; hence, you must use the same OS on all virtual machines,
4646-- /as well as on your local machine/. We use Ubuntu Server 12.04 LTS for our
4747-- tests (running on VirtualBox on our local machine).
@@ -312,7 +312,7 @@
312312-- >
313313-- > import System.Environment (getArgs)
314314-- > import Data.Binary (encode, decode)
315−-- > import Control.Monad (forever)
315+-- > import Control.Monad (void, forever)
316316-- > import Control.Monad.IO.Class (liftIO)
317317-- > import Control.Exception (try, IOException)
318318-- > import Control.Distributed.Process
@@ -386,7 +386,7 @@
386386-- > -- The same binary can behave as the client or the server,
387387-- > -- depending on the command line arguments
388388-- > case cmd of
389−-- > "server" -> spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
389+-- > "server" -> void $ spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
390390-- > "client" -> callOnVM backend vm port $
391391-- > ProcessPair ($(mkClosure 'pingClientRemote) ())
392392-- > pingClientLocal
distributed-process-demos/distributed-process-demos.cabal+1−1
@@ -141,7 +141,7 @@ executable distributed-process-simplelocalnet-mapreduce
141141 distributed-static >= 0.2.0 && < 0.3,
142142 containers >= 0.4 && < 0.6,
143143 bytestring >= 0.9 && < 0.11,
144− binary >= 0.5 && < 0.6,
144+ binary >= 0.5 && < 0.7,
145145 array >= 0.4 && < 0.5,
146146 random >= 1.0 && < 1.1
147147 extensions: BangPatterns, CPP, TemplateHaskell, ScopedTypeVariables
distributed-process-simplelocalnet/distributed-process-simplelocalnet.cabal+2−2
@@ -33,7 +33,7 @@ Library
3333 network >= 2.3 && < 2.5,
3434 network-multicast >= 0.0 && < 0.1,
3535 data-accessor >= 0.2 && < 0.3,
36− binary >= 0.5 && < 0.6,
36+ binary >= 0.5 && < 0.7,
3737 containers >= 0.4 && < 0.6,
3838 transformers >= 0.2 && < 0.4,
3939 network-transport >= 0.3 && < 0.4,
@@ -55,7 +55,7 @@ Executable TestSimpleLocalnet
5555 network >= 2.3 && < 2.5,
5656 network-multicast >= 0.0 && < 0.1,
5757 data-accessor >= 0.2 && < 0.3,
58− binary >= 0.5 && < 0.6,
58+ binary >= 0.5 && < 0.7,
5959 containers >= 0.4 && < 0.6,
6060 transformers >= 0.2 && < 0.4,
6161 network-transport >= 0.3 && < 0.4,
distributed-process/distributed-process.cabal+14−10
@@ -39,7 +39,7 @@ flag benchmarks
3939
4040Library
4141 Build-Depends: base >= 4.4 && < 5,
42− binary >= 0.5 && < 0.6,
42+ binary >= 0.5 && < 0.7,
4343 network-transport >= 0.3 && < 0.4,
4444 stm >= 2.3 && < 2.5,
4545 transformers >= 0.2 && < 0.4,
@@ -93,9 +93,11 @@ Test-Suite TestCH
9393 distributed-process,
9494 network-transport >= 0.3 && < 0.4,
9595 network-transport-tcp >= 0.3 && < 0.4,
96− binary >= 0.5 && < 0.6,
97− network >= 2.3 && < 2.5
98− Other-modules: TestAuxiliary
96+ binary >= 0.5 && < 0.7,
97+ network >= 2.3 && < 2.5,
98+ HUnit >= 1.2 && < 1.3,
99+ test-framework >= 0.6 && < 0.7,
100+ test-framework-hunit >= 0.2 && < 0.3
99101 Extensions: CPP,
100102 ScopedTypeVariables,
101103 DeriveDataTypeable,
@@ -114,8 +116,10 @@ Test-Suite TestClosure
114116 network-transport >= 0.3 && < 0.4,
115117 network-transport-tcp >= 0.3 && < 0.4,
116118 bytestring >= 0.9 && < 0.11,
117− network >= 2.3 && < 2.5
118− Other-modules: TestAuxiliary
119+ network >= 2.3 && < 2.5,
120+ HUnit >= 1.2 && < 1.3,
121+ test-framework >= 0.6 && < 0.7,
122+ test-framework-hunit >= 0.2 && < 0.3
119123 Extensions: CPP,
120124 ScopedTypeVariables
121125 ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
@@ -127,7 +131,7 @@ Executable distributed-process-throughput
127131 distributed-process,
128132 network-transport-tcp >= 0.3 && < 0.4,
129133 bytestring >= 0.9 && < 0.11,
130− binary >= 0.5 && < 0.6
134+ binary >= 0.5 && < 0.7
131135 else
132136 buildable: False
133137 Main-Is: benchmarks/Throughput.hs
@@ -140,7 +144,7 @@ Executable distributed-process-latency
140144 distributed-process,
141145 network-transport-tcp >= 0.3 && < 0.4,
142146 bytestring >= 0.9 && < 0.11,
143− binary >= 0.5 && < 0.6
147+ binary >= 0.5 && < 0.7
144148 else
145149 buildable: False
146150 Main-Is: benchmarks/Latency.hs
@@ -153,7 +157,7 @@ Executable distributed-process-channels
153157 distributed-process,
154158 network-transport-tcp >= 0.3 && < 0.4,
155159 bytestring >= 0.9 && < 0.11,
156− binary >= 0.5 && < 0.6
160+ binary >= 0.5 && < 0.7
157161 else
158162 buildable: False
159163 Main-Is: benchmarks/Channels.hs
@@ -166,7 +170,7 @@ Executable distributed-process-spawns
166170 distributed-process,
167171 network-transport-tcp >= 0.3 && < 0.4,
168172 bytestring >= 0.9 && < 0.11,
169− binary >= 0.5 && < 0.6
173+ binary >= 0.5 && < 0.7
170174 else
171175 buildable: False
172176 Main-Is: benchmarks/Spawns.hs
distributed-process/src/Control/Distributed/Process.hs+3−1
@@ -19,6 +19,7 @@ module Control.Distributed.Process
1919 -- * Basic messaging
2020 , send
2121 , expect
22+ , expectTimeout
2223 -- * Channels
2324 , ReceivePort
2425 , SendPort
@@ -26,6 +27,7 @@ module Control.Distributed.Process
2627 , newChan
2728 , sendChan
2829 , receiveChan
30+ , receiveChanTimeout
2931 , mergePortsBiased
3032 , mergePortsRR
3133 -- * Advanced messaging
@@ -91,7 +93,6 @@ module Control.Distributed.Process
9193 , bracket_
9294 , finally
9395 -- * Auxiliary API
94− , expectTimeout
9596 , spawnAsync
9697 , spawnSupervised
9798 , spawnLink
@@ -215,6 +216,7 @@ import Control.Distributed.Process.Internal.Primitives
215216 , finally
216217 -- Auxiliary API
217218 , expectTimeout
219+ , receiveChanTimeout
218220 , spawnAsync
219221 -- Reconnecting
220222 , reconnect
distributed-process/src/Control/Distributed/Process/Internal/Primitives.hs+58−19
@@ -54,6 +54,7 @@ module Control.Distributed.Process.Internal.Primitives
5454 , finally
5555 -- * Auxiliary API
5656 , expectTimeout
57+ , receiveChanTimeout
5758 , spawnAsync
5859 , linkNode
5960 , linkPort
@@ -74,6 +75,7 @@ import Data.Binary (decode)
7475import Data.Time.Clock (getCurrentTime)
7576import Data.Time.Format (formatTime)
7677import System.Locale (defaultTimeLocale)
78+import System.Timeout (timeout)
7779import Control.Monad.Reader (ask)
7880import Control.Monad.IO.Class (MonadIO, liftIO)
7981import Control.Applicative ((<$>))
@@ -87,6 +89,7 @@ import Control.Distributed.Process.Internal.StrictMVar
8789import Control.Concurrent.Chan (writeChan)
8890import Control.Concurrent.STM
8991 ( STM
92+ , TVar
9093 , atomically
9194 , orElse
9295 , newTVar
@@ -179,7 +182,7 @@ newChan = do
179182 let sport = SendPort cid
180183 chan <- liftIO newTQueueIO
181184 chan' <- mkWeakTQueue chan $ finalizer (processState proc) lcid
182− let rport = ReceivePortSingle chan
185+ let rport = ReceivePort $ readTQueue chan
183186 let tch = TypedChannel chan'
184187 return ( (channelCounter ^: (+ 1))
185188 . (typedChannelWithId lcid ^= Just tch)
@@ -188,7 +191,7 @@ newChan = do
188191 )
189192 where
190193 finalizer :: StrictMVar LocalProcessState -> LocalSendPortId -> IO ()
191− finalizer processState lcid = modifyMVar_ processState $
194+ finalizer st lcid = modifyMVar_ st $
192195 return . (typedChannelWithId lcid ^= Nothing)
193196
194197-- | Send a message on a typed channel
@@ -204,33 +207,39 @@ sendChan (SendPort cid) msg = do
204207-- | Wait for a message on a typed channel
205208receiveChan :: Serializable a => ReceivePort a -> Process a
206209receiveChan = liftIO . atomically . receiveSTM
207− where
208− receiveSTM :: ReceivePort a -> STM a
209− receiveSTM (ReceivePortSingle c) =
210− readTQueue c
211− receiveSTM (ReceivePortBiased ps) =
212− foldr1 orElse (map receiveSTM ps)
213− receiveSTM (ReceivePortRR psVar) = do
214− ps <- readTVar psVar
215− a <- foldr1 orElse (map receiveSTM ps)
216− writeTVar psVar (rotate ps)
217− return a
218210
219− rotate :: [a] -> [a]
220− rotate [] = []
221− rotate (x:xs) = xs ++ [x]
211+-- | Like 'receiveChan' but with a timeout. If the timeout is 0, do a
212+-- non-blocking check for a message.
213+receiveChanTimeout :: Serializable a => Int -> ReceivePort a -> Process (Maybe a)
214+receiveChanTimeout 0 ch = liftIO . atomically $
215+ (Just <$> receiveSTM ch) `orElse` return Nothing
216+receiveChanTimeout n ch = liftIO . timeout n . atomically $
217+ receiveSTM ch
222218
223219-- | Merge a list of typed channels.
224220--
225221-- The result port is left-biased: if there are messages available on more
226222-- than one port, the first available message is returned.
227223mergePortsBiased :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
228−mergePortsBiased = return . ReceivePortBiased
224+mergePortsBiased = return . ReceivePort. foldr1 orElse . map receiveSTM
229225
230226-- | Like 'mergePortsBiased', but with a round-robin scheduler (rather than
231227-- left-biased)
232228mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
233−mergePortsRR ps = liftIO . atomically $ ReceivePortRR <$> newTVar ps
229+mergePortsRR = \ps -> do
230+ psVar <- liftIO . atomically $ newTVar (map receiveSTM ps)
231+ return $ ReceivePort (rr psVar)
232+ where
233+ rotate :: [a] -> [a]
234+ rotate [] = []
235+ rotate (x:xs) = xs ++ [x]
236+
237+ rr :: TVar [STM a] -> STM a
238+ rr psVar = do
239+ ps <- readTVar psVar
240+ a <- foldr1 orElse ps
241+ writeTVar psVar (rotate ps)
242+ return a
234243
235244--------------------------------------------------------------------------------
236245-- Advanced messaging --
@@ -328,6 +337,25 @@ getSelfNode = localNodeId . processNode <$> ask
328337
329338-- | Link to a remote process (asynchronous)
330339--
340+-- When process A links to process B (that is, process A calls
341+-- @link pidB@) then an asynchronous exception will be thrown to process A
342+-- when process B terminates (normally or abnormally), or when process A gets
343+-- disconnected from process B. Although it is /technically/ possible to catch
344+-- these exceptions, chances are if you find yourself trying to do so you should
345+-- probably be using 'monitor' rather than 'link'. In particular, code such as
346+--
347+-- > link pidB -- Link to process B
348+-- > expect -- Wait for a message from process B
349+-- > unlink pidB -- Unlink again
350+--
351+-- doesn't quite do what one might expect: if process B sends a message to
352+-- process A, and /subsequently terminates/, then process A might or might not
353+-- be terminated too, depending on whether the exception is thrown before or
354+-- after the 'unlink' (i.e., this code has a race condition).
355+--
356+-- Linking is all-or-nothing: A is either linked to B, or it's not. A second
357+-- call to 'link' has no effect.
358+--
331359-- Note that 'link' provides unidirectional linking (see 'spawnSupervised').
332360-- Linking makes no distinction between normal and abnormal termination of
333361-- the remote process.
@@ -335,6 +363,17 @@ link :: ProcessId -> Process ()
335363link = sendCtrlMsg Nothing . Link . ProcessIdentifier
336364
337365-- | Monitor another process (asynchronous)
366+--
367+-- When process A monitors process B (that is, process A calls
368+-- @monitor pidB@) then process A will receive a 'ProcessMonitorNotification'
369+-- when process B terminates (normally or abnormally), or when process A gets
370+-- disconnected from process B. You receive this message like any other (using
371+-- 'expect'); the notification includes a reason ('DiedNormal', 'DiedException',
372+-- 'DiedDisconnect', etc.).
373+--
374+-- Every call to 'monitor' returns a new monitor reference 'MonitorRef'; if
375+-- multiple monitors are set up, multiple notifications will be delivered
376+-- and monitors can be disabled individually using 'unmonitor'.
338377monitor :: ProcessId -> Process MonitorRef
339378monitor = monitor' . ProcessIdentifier
340379
@@ -429,7 +468,7 @@ finally a sequel = bracket_ (return ()) sequel a
429468
430469-- | Like 'expect' but with a timeout
431470expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)
432−expectTimeout timeout = receiveTimeout timeout [match return]
471+expectTimeout n = receiveTimeout n [match return]
433472
434473-- | Asynchronous version of 'spawn'
435474--
distributed-process/src/Control/Distributed/Process/Internal/Types.hs+10−2
@@ -82,9 +82,9 @@ import Control.Category ((>>>))
8282import Control.Exception (Exception)
8383import Control.Concurrent (ThreadId)
8484import Control.Concurrent.Chan (Chan)
85−import Control.Concurrent.STM (TVar)
85+import Control.Concurrent.STM (STM)
8686import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
87−import Control.Applicative (Applicative, (<$>), (<*>))
87+import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
8888import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
8989import Control.Monad.IO.Class (MonadIO)
9090import Control.Distributed.Process.Serializable
@@ -262,6 +262,13 @@ newtype SendPort a = SendPort {
262262 deriving (Typeable, Binary, Show, Eq, Ord)
263263
264264-- | The receive end of a typed channel (not serializable)
265+--
266+-- Note that 'ReceivePort' implements 'Functor', 'Applicative', 'Alternative'
267+-- and 'Monad'. This is especially useful when merging receive ports.
268+newtype ReceivePort a = ReceivePort { receiveSTM :: STM a }
269+ deriving (Typeable, Functor, Applicative, Alternative, Monad)
270+
271+{-
265272data ReceivePort a =
266273 -- | A single receive port
267274 ReceivePortSingle (TQueue a)
@@ -270,6 +277,7 @@ data ReceivePort a =
270277 -- | A round-robin combination of receive ports
271278 | ReceivePortRR (TVar [ReceivePort a])
272279 deriving Typeable
280+-}
273281
274282--------------------------------------------------------------------------------
275283-- Messages --
distributed-process/tests/TestAuxiliary.hsdeleted−75
@@ -1,75 +0,0 @@
1−{-# OPTIONS_GHC -fno-warn-orphans #-}
2−module TestAuxiliary ( -- Running tests
3− runTest
4− , runTests
5− -- Writing tests
6− , forkTry
7− , trySome
8− , randomThreadDelay
9− ) where
10−
11−
12−#if ! MIN_VERSION_base(4,6,0)
13−import Prelude hiding (catch)
14−#endif
15−
16−import Control.Concurrent (myThreadId, forkIO, ThreadId, throwTo, threadDelay)
17−import Control.Monad (liftM2, unless)
18−import Control.Exception (SomeException, try, catch)
19−import System.Timeout (timeout)
20−import System.IO (stdout, hFlush)
21−import System.Console.ANSI ( SGR(SetColor, Reset)
22− , Color(Red, Green)
23− , ConsoleLayer(Foreground)
24− , ColorIntensity(Vivid)
25− , setSGR
26− )
27−import System.Random (randomIO)
28−
29−-- | Like fork, but throw exceptions in the child thread to the parent
30−forkTry :: IO () -> IO ThreadId
31−forkTry p = do
32− tid <- myThreadId
33− forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
34−
35−-- | Like try, but specialized to SomeException
36−trySome :: IO a -> IO (Either SomeException a)
37−trySome = try
38−
39−-- | Run the given test, catching timeouts and exceptions
40−runTest :: String -> IO () -> IO Bool
41−runTest description test = do
42− putStr $ "Running " ++ show description ++ ": "
43− hFlush stdout
44− done <- try . timeout 60000000 $ test -- 60 seconds
45− case done of
46− Left err -> failed $ "(exception: " ++ show (err :: SomeException) ++ ")"
47− Right Nothing -> failed "(timeout)"
48− Right (Just ()) -> ok
49− where
50− failed :: String -> IO Bool
51− failed err = do
52− setSGR [SetColor Foreground Vivid Red]
53− putStr "failed "
54− setSGR [Reset]
55− putStrLn err
56− return False
57−
58− ok :: IO Bool
59− ok = do
60− setSGR [SetColor Foreground Vivid Green]
61− putStrLn "ok"
62− setSGR [Reset]
63− return True
64−
65−-- | Run a bunch of tests and throw an exception if any fails
66−runTests :: [(String, IO ())] -> IO ()
67−runTests tests = do
68− success <- foldr (liftM2 (&&) . uncurry runTest) (return True) tests
69− unless success $ fail "Some tests failed"
70−
71−-- | Random thread delay between 0 and the specified max
72−randomThreadDelay :: Int -> IO ()
73−randomThreadDelay maxDelay = do
74− delay <- randomIO :: IO Int
75− threadDelay (delay `mod` maxDelay)
distributed-process/tests/TestCH.hs+255−120
@@ -4,10 +4,11 @@ module Main where
44import Prelude hiding (catch)
55#endif
66
7+
78import Data.Binary (Binary(..))
89import Data.Typeable (Typeable)
910import Data.Foldable (forM_)
10−import Control.Concurrent (forkIO, threadDelay)
11+import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)
1112import Control.Concurrent.MVar
1213 ( MVar
1314 , newEmptyMVar
@@ -16,8 +17,9 @@ import Control.Concurrent.MVar
1617 , readMVar
1718 )
1819import Control.Monad (replicateM_, replicateM, forever)
19−import Control.Exception (throwIO)
20−import Control.Applicative ((<$>), (<*>))
20+import Control.Exception (SomeException, throwIO)
21+import qualified Control.Exception as Ex (catch)
22+import Control.Applicative ((<$>), (<*>), pure, (<|>))
2123import qualified Network.Transport as NT (Transport, closeEndPoint)
2224import Network.Socket (sClose)
2325import Network.Transport.TCP
@@ -32,7 +34,10 @@ import Control.Distributed.Process.Internal.Types
3234 )
3335import Control.Distributed.Process.Node
3436import Control.Distributed.Process.Serializable (Serializable)
35−import TestAuxiliary
37+
38+import Test.HUnit (Assertion)
39+import Test.Framework (Test, defaultMain, testGroup)
40+import Test.Framework.Providers.HUnit (testCase)
3641
3742newtype Ping = Ping ProcessId
3843 deriving (Typeable, Binary, Show)
@@ -40,6 +45,16 @@ newtype Ping = Ping ProcessId
4045newtype Pong = Pong ProcessId
4146 deriving (Typeable, Binary, Show)
4247
48+--------------------------------------------------------------------------------
49+-- Supporting definitions --
50+--------------------------------------------------------------------------------
51+
52+-- | Like fork, but throw exceptions in the child thread to the parent
53+forkTry :: IO () -> IO ThreadId
54+forkTry p = do
55+ tid <- myThreadId
56+ forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
57+
4358-- | The ping server from the paper
4459ping :: Process ()
4560ping = do
@@ -55,35 +70,32 @@ whereisRemote nid string = do
5570 WhereIsReply _ mPid <- expect
5671 return mPid
5772
58−-- | Basic ping test
59−testPing :: NT.Transport -> IO ()
60−testPing transport = do
61− serverAddr <- newEmptyMVar
62− clientDone <- newEmptyMVar
63−
64− -- Server
65− forkIO $ do
66− localNode <- newLocalNode transport initRemoteTable
67− addr <- forkProcess localNode ping
68− putMVar serverAddr addr
69−
70− -- Client
71− forkIO $ do
72− localNode <- newLocalNode transport initRemoteTable
73− pingServer <- readMVar serverAddr
73+data Add = Add ProcessId Double Double deriving (Typeable)
74+data Divide = Divide ProcessId Double Double deriving (Typeable)
75+data DivByZero = DivByZero deriving (Typeable)
7476
75− let numPings = 10000
77+instance Binary Add where
78+ put (Add pid x y) = put pid >> put x >> put y
79+ get = Add <$> get <*> get <*> get
7680
77− runProcess localNode $ do
78− pid <- getSelfPid
79− replicateM_ numPings $ do
80− send pingServer (Pong pid)
81− Ping _ <- expect
82− return ()
81+instance Binary Divide where
82+ put (Divide pid x y) = put pid >> put x >> put y
83+ get = Divide <$> get <*> get <*> get
8384
84− putMVar clientDone ()
85+instance Binary DivByZero where
86+ put DivByZero = return ()
87+ get = return DivByZero
8588
86− takeMVar clientDone
89+-- The math server from the paper
90+math :: Process ()
91+math = do
92+ receiveWait
93+ [ match (\(Add pid x y) -> send pid (x + y))
94+ , matchIf (\(Divide _ _ y) -> y /= 0)
95+ (\(Divide pid x y) -> send pid (x / y))
96+ , match (\(Divide pid _ _) -> send pid DivByZero)
97+ ]
98+ math
8799
88100-- | Monitor or link to a remote node
89101monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
@@ -125,9 +137,42 @@ monitorTestProcess theirAddr mOrL un reason monitorSetup done =
125137 liftIO $ putMVar done ()
126138 )
127139
140+--------------------------------------------------------------------------------
141+-- The tests proper --
142+--------------------------------------------------------------------------------
143+
144+-- | Basic ping test
145+testPing :: NT.Transport -> Assertion
146+testPing transport = do
147+ serverAddr <- newEmptyMVar
148+ clientDone <- newEmptyMVar
149+
150+ -- Server
151+ forkIO $ do
152+ localNode <- newLocalNode transport initRemoteTable
153+ addr <- forkProcess localNode ping
154+ putMVar serverAddr addr
155+
156+ -- Client
157+ forkIO $ do
158+ localNode <- newLocalNode transport initRemoteTable
159+ pingServer <- readMVar serverAddr
160+
161+ let numPings = 10000
162+
163+ runProcess localNode $ do
164+ pid <- getSelfPid
165+ replicateM_ numPings $ do
166+ send pingServer (Pong pid)
167+ Ping _ <- expect
168+ return ()
169+
170+ putMVar clientDone ()
171+
172+ takeMVar clientDone
128173
129174-- | Monitor a process on an unreachable node
130−testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> IO ()
175+testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion
131176testMonitorUnreachable transport mOrL un = do
132177 deadProcess <- newEmptyMVar
133178 done <- newEmptyMVar
@@ -147,7 +192,7 @@ testMonitorUnreachable transport mOrL un = do
147192 takeMVar done
148193
149194-- | Monitor a process which terminates normally
150−testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> IO ()
195+testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
151196testMonitorNormalTermination transport mOrL un = do
152197 monitorSetup <- newEmptyMVar
153198 monitoredProcess <- newEmptyMVar
@@ -168,7 +213,7 @@ testMonitorNormalTermination transport mOrL un = do
168213 takeMVar done
169214
170215-- | Monitor a process which terminates abnormally
171−testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> IO ()
216+testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
172217testMonitorAbnormalTermination transport mOrL un = do
173218 monitorSetup <- newEmptyMVar
174219 monitoredProcess <- newEmptyMVar
@@ -192,7 +237,7 @@ testMonitorAbnormalTermination transport mOrL un = do
192237 takeMVar done
193238
194239-- | Monitor a local process that is already dead
195−testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()
240+testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
196241testMonitorLocalDeadProcess transport mOrL un = do
197242 processDead <- newEmptyMVar
198243 processAddr <- newEmptyMVar
@@ -212,7 +257,7 @@ testMonitorLocalDeadProcess transport mOrL un = do
212257 takeMVar done
213258
214259-- | Monitor a remote process that is already dead
215−testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()
260+testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
216261testMonitorRemoteDeadProcess transport mOrL un = do
217262 processDead <- newEmptyMVar
218263 processAddr <- newEmptyMVar
@@ -233,7 +278,7 @@ testMonitorRemoteDeadProcess transport mOrL un = do
233278 takeMVar done
234279
235280-- | Monitor a process that becomes disconnected
236−testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> IO ()
281+testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion
237282testMonitorDisconnect transport mOrL un = do
238283 processAddr <- newEmptyMVar
239284 monitorSetup <- newEmptyMVar
@@ -254,34 +299,8 @@ testMonitorDisconnect transport mOrL un = do
254299
255300 takeMVar done
256301
257−data Add = Add ProcessId Double Double deriving (Typeable)
258−data Divide = Divide ProcessId Double Double deriving (Typeable)
259−data DivByZero = DivByZero deriving (Typeable)
260−
261−instance Binary Add where
262− put (Add pid x y) = put pid >> put x >> put y
263− get = Add <$> get <*> get <*> get
264−
265−instance Binary Divide where
266− put (Divide pid x y) = put pid >> put x >> put y
267− get = Divide <$> get <*> get <*> get
268−
269−instance Binary DivByZero where
270− put DivByZero = return ()
271− get = return DivByZero
272−
273−math :: Process ()
274−math = do
275− receiveWait
276− [ match (\(Add pid x y) -> send pid (x + y))
277− , matchIf (\(Divide _ _ y) -> y /= 0)
278− (\(Divide pid x y) -> send pid (x / y))
279− , match (\(Divide pid _ _) -> send pid DivByZero)
280− ]
281− math
282−
283302-- | Test the math server (i.e., receiveWait)
284−testMath :: NT.Transport -> IO ()
303+testMath :: NT.Transport -> Assertion
285304testMath transport = do
286305 serverAddr <- newEmptyMVar
287306 clientDone <- newEmptyMVar
@@ -312,7 +331,7 @@ testMath transport = do
312331-- | Send first message (i.e. connect) to an already terminated process
313332-- (without monitoring); then send another message to a second process on
314333-- the same remote node (we're checking that the remote node did not die)
315−testSendToTerminated :: NT.Transport -> IO ()
334+testSendToTerminated :: NT.Transport -> Assertion
316335testSendToTerminated transport = do
317336 serverAddr1 <- newEmptyMVar
318337 serverAddr2 <- newEmptyMVar
@@ -342,7 +361,7 @@ testSendToTerminated transport = do
342361 takeMVar clientDone
343362
344363-- | Test (non-zero) timeout
345−testTimeout :: NT.Transport -> IO ()
364+testTimeout :: NT.Transport -> Assertion
346365testTimeout transport = do
347366 localNode <- newLocalNode transport initRemoteTable
348367 runProcess localNode $ do
@@ -350,7 +369,7 @@ testTimeout transport = do
350369 return ()
351370
352371-- | Test zero timeout
353−testTimeout0 :: NT.Transport -> IO ()
372+testTimeout0 :: NT.Transport -> Assertion
354373testTimeout0 transport = do
355374 serverAddr <- newEmptyMVar
356375 clientDone <- newEmptyMVar
@@ -384,7 +403,7 @@ testTimeout0 transport = do
384403 takeMVar clientDone
385404
386405-- | Test typed channels
387−testTypedChannels :: NT.Transport -> IO ()
406+testTypedChannels :: NT.Transport -> Assertion
388407testTypedChannels transport = do
389408 serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
390409 clientDone <- newEmptyMVar
@@ -410,7 +429,7 @@ testTypedChannels transport = do
410429 takeMVar clientDone
411430
412431-- | Test merging receive ports
413−testMergeChannels :: NT.Transport -> IO ()
432+testMergeChannels :: NT.Transport -> Assertion
414433testMergeChannels transport = do
415434 localNode <- newLocalNode transport initRemoteTable
416435 testFlat localNode True "aaabbbccc"
@@ -424,29 +443,34 @@ testMergeChannels transport = do
424443 where
425444 -- Single layer of merging
426445 testFlat :: LocalNode -> Bool -> String -> IO ()
427− testFlat localNode biased expected =
428− runProcess localNode $ do
446+ testFlat localNode biased expected = do
447+ done <- newEmptyMVar
448+ forkProcess localNode $ do
429449 rs <- mapM charChannel "abc"
430450 m <- mergePorts biased rs
431451 xs <- replicateM 9 $ receiveChan m
432452 True <- return $ xs == expected
433− return ()
453+ liftIO $ putMVar done ()
454+ takeMVar done
434455
435456 -- Two layers of merging
436457 testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
437− testNested localNode biasedInner biasedOuter expected =
438− runProcess localNode $ do
458+ testNested localNode biasedInner biasedOuter expected = do
459+ done <- newEmptyMVar
460+ forkProcess localNode $ do
439461 rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
440462 ms <- mapM (mergePorts biasedInner) rss
441463 m <- mergePorts biasedOuter ms
442464 xs <- replicateM (9 * 3) $ receiveChan m
443465 True <- return $ xs == expected
444− return ()
466+ liftIO $ putMVar done ()
467+ takeMVar done
445468
446469 -- Test that if no messages are (immediately) available, the scheduler makes no difference
447470 testBlocked :: LocalNode -> Bool -> IO ()
448471 testBlocked localNode biased = do
449472 vs <- replicateM 3 newEmptyMVar
473+ done <- newEmptyMVar
450474
451475 forkProcess localNode $ do
452476 [sa, sb, sc] <- liftIO $ mapM readMVar vs
@@ -477,13 +501,15 @@ testMergeChannels transport = do
477501 , (sa, 'a')
478502 ]
479503
480− runProcess localNode $ do
504+ forkProcess localNode $ do
481505 (ss, rs) <- unzip <$> replicateM 3 newChan
482506 liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
483507 m <- mergePorts biased rs
484508 xs <- replicateM (6 * 3) $ receiveChan m
485509 True <- return $ xs == "abcacbbacbcacabcba"
486− return ()
510+ liftIO $ putMVar done ()
511+
512+ takeMVar done
487513
488514 mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
489515 mergePorts True = mergePortsBiased
@@ -496,7 +522,7 @@ testMergeChannels transport = do
496522 liftIO $ threadDelay 10000 -- Make sure messages have been sent
497523 return rport
498524
499−testTerminate :: NT.Transport -> IO ()
525+testTerminate :: NT.Transport -> Assertion
500526testTerminate transport = do
501527 localNode <- newLocalNode transport initRemoteTable
502528
@@ -510,7 +536,7 @@ testTerminate transport = do
510536 True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
511537 return ()
512538
513−testMonitorNode :: NT.Transport -> IO ()
539+testMonitorNode :: NT.Transport -> Assertion
514540testMonitorNode transport = do
515541 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
516542
@@ -522,7 +548,7 @@ testMonitorNode transport = do
522548 True <- return $ ref == ref' && nid == localNodeId node1
523549 return ()
524550
525−testMonitorChannel :: NT.Transport -> IO ()
551+testMonitorChannel :: NT.Transport -> Assertion
526552testMonitorChannel transport = do
527553 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
528554 gotNotification <- newEmptyMVar
@@ -543,7 +569,7 @@ testMonitorChannel transport = do
543569
544570 takeMVar gotNotification
545571
546−testRegistry :: NT.Transport -> IO ()
572+testRegistry :: NT.Transport -> Assertion
547573testRegistry transport = do
548574 node <- newLocalNode transport initRemoteTable
549575
@@ -559,7 +585,7 @@ testRegistry transport = do
559585 True <- return $ pingServer == pid'
560586 return ()
561587
562−testRemoteRegistry :: NT.Transport -> IO ()
588+testRemoteRegistry :: NT.Transport -> Assertion
563589testRemoteRegistry transport = do
564590 node1 <- newLocalNode transport initRemoteTable
565591 node2 <- newLocalNode transport initRemoteTable
@@ -577,7 +603,7 @@ testRemoteRegistry transport = do
577603 True <- return $ pingServer == pid'
578604 return ()
579605
580−testSpawnLocal :: NT.Transport -> IO ()
606+testSpawnLocal :: NT.Transport -> Assertion
581607testSpawnLocal transport = do
582608 node <- newLocalNode transport initRemoteTable
583609
@@ -595,7 +621,7 @@ testSpawnLocal transport = do
595621 send pid sport
596622 expect
597623
598−testReconnect :: NT.Transport -> TransportInternals -> IO ()
624+testReconnect :: NT.Transport -> TransportInternals -> Assertion
599625testReconnect transport transportInternals = do
600626 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
601627 let nid1 = localNodeId node1
@@ -664,7 +690,7 @@ testReconnect transport transportInternals = do
664690
665691-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
666692-- in between
667−testMatchAny :: NT.Transport -> IO ()
693+testMatchAny :: NT.Transport -> Assertion
668694testMatchAny transport = do
669695 proxyAddr <- newEmptyMVar
670696 clientDone <- newEmptyMVar
@@ -695,48 +721,157 @@ testMatchAny transport = do
695721
696722 takeMVar clientDone
697723
698−main :: IO ()
699−main = do
700− Right (transport, transportInternals) <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
701− runTests
702− [ ("Ping", testPing transport)
703− , ("Math", testMath transport)
704− , ("Timeout", testTimeout transport)
705− , ("Timeout0", testTimeout0 transport)
706− , ("SendToTerminated", testSendToTerminated transport)
707− , ("TypedChannnels", testTypedChannels transport)
708− , ("MergeChannels", testMergeChannels transport)
709− , ("Terminate", testTerminate transport)
710− , ("Registry", testRegistry transport)
711− , ("RemoteRegistry", testRemoteRegistry transport)
712− , ("SpawnLocal", testSpawnLocal transport)
713− , ("MatchAny", testMatchAny transport)
724+-- Test 'receiveChanTimeout'
725+testReceiveChanTimeout :: NT.Transport -> Assertion
726+testReceiveChanTimeout transport = do
727+ done <- newEmptyMVar
728+ sendPort <- newEmptyMVar
729+
730+ forkTry $ do
731+ localNode <- newLocalNode transport initRemoteTable
732+ runProcess localNode $ do
733+ -- Create a typed channel
734+ (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
735+ liftIO $ putMVar sendPort sp
736+
737+ -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
738+ Nothing <- receiveChanTimeout 1000000 rp
739+
740+ -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
741+ Just True <- receiveChanTimeout 1000000 rp
742+
743+ -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
744+ Nothing <- receiveChanTimeout 0 rp
745+
746+ -- Again, but now there is a message available
747+ liftIO $ threadDelay 1000000
748+ Just False <- receiveChanTimeout 0 rp
749+
750+ liftIO $ putMVar done ()
751+
752+ forkTry $ do
753+ localNode <- newLocalNode transport initRemoteTable
754+ runProcess localNode $ do
755+ sp <- liftIO $ readMVar sendPort
756+
757+ liftIO $ threadDelay 1500000
758+ sendChan sp True
759+
760+ liftIO $ threadDelay 500000
761+ sendChan sp False
762+
763+ takeMVar done
764+
765+-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
766+testReceiveChanFeatures :: NT.Transport -> Assertion
767+testReceiveChanFeatures transport = do
768+ done <- newEmptyMVar
769+
770+ forkTry $ do
771+ localNode <- newLocalNode transport initRemoteTable
772+ runProcess localNode $ do
773+ (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
774+ (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
775+
776+ -- Test Functor instance
777+
778+ sendChan spInt 2
779+ sendChan spBool False
780+
781+ rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
782+
783+ True <- receiveChan rp1
784+ False <- receiveChan rp1
785+
786+ -- Test Applicative instance
787+
788+ sendChan spInt 3
789+ sendChan spInt 4
790+
791+ let rp2 = pure (+) <*> rpInt <*> rpInt
792+
793+ 7 <- receiveChan rp2
794+
795+ -- Test Alternative instance
796+
797+ sendChan spInt 3
798+ sendChan spBool True
799+
800+ let rp3 = (even <$> rpInt) <|> rpBool
801+
802+ False <- receiveChan rp3
803+ True <- receiveChan rp3
804+
805+ -- Test Monad instance
806+
807+ sendChan spBool True
808+ sendChan spBool False
809+ sendChan spInt 5
810+
811+ let rp4 :: ReceivePort Int
812+ rp4 = do b <- rpBool
813+ if b
814+ then rpInt
815+ else return 7
816+
817+ 5 <- receiveChan rp4
818+ 7 <- receiveChan rp4
819+
820+ liftIO $ putMVar done ()
821+
822+ takeMVar done
823+
824+tests :: (NT.Transport, TransportInternals) -> [Test]
825+tests (transport, transportInternals) = [
826+ testGroup "Basic features" [
827+ testCase "Ping" (testPing transport)
828+ , testCase "Math" (testMath transport)
829+ , testCase "Timeout" (testTimeout transport)
830+ , testCase "Timeout0" (testTimeout0 transport)
831+ , testCase "SendToTerminated" (testSendToTerminated transport)
832+ , testCase "TypedChannnels" (testTypedChannels transport)
833+ , testCase "MergeChannels" (testMergeChannels transport)
834+ , testCase "Terminate" (testTerminate transport)
835+ , testCase "Registry" (testRegistry transport)
836+ , testCase "RemoteRegistry" (testRemoteRegistry transport)
837+ , testCase "SpawnLocal" (testSpawnLocal transport)
838+ , testCase "MatchAny" (testMatchAny transport)
839+ , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
840+ , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
841+ ]
842+ , testGroup "Monitoring and Linking" [
714843 -- Monitoring processes
715844 --
716845 -- The "missing" combinations in the list below don't make much sense, as
717846 -- we cannot guarantee that the monitor reply or link exception will not
718847 -- happen before the unmonitor or unlink
719− , ("MonitorUnreachable", testMonitorUnreachable transport True False)
720− , ("MonitorNormalTermination", testMonitorNormalTermination transport True False)
721− , ("MonitorAbnormalTermination", testMonitorAbnormalTermination transport True False)
722− , ("MonitorLocalDeadProcess", testMonitorLocalDeadProcess transport True False)
723− , ("MonitorRemoteDeadProcess", testMonitorRemoteDeadProcess transport True False)
724− , ("MonitorDisconnect", testMonitorDisconnect transport True False)
725− , ("LinkUnreachable", testMonitorUnreachable transport False False)
726− , ("LinkNormalTermination", testMonitorNormalTermination transport False False)
727− , ("LinkAbnormalTermination", testMonitorAbnormalTermination transport False False)
728− , ("LinkLocalDeadProcess", testMonitorLocalDeadProcess transport False False)
729− , ("LinkRemoteDeadProcess", testMonitorRemoteDeadProcess transport False False)
730− , ("LinkDisconnect", testMonitorDisconnect transport False False)
731− , ("UnmonitorNormalTermination", testMonitorNormalTermination transport True True)
732− , ("UnmonitorAbnormalTermination", testMonitorAbnormalTermination transport True True)
733− , ("UnmonitorDisconnect", testMonitorDisconnect transport True True)
734− , ("UnlinkNormalTermination", testMonitorNormalTermination transport False True)
735− , ("UnlinkAbnormalTermination", testMonitorAbnormalTermination transport False True)
736− , ("UnlinkDisconnect", testMonitorDisconnect transport False True)
848+ testCase "MonitorUnreachable" (testMonitorUnreachable transport True False)
849+ , testCase "MonitorNormalTermination" (testMonitorNormalTermination transport True False)
850+ , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination transport True False)
851+ , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess transport True False)
852+ , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess transport True False)
853+ , testCase "MonitorDisconnect" (testMonitorDisconnect transport True False)
854+ , testCase "LinkUnreachable" (testMonitorUnreachable transport False False)
855+ , testCase "LinkNormalTermination" (testMonitorNormalTermination transport False False)
856+ , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination transport False False)
857+ , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess transport False False)
858+ , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess transport False False)
859+ , testCase "LinkDisconnect" (testMonitorDisconnect transport False False)
860+ , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination transport True True)
861+ , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True True)
862+ , testCase "UnmonitorDisconnect" (testMonitorDisconnect transport True True)
863+ , testCase "UnlinkNormalTermination" (testMonitorNormalTermination transport False True)
864+ , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination transport False True)
865+ , testCase "UnlinkDisconnect" (testMonitorDisconnect transport False True)
737866 -- Monitoring nodes and channels
738− , ("MonitorNode", testMonitorNode transport)
739− , ("MonitorChannel", testMonitorChannel transport)
867+ , testCase "MonitorNode" (testMonitorNode transport)
868+ , testCase "MonitorChannel" (testMonitorChannel transport)
740869 -- Reconnect
741− , ("Reconnect", testReconnect transport transportInternals)
870+ , testCase "Reconnect" (testReconnect transport transportInternals)
742871 ]
872+ ]
873+
874+main :: IO ()
875+main = do
876+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
877+ defaultMain (tests transport)
distributed-process/tests/TestClosure.hs+60−45
@@ -29,7 +29,14 @@ import Control.Distributed.Process.Closure
2929import Control.Distributed.Process.Node
3030import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress))
3131import Control.Distributed.Static (staticLabel, staticClosure)
32−import TestAuxiliary
32+
33+import Test.HUnit (Assertion)
34+import Test.Framework (Test, defaultMain)
35+import Test.Framework.Providers.HUnit (testCase)
36+
37+--------------------------------------------------------------------------------
38+-- Supporting definitions --
39+--------------------------------------------------------------------------------
3340
3441quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
3542quintuple a b c d e = (a, b, c, d, e)
@@ -133,7 +140,18 @@ factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
133140waitClosure :: Int -> Closure (Process ())
134141waitClosure = $(mkClosure 'wait)
135142
136−testUnclosure :: Transport -> RemoteTable -> IO ()
143+simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
144+simulateNetworkFailure transportInternals fr to = liftIO $ do
145+ threadDelay 10000
146+ sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
147+ sClose sock
148+ threadDelay 10000
149+
150+--------------------------------------------------------------------------------
151+-- The tests proper --
152+--------------------------------------------------------------------------------
153+
154+testUnclosure :: Transport -> RemoteTable -> Assertion
137155testUnclosure transport rtable = do
138156 node <- newLocalNode transport rtable
139157 done <- newEmptyMVar
@@ -142,7 +160,7 @@ testUnclosure transport rtable = do
142160 liftIO $ putMVar done ()
143161 takeMVar done
144162
145−testBind :: Transport -> RemoteTable -> IO ()
163+testBind :: Transport -> RemoteTable -> Assertion
146164testBind transport rtable = do
147165 node <- newLocalNode transport rtable
148166 done <- newEmptyMVar
@@ -153,7 +171,7 @@ testBind transport rtable = do
153171 liftIO $ putMVar done ()
154172 takeMVar done
155173
156−testSendPureClosure :: Transport -> RemoteTable -> IO ()
174+testSendPureClosure :: Transport -> RemoteTable -> Assertion
157175testSendPureClosure transport rtable = do
158176 serverAddr <- newEmptyMVar
159177 serverDone <- newEmptyMVar
@@ -174,7 +192,7 @@ testSendPureClosure transport rtable = do
174192
175193 takeMVar serverDone
176194
177−testSendIOClosure :: Transport -> RemoteTable -> IO ()
195+testSendIOClosure :: Transport -> RemoteTable -> Assertion
178196testSendIOClosure transport rtable = do
179197 serverAddr <- newEmptyMVar
180198 serverDone <- newEmptyMVar
@@ -198,7 +216,7 @@ testSendIOClosure transport rtable = do
198216
199217 takeMVar serverDone
200218
201−testSendProcClosure :: Transport -> RemoteTable -> IO ()
219+testSendProcClosure :: Transport -> RemoteTable -> Assertion
202220testSendProcClosure transport rtable = do
203221 serverAddr <- newEmptyMVar
204222 clientDone <- newEmptyMVar
@@ -222,7 +240,7 @@ testSendProcClosure transport rtable = do
222240
223241 takeMVar clientDone
224242
225−testSpawn :: Transport -> RemoteTable -> IO ()
243+testSpawn :: Transport -> RemoteTable -> Assertion
226244testSpawn transport rtable = do
227245 serverNodeAddr <- newEmptyMVar
228246 clientDone <- newEmptyMVar
@@ -243,7 +261,7 @@ testSpawn transport rtable = do
243261
244262 takeMVar clientDone
245263
246−testCall :: Transport -> RemoteTable -> IO ()
264+testCall :: Transport -> RemoteTable -> Assertion
247265testCall transport rtable = do
248266 serverNodeAddr <- newEmptyMVar
249267 clientDone <- newEmptyMVar
@@ -261,7 +279,7 @@ testCall transport rtable = do
261279
262280 takeMVar clientDone
263281
264−testCallBind :: Transport -> RemoteTable -> IO ()
282+testCallBind :: Transport -> RemoteTable -> Assertion
265283testCallBind transport rtable = do
266284 serverNodeAddr <- newEmptyMVar
267285 clientDone <- newEmptyMVar
@@ -279,7 +297,7 @@ testCallBind transport rtable = do
279297
280298 takeMVar clientDone
281299
282−testSeq :: Transport -> RemoteTable -> IO ()
300+testSeq :: Transport -> RemoteTable -> Assertion
283301testSeq transport rtable = do
284302 node <- newLocalNode transport rtable
285303 done <- newEmptyMVar
@@ -297,7 +315,7 @@ testSeq transport rtable = do
297315-- child. The supervisor then throws an exception, the child dies because it
298316-- was linked to the supervisor, and the third process notices that the child
299317-- dies.
300−testSpawnSupervised :: Transport -> RemoteTable -> IO ()
318+testSpawnSupervised :: Transport -> RemoteTable -> Assertion
301319testSpawnSupervised transport rtable = do
302320 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
303321 [superPid, childPid] <- replicateM 2 $ newEmptyMVar
@@ -326,7 +344,7 @@ testSpawnSupervised transport rtable = do
326344 supervisorDeath :: IOException
327345 supervisorDeath = userError "Supervisor died"
328346
329−testSpawnInvalid :: Transport -> RemoteTable -> IO ()
347+testSpawnInvalid :: Transport -> RemoteTable -> Assertion
330348testSpawnInvalid transport rtable = do
331349 node <- newLocalNode transport rtable
332350 done <- newEmptyMVar
@@ -338,7 +356,7 @@ testSpawnInvalid transport rtable = do
338356 liftIO $ putMVar done ()
339357 takeMVar done
340358
341−testClosureExpect :: Transport -> RemoteTable -> IO ()
359+testClosureExpect :: Transport -> RemoteTable -> Assertion
342360testClosureExpect transport rtable = do
343361 node <- newLocalNode transport rtable
344362 done <- newEmptyMVar
@@ -351,7 +369,7 @@ testClosureExpect transport rtable = do
351369 liftIO $ putMVar done ()
352370 takeMVar done
353371
354−testSpawnChannel :: Transport -> RemoteTable -> IO ()
372+testSpawnChannel :: Transport -> RemoteTable -> Assertion
355373testSpawnChannel transport rtable = do
356374 done <- newEmptyMVar
357375 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
@@ -368,7 +386,7 @@ testSpawnChannel transport rtable = do
368386
369387 takeMVar done
370388
371−testTDict :: Transport -> RemoteTable -> IO ()
389+testTDict :: Transport -> RemoteTable -> Assertion
372390testTDict transport rtable = do
373391 done <- newEmptyMVar
374392 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
@@ -377,14 +395,7 @@ testTDict transport rtable = do
377395 liftIO $ putMVar done ()
378396 takeMVar done
379397
380−simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
381−simulateNetworkFailure transportInternals fr to = liftIO $ do
382− threadDelay 10000
383− sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
384− sClose sock
385− threadDelay 10000
386−
387−testFib :: Transport -> RemoteTable -> IO ()
398+testFib :: Transport -> RemoteTable -> Assertion
388399testFib transport rtable = do
389400 nodes <- replicateM 4 $ newLocalNode transport rtable
390401 done <- newEmptyMVar
@@ -397,7 +408,7 @@ testFib transport rtable = do
397408
398409 takeMVar done
399410
400−testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> IO ()
411+testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion
401412testSpawnReconnect transport rtable transportInternals = do
402413 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
403414 let nid1 = localNodeId node1
@@ -426,7 +437,7 @@ testSpawnReconnect transport rtable transportInternals = do
426437
427438-- | 'spawn' used to ave a race condition which would be triggered if the
428439-- spawning process terminates immediately after spawning
429−testSpawnTerminate :: Transport -> RemoteTable -> IO ()
440+testSpawnTerminate :: Transport -> RemoteTable -> Assertion
430441testSpawnTerminate transport rtable = do
431442 slave <- newLocalNode transport rtable
432443 master <- newLocalNode transport rtable
@@ -440,26 +451,30 @@ testSpawnTerminate transport rtable = do
440451
441452 takeMVar masterDone
442453
454+tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
455+tests (transport, transportInternals) rtable = [
456+ testCase "Unclosure" (testUnclosure transport rtable)
457+ , testCase "Bind" (testBind transport rtable)
458+ , testCase "SendPureClosure" (testSendPureClosure transport rtable)
459+ , testCase "SendIOClosure" (testSendIOClosure transport rtable)
460+ , testCase "SendProcClosure" (testSendProcClosure transport rtable)
461+ , testCase "Spawn" (testSpawn transport rtable)
462+ , testCase "Call" (testCall transport rtable)
463+ , testCase "CallBind" (testCallBind transport rtable)
464+ , testCase "Seq" (testSeq transport rtable)
465+ , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)
466+ , testCase "SpawnInvalid" (testSpawnInvalid transport rtable)
467+ , testCase "ClosureExpect" (testClosureExpect transport rtable)
468+ , testCase "SpawnChannel" (testSpawnChannel transport rtable)
469+ , testCase "TDict" (testTDict transport rtable)
470+ , testCase "Fib" (testFib transport rtable)
471+ , testCase "SpawnTerminate" (testSpawnTerminate transport rtable)
472+ , testCase "SpawnReconnect" (testSpawnReconnect transport rtable transportInternals)
473+ ]
474+
475+
443476main :: IO ()
444477main = do
445− Right (transport, transportInternals) <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
478+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
446479 let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
447− runTests
448− [ ("Unclosure", testUnclosure transport rtable)
449− , ("Bind", testBind transport rtable)
450− , ("SendPureClosure", testSendPureClosure transport rtable)
451− , ("SendIOClosure", testSendIOClosure transport rtable)
452− , ("SendProcClosure", testSendProcClosure transport rtable)
453− , ("Spawn", testSpawn transport rtable)
454− , ("Call", testCall transport rtable)
455− , ("CallBind", testCallBind transport rtable)
456− , ("Seq", testSeq transport rtable)
457− , ("SpawnSupervised", testSpawnSupervised transport rtable)
458− , ("SpawnInvalid", testSpawnInvalid transport rtable)
459− , ("ClosureExpect", testClosureExpect transport rtable)
460− , ("SpawnChannel", testSpawnChannel transport rtable)
461− , ("TDict", testTDict transport rtable)
462− , ("Fib", testFib transport rtable)
463− , ("SpawnTerminate", testSpawnTerminate transport rtable)
464− , ("SpawnReconnect", testSpawnReconnect transport rtable transportInternals)
465− ]
480+ defaultMain (tests transport rtable)
distributed-static/distributed-static.cabal+1−1
@@ -30,7 +30,7 @@ Library
3030 rank1dynamic >= 0.1 && < 0.2,
3131 containers >= 0.4 && < 0.6,
3232 bytestring >= 0.9 && < 0.11,
33− binary >= 0.5 && < 0.6
33+ binary >= 0.5 && < 0.7
3434 HS-Source-Dirs: src
3535 Extensions: DeriveDataTypeable,
3636 ScopedTypeVariables
doc/semantics/CloudHaskellSemantics.pdf

Binary file not shown.

doc/semantics/CloudHaskellSemantics.tex+54−2
@@ -3,6 +3,7 @@
33\usepackage{amsmath, amssymb, stmaryrd}
44\usepackage{mathpazo, times}
55\usepackage{float}
6+\usepackage{listings}
67
78\newcommand{\sLam}[2]{\lambda {#1} \cdot {#2}}
89\newcommand{\sApp}[2]{{#1} {#2}}
@@ -60,6 +61,8 @@
6061\floatstyle{boxed}
6162\restylefloat{figure}
6263
64+\lstset{basicstyle=\ttfamily\small}
65+
6366\begin{document}
6467
6568\title{Cloud Haskell Semantics (DRAFT)}
@@ -256,8 +259,8 @@ Figure~\ref{fig:disconnect} gives the semantics for disconnecting and
256259reconnecting. Rule \textsc{Disconnect} models random network disconnect
257260between nodes $\sNid_1$ and $\sNid_2$. We assume that entire \emph{nodes} get
258261disconnected from each other, not individual processes. \emph{Reconnecting}
259−however is on a per process basis (\textsc{Recon-Ex}). Connections to and from
260−node controllers can be implicitly reconnected ($\textsc{Recon-Im}_1$ and
262+however is on a per connection basis (\textsc{Recon-Ex}). Connections to and
263+from node controllers can be implicitly reconnected ($\textsc{Recon-Im}_1$ and
261264$\textsc{Recon-Im}_2$).
262265
263266\begin{figure}
@@ -648,6 +651,55 @@ silently, unless monitors are setup.
648651\caption{\label{fig:termination}Process Termination}
649652\end{figure}
650653
654+\section{Open Issues}
655+
656+\subsection{Ordering of Monitor/Link Notifications}
657+
658+The semantics as described above, like the original Unified semantics, does not
659+guarantee that messages send from process $P$ to process $Q$ (\textsc{Send})
660+are ordered with respect to the link or monitor notification sent when process
661+$P$ terminates normally or abnormally (\textsc{Mon-Ret}, \textsc{Mon-Throw}). This means that if process $P$ does
662+
663+\begin{lstlisting}
664+receiveWait [
665+ match $ \(Reply reply) -> ...
666+ , match $ \(ProcessMonitorNotification ..) -> ...
667+ ]
668+\end{lstlisting}
669+
670+and process $Q$ does
671+
672+\begin{lstlisting}
673+send pidA (Reply reply)
674+// terminate or indeed throw an exception
675+\end{lstlisting}
676+
677+then the semantics does not guarantee that the reply from process $Q$ will
678+arrive at process $P$ before the monitor notification; hence, this results in
679+an (artificial) race condition in process $P$.
680+
681+One possible solution is to regard such a link notification as a message from
682+process $Q$ to process $P$, which should be ordered along with the other
683+messages.
684+
685+\subsection{Ordering and Typed Channels}
686+
687+The situation is more tricky still than sketched above because we of typed
688+channels. The semantics does not provide ordering guarantees between messages
689+sent directly to the process and messages sent on typed channels. That means
690+that even if we consider a link or monitor notification as a message sent to
691+the process, to be ordered with other messages sent to that process, it is
692+still unordered with respect to messages sent on typed channels. This means
693+that we have similar race conditions\footnote{Even ignoring the fact that we
694+currently don't even provide a way to wait for a message on a typed channel
695+\emph{or} a direct message.}
696+
697+A possible solution is to insist that \emph{all} messages from process $P$ to
698+process $Q$ are ordered, no matter how they are sent. From a implementation
699+point of view, this would entail the use of a single ordered network connection
700+for all messages from $P$ to $Q$, rather than using an ordered connection per
701+typed channel plus one for direct messages.
702+
651703\bibliographystyle{apalike}
652704\bibliography{references}
653705
network-transport/network-transport.cabal+1−1
@@ -65,7 +65,7 @@ Source-Repository head
6565
6666Library
6767 Build-Depends: base >= 4.3 && < 5,
68− binary >= 0.5 && < 0.6,
68+ binary >= 0.5 && < 0.7,
6969 bytestring >= 0.9 && < 0.11,
7070 transformers >= 0.2 && < 0.4
7171 Exposed-Modules: Network.Transport,
rank1dynamic/rank1dynamic.cabal+1−1
@@ -19,7 +19,7 @@ Library
1919 Data.Rank1Typeable
2020 Build-Depends: base >= 4.4 && < 4.7,
2121 ghc-prim >= 0.2 && < 0.4,
22− binary >= 0.5 && < 0.6
22+ binary >= 0.5 && < 0.7
2323 HS-Source-Dirs: src
2424 GHC-Options: -Wall
2525 Extensions: EmptyDataDecls,