git.iko.soy/distributed-process
on a307db2 help sign in
.gitignore+13−3
since splitsnapshots
unifiedsplit
@@ -1,4 +1,14 @@
1+tags
2+TAGS
1dist/
3dist/
4+\#*#
5+*~
6+*.aux
7+*.hp
8+*.hi
9+*.o
10+*.prof
11+*.ps
12+*.swp
2−.cabal-sandbox
13+cabal-dev
3−cabal.sandbox.config
14+.DS_Store
4−.stack*
.travis.ymldeleted−43
@@ -1,43 +0,0 @@
1−language: c
2−
3−sudo: false
4−
5−matrix:
6− include:
7− - env: ARGS="--stack-yaml stack-ghc-7.10.3.yaml"
8− addons: {apt: {packages: [libgmp-dev]}}
9− - env: ARGS="--stack-yaml stack-ghc-8.2.2.yaml"
10− addons: {apt: {packages: [libgmp-dev]}}
11− - env: ARGS="--stack-yaml stack-ghc-8.4.4.yaml"
12− addons: {apt: {packages: [libgmp-dev]}}
13− - env: ARGS="--stack-yaml stack-nightly.yaml --resolver nightly"
14− addons: {apt: {packages: [libgmp-dev]}}
15−
16−cache:
17− directories:
18− - $HOME/.stack
19−
20−before_install:
21−- export PATH=$HOME/.local/bin:$PATH;
22− mkdir -p ~/.local/bin;
23− travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack';
24− stack --version
25−
26−install:
27−- stack $ARGS setup --no-terminal;
28− stack $ARGS build --no-terminal --only-snapshot --test --no-run-tests;
29−
30−script:
31−- export ARG='--test-arguments'
32− export TEST_PACKAGE="distributed-process-tests:"
33−- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInMemory
34−- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInTCP
35−- stack ${ARGS} test $ARG='--plain -t "!SpawnReconnect"' ${TEST_PACKAGE}TestClosure
36−- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestStats
37−- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInMemory
38−- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInTCP
39−- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestTracingInMemory
40−
41−notifications:
42− slack:
43− secure: LbqbNjK1KiHNiQo/i/Za4vkMD9l9mhmN+PWYFBQkNvGFWffSRayRKFrTqt4znV2p7h4dY1XZFoC7T5RemBVQLq2ppZAUWxkITeu1OUlnmVLDfJLeVYGTR/fn90nP6Y5ITE7T3A07nKmWaRbKpIgFBAOjzgqaM0csscDx3z0WW18=
CONTRIBUTING.mddeleted−1
@@ -1 +0,0 @@
1−See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
ChangeLogdeleted−236
@@ -1,236 +0,0 @@
1−[Unrealeased]
2−
3−*
4−
5−2018-06-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.4
6−
7−* Added support for exceptions >= 0.10
8−
9−2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.3
10−
11−* Drop support for ghc-7.8.* and earlier.
12−
13−2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.2
14−
15−* Fixed build errors with ghc-8.2.1.
16−
17−2017-08-22 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.1
18−
19−* Relax upper bounds in dependencies to build with ghc-8.2.1.
20−
21−2017-08-21 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.0
22−
23−* Change type of message sent by `say` from a 3-tuple to a proper
24−type (`SayMessage`) with a proper `UTCTime`. (#291)
25−* Expose the MonitorRef in the withMonitor call.
26−* Have unmonitor remove the monitor message in the inbox. (#268)
27−* Remove Mx Data Tables. This API isn't used, is easy to replace with various
28−other packages. (#276)
29−* Add Ord instance to SpawnRef.
30−
31−2016-10-13 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.6
32−
33−* Remove monitors from remote nodes when a process dies. (#295)
34−
35−2016-10-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.5
36−
37−* Use only one connection to communicate between NCs. (#296, #297)
38−* Improve documentation of CQueue.
39−* Implement bidirectional multimaps for links and monitors. (#293, #294)
40−* Fix various warnings. (#292)
41−* Fix some of the intermittent failures in tests.
42−* Improve error messages when node controllers receive invalid requests.
43−(#290)
44−
45−2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.4
46−
47−* Fixup build errors.
48−
49−2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.3
50−
51−* Relax template-haskell upper bound.
52−
53−2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.2
54−
55−* Provide compatibility with ghc-8.0.1
56−* Remove dependency on ghc-prim.
57−* Don't throw exceptions asynchronously.
58−* Bump upper bounds of dependencies.
59−* Fix exception handling in callLocal.
60−* Have spawnLocal inherit the masking state of the caller.
61−* Have nsend send unencoded messages to local processes.
62−
63−2016-03-03 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.1
64−
65−* Implement MonadCatch, MonadThrow, MonadMask for Process.
66−
67−2016-02-18 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.0
68−
69−* Have nsendRemote skip the transport for local communication.
70−* Unsafe primitives for usend and nsendRemote.
71−* Stop using the transport for local communication.
72−* Skip the transport for whereisRemoteAsync and registerRemoteAsync.
73−* Have nsendRemote skip the transport for local communication.
74−* Have runProcess forward exceptions.
75−* Reimport distributed-process-tests. d-p and d-p-test now can be kept in
76−sync.
77−* Add a stack.yaml file for building tests and d-p all at once conveniently.
78−* Implement unreliable forward (uforward).
79−* Add Functor instance for Match data type
80−* Have `spawnAsync` not use the transport in the local case.
81−* Fix monitor race in 'call'.
82−* Add compatibility with ghc-7.10: support new typeable, loosen deps, write
83−proper NFData instances, support new TH.
84−* Kill processes on a local node upon closeLocalNode.
85−* Fix getNodeStats function, see DP-97
86−* Return size of the queue in ProcessInfo.
87−* Implement unreliable send (usend).
88−* Implement MonadFix instance for Process.
89−* Introduce callLocal primitive.
90−* Prevent message loss due to timeouts in CQueue.
91−* More informative ProcessRegistrationException. Now includes the identifier
92−of the process that owns the name, if any.
93−* Avoid message loop between threads when tracing received messages.
94−
95−2015-06-15 Facundo Domínguez <facundo.dominguez@tweag.io> 0.5.5
96−
97−* Fix dependencies.
98−* Add compatibility with GHC-7.10.
99−* Fix various race conditions (DP-99, DP-103).
100−
101−2014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
102−
103−* Fix docstring for `register`
104−* Added Data instance to ProcessId, LocalProcessId and NodeId
105−* Add static serialiation dictionary for 'Static', for completeness
106−* Add Closure static serialization dictionary
107−* Replacement for modifyMVarMasked for GHC <= 7.4
108−* Document the use of built-in trace flags
109−* Make forkProcess exception-safe
110−* Make -Wall clean
111−
112−2014-08-13 Tim Watson <watson.timothy@gmail.com> 0.5.1
113−
114−* Fix cabal docs (thanks Markus Barenhoff)
115−* Expose lifted version of Control.Exception.mask_ (thanks Alexander Vershilov)
116−
117−2014-05-30 Tim Watson <watson.timothy@gmail.com> 0.5.0
118−
119−* Dependency on STM implicitly changed from 1.3 to 1.4, but was not reflected in the cabal file
120−* Race condition in local monitoring when using call
121−* mask now works correctly if unmask is called by another process
122−* Improve efficiency of local message passing
123−* nsend uses local communication channels
124−* Link Node Controller and Network Listener
125−* Label spawned processes using labelThread
126−* Relaxed upper bound on syb in the cabal manifest
127−* Bump binary version to include 0.7.*
128−* Exposed process info
129−* Exposed node statistics
130−* Moved tests to https://github.com/haskell-distributed/distributed-process-tests
131−* Added "polymorphic expect"
132−* Exposed Message type and broaden scope of polymorphic expect
133−* Added Management API (for working with internal/system events)
134−* Tracing can no longer be disabled
135−* We now report node statistics for monitoring/management
136−* Node.runProcess now propagates exceptions to its caller
137−* Added simple micro benchmarks
138−
139−2013-01-27 Tim Watson <watson.timothy@gmail.com> 0.4.2
140−
141−* Improved exception handling for deferred type checked exit reasons
142−* Add matchChan primitive (thanks Simon Marlow)
143−* Expose deferred message handling/checking for AbstractMessage
144−* Add `getProcessInfo' API
145−* Add `trace' API backed by the GHC eventlog
146−
147−2012-11-22 Edsko de Vries <edsko@well-typed.com> 0.4.1
148−
149−* Make behaviour of 'register' more Erlang-like (register will now fail if the
150−name is already registered). Patch by Jeff Epstein.
151−* Functor, Applicative, Alternative and Monad instances for ReceivePort
152−* Add support for receiveChanTimeout
153−* Improved documentation
154−* Avoid name clashes in the TH generation for closures
155−* Relax package bounds to allow for Binary 0.6
156−
157−2012-10-23 Edsko de Vries <edsko@well-typed.com> 0.4.0.2
158−
159−* Fix race condition in spawn
160−
161−2012-10-04 Edsko de Vries <edsko@well-typed.com> 0.4.0.1
162−
163−* Relax package boundaries
164−
165−2012-10-03 Edsko de Vries <edsko@well-typed.com> 0.4.0
166−
167−* Improved treatment of network failure, using new failure semantics of
168−Network.Transport.
169−* Make NodeId Typeable
170−* Extend Template Haskell support with "remotableDec" so that you can refer to
171−$(mkClosure 'f) within the body of "f".
172−* Fix bug in spawnChannelLocal
173−* Numerous memory leaks plugged
174−* Relax upper bound on dependency on 'network'
175−* New primitive 'matchAny'
176−* Remove 'whereisRemote' (see comment of 'whereisRemoteAsync')
177−
178−2012-08-16 Edsko de Vries <edsko@well-typed.com> 0.3.1
179−
180−* Fix memory leaks
181−* Make Template Haskell support optional
182−* Relax dependency constraints
183−
184−2012-08-07 Edsko de Vries <edsko@well-typed.com> 0.3.0
185−
186−* Extract 'static' into a separate package (C.D.Static)
187−* Use new package rank1dynamic to proper runtime checks for polymorphic values
188−
189−2012-08-02 Edsko de Vries <edsko@well-typed.com> 0.2.3.0
190−
191−* Expose the constructors of Closure
192−* Add instance (Typeable a => Serializable (Static a)) and make sure we only
193−use the internal representation of Static where really necessary
194−* Improved docs
195−
196−2012-07-31 Edsko de Vries <edsko@well-typed.com> 0.2.2.0
197−
198−* Add exception handling primitives
199−* Fix runProcess: if the process threw an exception, a 'waiting indefinitely on
200−MVar' exception would be thrown.
201−
202−2012-07-21 Edsko de Vries <edsko@well-typed.com> 0.2.1.4
203−
204−* Bugfix in the node controller
205−(one way this bug materialized: when using the SimpleLocalnet backend,
206−slave nodes could not be reused)
207−* Improved documentation in Control.Distributed.Process.Closure
208−
209−2012-07-20 Edsko de Vries <edsko@well-typed.com> 0.2.1.3
210−
211−* Improve docs
212−* Local versions of spawn
213−
214−2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.2
215−
216−* Base 4.6 compatibility
217−* Relax constraints on bytestring and containers
218−
219−2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.1
220−
221−* Relax upper bound on 'time' dependency
222−
223−2012-07-11 Edsko de Vries <edsko@well-typed.com> 0.2.1
224−
225−* Complete redesign of the underlying implementation of static values and
226−closures.
227−
228−* Add support for 'spawnChannel'
229−
230−2012-07-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
231−
232−* Bugfix: Continue processing messages when a connection breaks.
233−
234−2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
235−
236−* Initial release.
README.md+29−11
@@ -1,16 +1,34 @@
1−# distributed-process
1+Haskell Distributed Project
2−[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process.png)](http://travis-ci.org/haskell-distributed/distributed-process)
2+===========================
3−[![Release](https://img.shields.io/hackage/v/distributed-process.svg)](https://hackage.haskell.org/package/distributed-process)
4
3
5−This repository is part of Cloud Haskell.
4+This repository holds an implementation of [Cloud Haskell][1].
6
5
7−See http://haskell-distributed.github.com for documentation, user guides,
6+At present, this repository hosts
8−tutorials and assistance.
9
7
10−## Getting Help / Raising Issues
8+* network-transport: Generic Network.Transport API
9+* network-transport-tests: Test suite for Network.Transport instantiations
10+* network-transport-tcp: TCP instantiation of Network.Transport
11+* network-transport-inmemory: In-memory instantiation of Network.Transport (incomplete)
12+* network-transport-composed: Compose two transports (incomplete)
13+* distributed-static: Support for static values
14+* distributed-process: The main Cloud Haskell package
15+* 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)
18+* rank1dynamic: Like Data.Dynamic and Data.Typeable but with support for polymorphic values
11
19
12−Please visit the [bug tracker](https://github.com/haskell-distributed/distributed-process/issues) to submit issues. You can contact the distributed-haskell@googlegroups.com mailing list for help and comments.
20+For more detailed information about the interfaces provided by these packages,
21+please refer to the [distributed-process repository wiki][2]. People who wish
22+to get started with Cloud Haskell should cabal install
23+distributed-process and possibly distributed-process-simplelocalnet and refer
24+to the corresponding Haddock documentation ([Control.Distributed.Process][3],
25+[Control.Distributed.Process.Closure][4],
26+[Control.Distributed.Process.Node][5], and
27+[Control.Distributed.Process.Backend.SimpleLocalnet][6]).
13
28
14−## License
29+[1]: http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/remote.pdf
15−
30+[2]: https://github.com/haskell-distributed/distributed-process/wiki
16−This package is made available under a 3-clause BSD-style license.
31+[3]: http://hackage.haskell.org/packages/archive/distributed-process/0.2.1.4/doc/html/Control-Distributed-Process.html
32+[4]: http://hackage.haskell.org/packages/archive/distributed-process/0.2.1.4/doc/html/Control-Distributed-Process-Closure.html
33+[5]: http://hackage.haskell.org/packages/archive/distributed-process/0.2.1.4/doc/html/Control-Distributed-Process-Node.html
34+[6]: http://hackage.haskell.org/packages/archive/distributed-process-simplelocalnet/0.2.0.3/doc/html/Control-Distributed-Process-Backend-SimpleLocalnet.html
azure-service-api/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2012, Edsko de Vries
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Edsko de Vries nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Setup.hs → azure-service-api/Setup.hsrenamed

The content is the same.

azure-service-api/azure-service-api.cabalnew file+36
@@ -0,0 +1,36 @@
1+-- Initial azure-service-api.cabal generated by cabal init. For further
2+-- documentation, see http://haskell.org/cabal/users-guide/
3+
4+Name: azure-service-api
5+Version: 0.1.0.0
6+Synopsis: Haskell bindings for the Azure Service Management API
7+-- description:
8+Homepage: github.com/haskell-distributed/azure-service-api
9+License: BSD3
10+License-File: LICENSE
11+Author: Edsko de Vries
12+Maintainer: edsko@well-typed.com
13+Copyright: Well-Typed LLP
14+Category: Network
15+Build-Type: Simple
16+Cabal-Version: >=1.8
17+
18+Library
19+ Exposed-Modules: Network.Azure.ServiceManagement
20+ Build-Depends: base ==4.5.*,
21+ http-conduit >= 1.6.1 && < 1.7,
22+ tls >= 1.0 && < 1.1,
23+ tls-extra >= 0.5 && < 0.6,
24+ bytestring >= 0.9 && < 1.0,
25+ certificate >= 1.2 && < 1.3,
26+ case-insensitive >= 0.4 && < 0.5,
27+ hxt >= 9.2 && < 9.3,
28+ pretty >= 1.1 && < 1.2,
29+ crypto-pubkey-types >= 0.1 && < 0.2,
30+ binary >= 0.5 && < 0.6,
31+ resourcet,
32+ transformers,
33+ hxt-xpath
34+ HS-Source-Dirs: src
35+ ghc-options: -Wall
36+ Extensions: RankNTypes
azure-service-api/src/Network/Azure/ServiceManagement.hsnew file+291
@@ -0,0 +1,291 @@
1+{-# LANGUAGE Arrows #-}
2+module Network.Azure.ServiceManagement
3+ ( -- * Data types
4+ HostedService(..)
5+ , CloudService(..)
6+ , VirtualMachine(..)
7+ , Endpoint(..)
8+ -- * Pure functions
9+ , vmSshEndpoint
10+ -- * Setup
11+ , AzureSetup(..)
12+ , azureSetup
13+ -- * High-level API
14+ , hostedServices
15+ , cloudServices
16+ ) where
17+
18+import Prelude hiding (id, (.))
19+import Control.Category (id, (.))
20+import Control.Arrow (arr)
21+import Control.Monad (forM)
22+import Control.Applicative ((<$>), (<*>))
23+import Data.Maybe (listToMaybe)
24+import Data.ByteString.Lazy (ByteString)
25+import Data.ByteString.Char8 as BSC (pack)
26+import Data.ByteString.Lazy.Char8 as BSLC (unpack)
27+import Data.Binary (Binary(get,put))
28+import Data.Binary.Put (runPut)
29+import Data.Binary.Get (Get, runGet)
30+import Network.TLS (PrivateKey(PrivRSA))
31+import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
32+import Data.Certificate.X509 (X509, encodeCertificate, decodeCertificate)
33+import qualified Crypto.Types.PubKey.RSA as RSA (PrivateKey(..))
34+import Control.Monad.Trans.Resource (ResourceT)
35+import Control.Monad.IO.Class (liftIO)
36+import Control.Arrow.ArrowList (listA, arr2A)
37+import Text.PrettyPrint
38+ ( Doc
39+ , text
40+ , (<+>)
41+ , ($$)
42+ , vcat
43+ , hang
44+ , doubleQuotes
45+ )
46+import Network.HTTP.Conduit
47+ ( parseUrl
48+ , clientCertificates
49+ , requestHeaders
50+ , withManager
51+ , Response(Response)
52+ , httpLbs
53+ , Manager
54+ )
55+import Data.CaseInsensitive as CI (mk)
56+import Text.XML.HXT.Core
57+ ( runX
58+ , readString
59+ , withValidate
60+ , no
61+ , XmlTree
62+ , IOSArrow
63+ , ArrowXml
64+ , getText
65+-- , writeDocumentToString
66+-- , withIndent
67+-- , yes
68+ )
69+-- import Control.Arrow.ArrowIO (arrIO)
70+import Text.XML.HXT.XPath (getXPathTrees)
71+
72+--------------------------------------------------------------------------------
73+-- Data types --
74+--------------------------------------------------------------------------------
75+
76+data HostedService = HostedService {
77+ hostedServiceName :: String
78+ }
79+
80+data CloudService = CloudService {
81+ cloudServiceName :: String
82+ , cloudServiceVMs :: [VirtualMachine]
83+ }
84+
85+data VirtualMachine = VirtualMachine {
86+ vmName :: String
87+ , vmIpAddress :: String
88+ , vmInputEndpoints :: [Endpoint]
89+ }
90+
91+data Endpoint = Endpoint {
92+ endpointName :: String
93+ , endpointPort :: String
94+ , endpointVip :: String
95+ }
96+
97+--------------------------------------------------------------------------------
98+-- Pretty-printing --
99+--------------------------------------------------------------------------------
100+
101+instance Show HostedService where
102+ show = show . ppHostedService
103+
104+instance Show CloudService where
105+ show = show . ppCloudService
106+
107+instance Show VirtualMachine where
108+ show = show . ppVirtualMachine
109+
110+instance Show Endpoint where
111+ show = show . ppEndpoint
112+
113+ppHostedService :: HostedService -> Doc
114+ppHostedService = text . hostedServiceName
115+
116+ppCloudService :: CloudService -> Doc
117+ppCloudService cs =
118+ (text "Cloud Service" <+> (doubleQuotes . text . cloudServiceName $ cs))
119+ `hang2`
120+ ( text "VIRTUAL MACHINES"
121+ `hang2`
122+ (vcat . map ppVirtualMachine . cloudServiceVMs $ cs)
123+ )
124+
125+ppVirtualMachine :: VirtualMachine -> Doc
126+ppVirtualMachine vm =
127+ (text "Virtual Machine" <+> (doubleQuotes . text . vmName $ vm))
128+ `hang2`
129+ ( text "IP" <+> text (vmIpAddress vm)
130+ $$ ( text "INPUT ENDPOINTS"
131+ `hang2`
132+ (vcat . map ppEndpoint . vmInputEndpoints $ vm)
133+ )
134+ )
135+
136+ppEndpoint :: Endpoint -> Doc
137+ppEndpoint ep =
138+ (text "Input endpoint" <+> (doubleQuotes . text . endpointName $ ep))
139+ `hang2`
140+ ( text "Port" <+> text (endpointPort ep)
141+ $$ text "VIP" <+> text (endpointVip ep)
142+ )
143+
144+hang2 :: Doc -> Doc -> Doc
145+hang2 d1 = hang d1 2
146+
147+--------------------------------------------------------------------------------
148+-- Pure operations --
149+--------------------------------------------------------------------------------
150+
151+vmSshEndpoint :: VirtualMachine -> Maybe Endpoint
152+vmSshEndpoint vm = listToMaybe
153+ [ ep
154+ | ep <- vmInputEndpoints vm
155+ , endpointName ep == "SSH"
156+ ]
157+
158+--------------------------------------------------------------------------------
159+-- Setup --
160+--------------------------------------------------------------------------------
161+
162+-- | Azure setup
163+data AzureSetup = AzureSetup
164+ { subscriptionId :: String
165+ , certificate :: X509
166+ , privateKey :: PrivateKey
167+ , baseUrl :: String
168+ }
169+
170+-- TODO: it's dubious to be transferring private keys, but we transfer them
171+-- over a secure connection and it can be argued that it's safer than actually
172+-- storing the private key on each remote server
173+
174+encodePrivateKey :: PrivateKey -> ByteString
175+encodePrivateKey (PrivRSA pkey) = runPut $ do
176+ put (RSA.private_size pkey)
177+ put (RSA.private_n pkey)
178+ put (RSA.private_d pkey)
179+ put (RSA.private_p pkey)
180+ put (RSA.private_q pkey)
181+ put (RSA.private_dP pkey)
182+ put (RSA.private_dQ pkey)
183+ put (RSA.private_qinv pkey)
184+
185+decodePrivateKey :: ByteString -> PrivateKey
186+decodePrivateKey = PrivRSA . runGet getPrivateKey
187+ where
188+ getPrivateKey :: Get RSA.PrivateKey
189+ getPrivateKey =
190+ RSA.PrivateKey <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get
191+
192+instance Binary AzureSetup where
193+ put (AzureSetup sid cert pkey url) = do
194+ put sid
195+ put (encodeCertificate cert)
196+ put (encodePrivateKey pkey)
197+ put url
198+ get = do
199+ sid <- get
200+ Right cert <- decodeCertificate <$> get
201+ pkey <- decodePrivateKey <$> get
202+ url <- get
203+ return $ AzureSetup sid cert pkey url
204+
205+-- | Initialize Azure
206+azureSetup :: String -- ^ Subscription ID
207+ -> String -- ^ Path to certificate
208+ -> String -- ^ Path to private key
209+ -> IO AzureSetup
210+azureSetup sid certPath pkeyPath = do
211+ cert <- fileReadCertificate certPath
212+ pkey <- fileReadPrivateKey pkeyPath
213+ return AzureSetup {
214+ subscriptionId = sid
215+ , certificate = cert
216+ , privateKey = pkey
217+ , baseUrl = "https://management.core.windows.net"
218+ }
219+
220+--------------------------------------------------------------------------------
221+-- High-level API --
222+--------------------------------------------------------------------------------
223+
224+-- | Get a list of virtual machines
225+cloudServices :: AzureSetup -> IO [CloudService]
226+cloudServices setup = azureExecute setup $ \exec -> do
227+ services <- exec hostedServicesRequest
228+ forM services $ \service -> do
229+ roles <- exec AzureRequest {
230+ relativeUrl = "/services/hostedservices/" ++ hostedServiceName service
231+ ++ "?embed-detail=true"
232+ , apiVersion = "2012-03-01"
233+ , parser = proc xml -> do
234+ role <- getXPathTrees "//Role[@type='PersistentVMRole']" -< xml
235+ name <- getText . getXPathTrees "/Role/RoleName/text()" -< role
236+ roleInst <- arr2A getXPathTrees -< ("//RoleInstance[RoleName='" ++ name ++ "']", xml)
237+ ip <- getText . getXPathTrees "/RoleInstance/IpAddress/text()" -< roleInst
238+ endpoints <- listA (parseEndpoint . getXPathTrees "//InputEndpoint") -< role
239+ id -< VirtualMachine name ip endpoints
240+ }
241+ return $ CloudService (hostedServiceName service) roles
242+
243+-- | Get list of available hosted services
244+hostedServices :: AzureSetup -> IO [HostedService]
245+hostedServices setup = azureExecute setup (\exec -> exec hostedServicesRequest)
246+
247+hostedServicesRequest :: AzureRequest HostedService
248+hostedServicesRequest = AzureRequest
249+ { relativeUrl = "/services/hostedservices"
250+ , apiVersion = "2012-03-01"
251+ , parser = arr HostedService
252+ . getText
253+ . getXPathTrees "//ServiceName/text()"
254+ }
255+
256+parseEndpoint :: ArrowXml t => t XmlTree Endpoint
257+parseEndpoint = proc endpoint -> do
258+ name <- getText . getXPathTrees "//Name/text()" -< endpoint
259+ port <- getText . getXPathTrees "//Port/text()" -< endpoint
260+ vip <- getText . getXPathTrees "//Vip/text()" -< endpoint
261+ id -< Endpoint name port vip
262+
263+--------------------------------------------------------------------------------
264+-- Low-level API --
265+--------------------------------------------------------------------------------
266+
267+data AzureRequest c = AzureRequest {
268+ relativeUrl :: String
269+ , apiVersion :: String
270+ , parser :: IOSArrow XmlTree c
271+ }
272+
273+azureExecute :: AzureSetup -> ((forall b. AzureRequest b -> ResourceT IO [b]) -> ResourceT IO a) -> IO a
274+azureExecute setup f = withManager (\manager -> f (go manager))
275+ where
276+ go :: Manager -> forall b. AzureRequest b -> ResourceT IO [b]
277+ go manager request = do
278+ req <- parseUrl $ baseUrl setup
279+ ++ "/" ++ subscriptionId setup
280+ ++ "/" ++ relativeUrl request
281+ let req' = req {
282+ clientCertificates = [ (certificate setup, Just $ privateKey setup) ]
283+ , requestHeaders = [ (CI.mk $ BSC.pack "x-ms-version", BSC.pack $ apiVersion request)
284+ , (CI.mk $ BSC.pack "content-type", BSC.pack "application/xml")
285+ ]
286+ }
287+ Response _ _ _ lbs <- httpLbs req' manager
288+ liftIO . runX $ proc _ -> do
289+ xml <- readString [withValidate no] (BSLC.unpack lbs) -< ()
290+ -- arrIO putStrLn . writeDocumentToString [withIndent yes] -< xml
291+ parser request -< xml
benchmarks/ProcessRing.hsdeleted−117
@@ -1,117 +0,0 @@
1−{- ProcessRing benchmarks.
2−
3−To run the benchmarks, select a value for the ring size (sz) and
4−the number of times to send a message around the ring
5−
6−-}
7−
8−{-# LANGUAGE BangPatterns #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−
11−import Control.Monad
12−import Control.Distributed.Process hiding (catch)
13−import Control.Distributed.Process.Node
14−import Control.Exception (catch, SomeException)
15−import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
16−import System.Environment
17−import System.Console.GetOpt
18−
19−data Options = Options
20− { optRingSize :: Int
21− , optIterations :: Int
22− , optForward :: Bool
23− , optParallel :: Bool
24− , optUnsafe :: Bool
25− } deriving Show
26−
27−initialProcess :: Options -> Process ()
28−initialProcess op =
29− let ringSz = optRingSize op
30− msgCnt = optIterations op
31− fwd = optForward op
32− unsafe = optUnsafe op
33− msg = ("foobar", "baz")
34− in do
35− self <- getSelfPid
36− ring <- makeRing fwd unsafe ringSz self
37− forM_ [1..msgCnt] (\_ -> send ring msg)
38− collect msgCnt
39− where relay fsend pid = do
40− msg <- expect :: Process (String, String)
41− fsend pid msg
42− relay fsend pid
43−
44− forward' pid =
45− receiveWait [ matchAny (\m -> forward m pid) ] >> forward' pid
46−
47− makeRing :: Bool -> Bool -> Int -> ProcessId -> Process ProcessId
48− makeRing !f !u !n !pid
49− | n == 0 = go f u pid
50− | otherwise = go f u pid >>= makeRing f u (n - 1)
51−
52− go :: Bool -> Bool -> ProcessId -> Process ProcessId
53− go False False next = spawnLocal $ relay send next
54− go False True next = spawnLocal $ relay unsafeSend next
55− go True _ next = spawnLocal $ forward' next
56−
57− collect :: Int -> Process ()
58− collect !n
59− | n == 0 = return ()
60− | otherwise = do
61− receiveWait [
62− matchIf (\(a, b) -> a == "foobar" && b == "baz")
63− (\_ -> return ())
64− , matchAny (\_ -> error "unexpected input!")
65− ]
66− collect (n - 1)
67−
68−defaultOptions :: Options
69−defaultOptions = Options
70− { optRingSize = 10
71− , optIterations = 100
72− , optForward = False
73− , optParallel = False
74− , optUnsafe = False
75− }
76−
77−options :: [OptDescr (Options -> Options)]
78−options =
79− [ Option ['s'] ["ring-size"] (OptArg optSz "SIZE") "# of processes in ring"
80− , Option ['i'] ["iterations"] (OptArg optMsgCnt "ITER") "# of times to send"
81− , Option ['f'] ["forward"]
82− (NoArg (\opts -> opts { optForward = True }))
83− "use `forward' instead of send - default = False"
84− , Option ['u'] ["unsafe-send"]
85− (NoArg (\opts -> opts { optUnsafe = True }))
86− "use 'unsafeSend' (ignored with -f) - default = False"
87− , Option ['p'] ["parallel"]
88− (NoArg (\opts -> opts { optParallel = True }))
89− "send in parallel and consume sequentially - default = False"
90− ]
91−
92−optMsgCnt :: Maybe String -> Options -> Options
93−optMsgCnt Nothing opts = opts
94−optMsgCnt (Just c) opts = opts { optIterations = ((read c) :: Int) }
95−
96−optSz :: Maybe String -> Options -> Options
97−optSz Nothing opts = opts
98−optSz (Just s) opts = opts { optRingSize = ((read s) :: Int) }
99−
100−parseArgv :: [String] -> IO (Options, [String])
101−parseArgv argv = do
102− pn <- getProgName
103− case getOpt Permute options argv of
104− (o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)
105− (_,_,errs) -> ioError (userError (concat errs ++ usageInfo (header pn) options))
106− where header pn' = "Usage: " ++ pn' ++ " [OPTION...]"
107−
108−main :: IO ()
109−main = do
110− argv <- getArgs
111− (opt, _) <- parseArgv argv
112− putStrLn $ "options: " ++ (show opt)
113− Right transport <- createTransport
114− (defaultTCPAddr "127.0.0.1" "8090" ) defaultTCPParameters
115− node <- newLocalNode transport initRemoteTable
116− catch (void $ runProcess node $ initialProcess opt)
117− (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
benchmarks/erlang/ring.erldeleted−62
@@ -1,62 +0,0 @@
1−%% can be run via escript if desired...
2−-module(ring).
3−
4−-export([main/1]).
5−
6−-record(opts, {
7− ring_size = 1 :: non_neg_integer(),
8− iterations = 1 :: non_neg_integer()
9−}).
10−
11−main(Argv) when is_list(Argv) ->
12− Opts = parse_args(Argv),
13− {Time, ok} = timer:tc(fun() -> run(Opts) end),
14− io:format("elapsed (wallclock) time: ~pms~n", [Time / 1000]).
15−
16−run(#opts{ ring_size=RingSize, iterations=SendCount }) ->
17− Self = self(),
18− Msg = {"foobar", "baz"},
19− Pid = make_ring(RingSize, Self),
20− [Pid ! Msg || _ <- lists:seq(1, SendCount)],
21− collect(SendCount).
22−
23−collect(0) ->
24− ok;
25−collect(N) ->
26− receive
27− {"foobar", "baz"} -> collect(N - 1);
28− Other -> exit({unexpected_message, Other})
29− end.
30−
31−-spec(make_ring(integer(), pid()) -> pid()).
32−make_ring(0, NextPid) ->
33− go(NextPid);
34−make_ring(NumProcs, NextPid) ->
35− make_ring(NumProcs - 1, go(NextPid)).
36−
37−-spec(go(pid()) -> pid()).
38−go(NextPid) ->
39− spawn(fun() -> relay(NextPid) end).
40−
41−-spec(relay(pid()) -> no_return()).
42−relay(NextPid) ->
43− receive
44− {_, _}=Msg -> NextPid ! Msg, relay(NextPid)
45− end.
46−
47−-spec(parse_args([string()]) -> #opts{}).
48−parse_args(Argv) ->
49− lists:foldl(fun parse_args/2, #opts{}, Argv).
50−
51−parse_args(("-r" ++ Val), Opts) ->
52− Sz = check_positive(ring_size, list_to_integer(Val)),
53− Opts#opts{ ring_size = Sz };
54−parse_args(("-i" ++ Val), Opts) ->
55− Iter = check_positive(iterations, list_to_integer(Val)),
56− Opts#opts{ iterations = Iter }.
57−
58−check_positive(K, N) when N < 1 ->
59− io:format("~p must be >= 1~n", [K]),
60− error({K, N});
61−check_positive(_, N) ->
62− N.
distributed-process-tests/LICENSE → distributed-process-azure/LICENSErenamed

The content is the same.

distributed-process-tests/Setup.hs → distributed-process-azure/Setup.hsrenamed

The content is the same.

distributed-process-azure/demos/Echo.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+import System.IO (hFlush, stdout)
4+import System.Environment (getArgs)
5+import Control.Monad (unless, forever)
6+import Control.Monad.IO.Class (liftIO)
7+import Control.Distributed.Process (Process, expect)
8+import Control.Distributed.Process.Closure (remotable, mkClosure)
9+import Control.Distributed.Process.Backend.Azure
10+
11+echoRemote :: () -> Backend -> Process ()
12+echoRemote () _backend = forever $ do
13+ str <- expect
14+ remoteSend (str :: String)
15+
16+remotable ['echoRemote]
17+
18+echoLocal :: LocalProcess ()
19+echoLocal = do
20+ str <- liftIO $ putStr "# " >> hFlush stdout >> getLine
21+ unless (null str) $ do
22+ localSend str
23+ liftIO $ putStr "Echo: " >> hFlush stdout
24+ echo <- localExpect
25+ liftIO $ putStrLn echo
26+ echoLocal
27+
28+main :: IO ()
29+main = do
30+ args <- getArgs
31+ case args of
32+ "onvm":args' ->
33+ -- Pass execution to 'onVmMain' if we are running on the VM
34+ -- ('callOnVM' will provide the right arguments)
35+ onVmMain __remoteTable args'
36+
37+ sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
38+ -- Initialize the Azure backend
39+ params <- defaultAzureParameters sid x509 pkey
40+ let params' = params { azureSshUserName = user }
41+ backend <- initializeBackend params' cloudService
42+
43+ -- Find the specified virtual machine
44+ Just vm <- findNamedVM backend virtualMachine
45+
46+ -- Run the echo client proper
47+ callOnVM backend vm port $
48+ ProcessPair ($(mkClosure 'echoRemote) ())
49+ echoLocal
distributed-process-azure/demos/Fib.hsnew file+74
@@ -0,0 +1,74 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+import System.Environment (getArgs)
3+import System.Random (randomIO)
4+import Control.Monad (forM)
5+import Control.Monad.IO.Class (liftIO)
6+import Control.Distributed.Process
7+ ( Process
8+ , NodeId
9+ , SendPort
10+ , newChan
11+ , sendChan
12+ , spawn
13+ , receiveChan
14+ , spawnLocal
15+ )
16+import Control.Distributed.Process.Backend.Azure
17+import Control.Distributed.Process.Closure
18+ ( remotable
19+ , remotableDecl
20+ , mkClosure
21+ )
22+
23+randomElement :: [a] -> IO a
24+randomElement xs = do
25+ ix <- randomIO
26+ return (xs !! (ix `mod` length xs))
27+
28+remotableDecl [
29+ [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
30+ dfib (_, reply, 0) = sendChan reply 0
31+ dfib (_, reply, 1) = sendChan reply 1
32+ dfib (nids, reply, n) = do
33+ nid1 <- liftIO $ randomElement nids
34+ nid2 <- liftIO $ randomElement nids
35+ (sport, rport) <- newChan
36+ _ <- spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
37+ _ <- spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
38+ n1 <- receiveChan rport
39+ n2 <- receiveChan rport
40+ sendChan reply $ n1 + n2
41+ |]
42+ ]
43+
44+remoteFib :: ([NodeId], Integer) -> Backend -> Process ()
45+remoteFib (nids, n) _backend = do
46+ (sport, rport) <- newChan
47+ _ <- spawnLocal $ dfib (nids, sport, n)
48+ fib_n <- receiveChan rport
49+ mapM_ terminateNode nids
50+ remoteSend fib_n
51+
52+remotable ['remoteFib]
53+
54+printResult :: LocalProcess ()
55+printResult = do
56+ result <- localExpect :: LocalProcess Integer
57+ liftIO $ print result
58+
59+main :: IO ()
60+main = do
61+ args <- getArgs
62+ case args of
63+ "onvm":args' -> onVmMain (__remoteTable . __remoteTableDecl) args'
64+ [sid, x509, pkey, user, cloudService, n] -> do
65+ params <- defaultAzureParameters sid x509 pkey
66+ let params' = params { azureSshUserName = user }
67+ backend <- initializeBackend params' cloudService
68+ vms <- findVMs backend
69+ nids <- forM vms $ \vm -> spawnNodeOnVM backend vm "8080"
70+ callOnVM backend (head vms) "8081" $
71+ ProcessPair ($(mkClosure 'remoteFib) (nids, read n :: Integer))
72+ printResult
73+ _ ->
74+ error "Invalid command line arguments"
distributed-process-azure/demos/Ping.hsnew file+85
@@ -0,0 +1,85 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+import System.Environment (getArgs)
4+import Data.Binary (encode, decode)
5+import Control.Monad (forever)
6+import Control.Monad.IO.Class (liftIO)
7+import Control.Exception (try, IOException)
8+import Control.Distributed.Process
9+ ( Process
10+ , getSelfPid
11+ , expect
12+ , send
13+ , monitor
14+ , receiveWait
15+ , match
16+ , ProcessMonitorNotification(..)
17+ )
18+import Control.Distributed.Process.Closure (remotable, mkClosure)
19+import Control.Distributed.Process.Backend.Azure
20+import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
21+
22+pingServer :: () -> Backend -> Process ()
23+pingServer () _backend = do
24+ us <- getSelfPid
25+ liftIO $ BSL.writeFile "pingServer.pid" (encode us)
26+ forever $ do
27+ them <- expect
28+ send them ()
29+
30+pingClientRemote :: () -> Backend -> Process ()
31+pingClientRemote () _backend = do
32+ mPingServerEnc <- liftIO $ try (BSL.readFile "pingServer.pid")
33+ case mPingServerEnc of
34+ Left err ->
35+ remoteSend $ "Ping server not found: " ++ show (err :: IOException)
36+ Right pingServerEnc -> do
37+ let pingServerPid = decode pingServerEnc
38+ pid <- getSelfPid
39+ _ref <- monitor pingServerPid
40+ send pingServerPid pid
41+ gotReply <- receiveWait
42+ [ match (\() -> return True)
43+ , match (\(ProcessMonitorNotification {}) -> return False)
44+ ]
45+ if gotReply
46+ then remoteSend $ "Ping server at " ++ show pingServerPid ++ " ok"
47+ else remoteSend $ "Ping server at " ++ show pingServerPid ++ " failure"
48+
49+remotable ['pingClientRemote, 'pingServer]
50+
51+pingClientLocal :: LocalProcess ()
52+pingClientLocal = localExpect >>= liftIO . putStrLn
53+
54+main :: IO ()
55+main = do
56+ args <- getArgs
57+ case args of
58+ "onvm":args' ->
59+ -- Pass execution to 'onVmMain' if we are running on the VM
60+ onVmMain __remoteTable args'
61+
62+ "list":sid:x509:pkey:_ -> do
63+ -- List all available cloud services
64+ -- (useful, but not strictly necessary for the example)
65+ params <- defaultAzureParameters sid x509 pkey
66+ css <- cloudServices (azureSetup params)
67+ mapM_ print css
68+
69+ cmd:sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
70+ -- Initialize the backend and find the right VM
71+ params <- defaultAzureParameters sid x509 pkey
72+ let params' = params { azureSshUserName = user }
73+ backend <- initializeBackend params' cloudService
74+ Just vm <- findNamedVM backend virtualMachine
75+
76+ -- The same binary can behave as the client or the server,
77+ -- depending on the command line arguments
78+ case cmd of
79+ "server" -> do
80+ pid <- spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
81+ putStrLn $ "Ping server started at " ++ show pid
82+ "client" ->
83+ callOnVM backend vm port $
84+ ProcessPair ($(mkClosure 'pingClientRemote) ())
85+ pingClientLocal
distributed-process-azure/distributed-process-azure.cabalnew file+95
@@ -0,0 +1,95 @@
1+Name: distributed-process-azure
2+Version: 0.1.0
3+Cabal-Version: >=1.8
4+Build-Type: Simple
5+License: BSD3
6+License-File: LICENSE
7+Copyright: Well-Typed LLP
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: edsko@well-typed.com, dcoutts@well-typed.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com
13+Synopsis: Microsoft Azure backend for Cloud Haskell
14+Category: Control
15+
16+Source-Repository head
17+ Type: git
18+ Location: https://github.com/haskell-distributed/distributed-process
19+ SubDir: distributed-process-azure
20+
21+Flag build-demos
22+ description: Build the demos
23+ default: False
24+
25+Library
26+ Build-Depends: base >= 4.4 && < 5,
27+ azure-service-api >= 0.1 && < 0.2,
28+ filepath >= 1.3 && < 1.4,
29+ executable-path >= 0.0.3 && < 0.1,
30+ libssh2 >= 0.2 && < 0.3,
31+ pureMD5 >= 2.1 && < 2.2,
32+ bytestring >= 0.9 && < 0.11,
33+ distributed-process >= 0.3.2 && < 0.4,
34+ binary >= 0.5 && < 0.6,
35+ network-transport-tcp >= 0.3 && < 0.4,
36+ optparse-applicative >= 0.2 && < 0.4,
37+ transformers >= 0.3 && < 0.4,
38+ certificate >= 1.2.4 && < 1.3,
39+ unix >= 2.5 && < 2.6,
40+ network-transport >= 0.3 && < 0.4,
41+ mtl >= 2.1 && < 2.2,
42+ rank1dynamic >= 0.1 && < 0.2,
43+ distributed-static >= 0.2 && < 0.3
44+ Exposed-modules: Control.Distributed.Process.Backend.Azure
45+ Extensions: ViewPatterns,
46+ RankNTypes,
47+ ExistentialQuantification,
48+ ScopedTypeVariables,
49+ DeriveDataTypeable,
50+ GeneralizedNewtypeDeriving
51+ ghc-options: -Wall
52+ HS-Source-Dirs: src
53+
54+Executable cloud-haskell-azure-echo
55+ if flag(build-demos)
56+ Build-Depends: base >= 4.4 && < 5,
57+ distributed-process-azure >= 0.1 && < 0.2,
58+ distributed-process >= 0.3.2 && < 0.4,
59+ transformers >= 0.3 && < 0.4
60+ else
61+ buildable: False
62+ Main-Is: demos/Echo.hs
63+ ghc-options: -Wall
64+
65+Executable cloud-haskell-azure-ping
66+ if flag(build-demos)
67+ Build-Depends: base >= 4.4 && < 5,
68+ distributed-process-azure >= 0.1 && < 0.2,
69+ distributed-process >= 0.3.2 && < 0.4,
70+ transformers >= 0.3 && < 0.4,
71+ bytestring >= 0.9 && < 0.11,
72+ binary >= 0.5 && < 0.6,
73+ mtl,
74+ libssh2
75+ else
76+ buildable: False
77+ Main-Is: demos/Ping.hs
78+ ghc-options: -Wall
79+
80+Executable cloud-haskell-azure-fib
81+ if flag(build-demos)
82+ Build-Depends: base >= 4.4 && < 5,
83+ distributed-process-azure >= 0.1 && < 0.2,
84+ distributed-process >= 0.3.2 && < 0.4,
85+ transformers >= 0.3 && < 0.4,
86+ bytestring >= 0.9 && < 0.11,
87+ binary >= 0.5 && < 0.6,
88+ binary-generic >= 0.2 && < 0.3,
89+ rank1dynamic >= 0.1 && < 0.2,
90+ distributed-static >= 0.2 && < 0.3,
91+ random >= 1.0 && < 1.1
92+ else
93+ buildable: False
94+ Main-Is: demos/Fib.hs
95+ ghc-options: -Wall
distributed-process-azure/src/Control/Distributed/Process/Backend/Azure.hsnew file+964
@@ -0,0 +1,964 @@
1+-- | This module provides the API for running Cloud Haskell on Microsoft Azure
2+-- virtual machines (<http://www.windowsazure.com>). Virtual machines within an
3+-- Azure cloud service can talk to each other directly using standard Cloud
4+-- Haskell primitives (using TCP/IP under the hood); to talk to the remote
5+-- machines from your local machine you can use the primitives provided in this
6+-- module (which use ssh under the hood). It looks something like
7+--
8+-- > _ _
9+-- > ( ` )_
10+-- > ( ) `) Azure cloud service
11+-- > (_ (_ . _) _)
12+-- >
13+-- > |
14+-- > | ssh connection
15+-- > |
16+-- >
17+-- > +---+
18+-- > | | Local machine
19+-- > +---+
20+--
21+-- /NOTE/: It is unfortunate that the local machine cannot talk to the remote
22+-- machine using the standard Cloud Haskell primitives. In an ideal world, we
23+-- could just start a Cloud Haskell node on the local machine, too.
24+-- Unfortunately, Cloud Haskell does not yet support using multiple network
25+-- transports within the same system (i.e. both TCP/IP and SSH). This is a
26+-- temporary workaround.
27+--
28+-- [Azure Setup]
29+--
30+-- In this section we describe how to set up an Azure Cloud Service for use
31+-- with Cloud Haskell, starting from a brand new Azure account. It is not
32+-- intended as an Azure tutorial, but as a guide to making the right choices to
33+-- get Cloud Haskell up and running as quickly as possible.
34+--
35+-- An Azure /Cloud Service/ is a set of virtual machines that can talk to each
36+-- other directly over TCP/IP (they are part of the same private network). You
37+-- don't create the cloud service directly; instead, after you have set up your
38+-- first virtual machine as a /stand alone/ virtual machine, you can /connect/
39+-- subsequent virtual machines to the first virtual machine, thereby implicitly
40+-- setting up a Cloud Service.
41+--
42+-- We have only tested Cloud Haskell with Linux based virtual machines;
43+-- Windows based virtual machines /might/ work, but you'll be entering
44+-- unchartered territory. Cloud Haskell assumes that all nodes run the same
45+-- binary code; hence, you must use the same OS on all virtual machines,
46+-- /as well as on your local machine/. We use Ubuntu Server 12.04 LTS for our
47+-- tests (running on VirtualBox on our local machine).
48+--
49+-- When you set up your virtual machine, you can pick an arbitrary virtual
50+-- machine name; these names are for your own use only and do not need to be
51+-- globally unique. Set a username and password; you should use the same
52+-- username on all virtual machines. You should also upload an SSH key for
53+-- authentication (see
54+-- /Converting OpenSSH keys for use on Windows Azure Linux VM's/,
55+-- <http://utlemming.azurewebsites.net/?p=91>, for
56+-- information on how to convert a standard Linux @id_rsa.pub@ public key to
57+-- X509 format suitable for Azure). For the first VM you create select
58+-- /Standalone Virtual Machine/, and pick an appropriate DNS name. The DNS name
59+-- /does/ have to be globally unique, and will also be the name of the Cloud
60+-- Service. For subsequent virtual machines, select
61+-- /Connect to Existing Virtual Machine/ instead and then select the first VM
62+-- you created.
63+--
64+-- Once your virtual machines have been set up, you have to make sure that the
65+-- user you created when you created the VM can ssh from any virtual machine to
66+-- any other using public key authentication. Moreover, you have to make sure
67+-- that @libssh2@ is installed; if you are using the Ubuntu image you can
68+-- install @libssh2@ using
69+--
70+-- > sudo apt-get install libssh2-1
71+--
72+-- (TODO: if you don't install libssh2 things will break without a clear error
73+-- message.)
74+--
75+-- In these notes, we assume three virtual machines called @CHDemo1@,
76+-- @CHDemo2@, and @CHDemo3@, all part of a @CloudHaskellDemo@ cloud service.
77+--
78+-- [Obtaining a Management Certificate]
79+--
80+-- Azure authentication is by means of an X509 certificate and corresponding
81+-- private key. /Create management certificates for Linux in Windows Azure/,
82+-- <https://www.windowsazure.com/en-us/manage/linux/common-tasks/manage-certificates/>,
83+-- describes how you can create a management certificate for Azure, download it
84+-- as a @.publishsettings@ file, and extract an @.pfx@ file from it. You cannot
85+-- use this @.pfx@ directly; instead, you will need to extract an X509
86+-- certificate from it and a private key in suitable format. You can use the
87+-- @openssl@ command line tool for both tasks; assuming that you stored the
88+-- @.pfx@ file as @credentials.pfx@, to extract the X509 certificate:
89+--
90+-- > openssl pkcs12 -in credentials.pfx -nokeys -out credentials.x509
91+--
92+-- And to extract the private key:
93+--
94+-- > openssl pkcs12 -in credentials.pfx -nocerts -nodes | openssl rsa -out credentials.private
95+--
96+-- (@openssl pkcs12@ outputs the private key in PKCS#8 format (BEGIN PRIVATE
97+-- KEY), but we need it in PKCS#1 format (BEGIN RSA PRIVATE KEY).
98+--
99+-- [Testing the Setup]
100+--
101+-- Build and install the @distributed-process-azure@ package, making sure to
102+-- pass the @build-demos@ flag to Cabal.
103+--
104+-- > cabal-dev install distributed-process-azure -f build-demos
105+--
106+-- We can the @cloud-haskell-azure-ping@ demo to test our setup:
107+--
108+-- > cloud-haskell-azure-ping list \
109+-- > <<your subscription ID>> \
110+-- > /path/to/credentials.x509 \
111+-- > /path/to/credentials.private
112+--
113+-- (you can find your subscription ID in the @.publishsettings@ file from the previous step).
114+-- If everything went well, this will output something like
115+--
116+-- > Cloud Service "CloudHaskellDemo"
117+-- > VIRTUAL MACHINES
118+-- > Virtual Machine "CHDemo3"
119+-- > IP 10.119.182.127
120+-- > INPUT ENDPOINTS
121+-- > Input endpoint "SSH"
122+-- > Port 50136
123+-- > VIP 168.63.31.38
124+-- > Virtual Machine "CHDemo2"
125+-- > IP 10.59.238.125
126+-- > INPUT ENDPOINTS
127+-- > Input endpoint "SSH"
128+-- > Port 63365
129+-- > VIP 168.63.31.38
130+-- > Virtual Machine "CHDemo1"
131+-- > IP 10.59.224.122
132+-- > INPUT ENDPOINTS
133+-- > Input endpoint "SSH"
134+-- > Port 22
135+-- > VIP 168.63.31.38
136+--
137+-- The IP addresses listed are /internal/ IP addresses; they can be used by the
138+-- virtual machines to talk to each other, but not by the outside world to talk
139+-- to the virtual machines. To do that, you will need to use the VIP (Virtual
140+-- IP) address instead, which you will notice is the same for all virtual
141+-- machines that are part of the cloud service. The corresponding DNS name
142+-- (here @CloudHaskellDemo.cloudapp.net@) will also resolve to this (V)IP
143+-- address. To login to individual machines (through SSH) you will need to use
144+-- the specific port mentioned under INPUT ENDPOINTS.
145+--
146+-- [Overview of the API]
147+--
148+-- The Azure 'Backend' provides low-level functionality for interacting with
149+-- Azure virtual machines. 'findVMs' finds all currently available virtual
150+-- machines; 'copyToVM' copies the executable to a specified VM (recall that
151+-- all VMs, as well as the local machine, are assumed to run the same OS so
152+-- that they can all run the same binary), and 'checkMD5' checks the MD5 hash
153+-- of the executable on a remote machine.
154+--
155+-- 'callOnVM' and 'spawnOnVM' deal with setting up Cloud Haskell nodes.
156+-- 'spawnOnVM' takes a virtual machine and a port number, as well as a
157+-- @RemoteProcess ()@, starts the executable on the remote node, sets up a new
158+-- Cloud Haskell node, and then runs the specified process. The Cloud Haskell
159+-- node will be shut down when the given process terminates. 'RemoteProcess' is
160+-- defined as
161+--
162+-- > type RemoteProcess a = Closure (Backend -> Process a)
163+--
164+-- (If you don't know what a 'Closure' is you should read
165+-- "Control.Distributed.Process.Closure".); the remote process will be supplied
166+-- with an Azure backend initialized with the same parameters. 'spawnOnVM'
167+-- returns once the Cloud Haskell node has been set up.
168+--
169+-- 'callOnVM' is similar to 'spawnOnVM', but it takes a /pair/ of processes:
170+-- one to run on the remote host (on a newly created Cloud Haskell node), and
171+-- one to run on the local machine. In this case, the new Cloud Haskell node
172+-- will be terminated when the /local/ process terminates. 'callOnVM' is useful
173+-- because the remote process and the local process can communicate through a
174+-- set of primitives provided in this module ('localSend', 'localExpect', and
175+-- 'remoteSend' -- there is no 'remoteExpect'; instead the remote process can
176+-- use the standard Cloud Haskell 'expect' primitive).
177+--
178+-- [First Example: Echo]
179+--
180+-- When we run the @cloud-haskell-azure-echo@ demo on our local machine, it
181+-- starts a new Cloud Haskell node on the specified remote virtual machine. It
182+-- then repeatedly waits for input from the user on the local machine, sends
183+-- this to the remote virtual machine which will echo it back, and wait for and
184+-- show the echo.
185+--
186+-- Before you can try it you will first need to copy the executable (for
187+-- example, using scp, although the Azure backend also provides this natively
188+-- in Haskell). Once that's done, you can run the demo as follows:
189+--
190+-- > cloud-haskell-azure-echo \
191+-- > <<subscription ID>> \
192+-- > /path/to/credentials.x509 \
193+-- > /path/to/credentials.private \
194+-- > <<remote username>> \
195+-- > <<cloud service name>> \
196+-- > <<virtual machine name>> \
197+-- > <<port number>>
198+-- > # Everything I type gets echoed back
199+-- > Echo: Everything I type gets echoed back
200+-- > # Until I enter a blank line
201+-- > Echo: Until I enter a blank line
202+-- > #
203+--
204+-- The full @echo@ demo is
205+--
206+-- > {-# LANGUAGE TemplateHaskell #-}
207+-- >
208+-- > import System.IO (hFlush, stdout)
209+-- > import System.Environment (getArgs)
210+-- > import Control.Monad (unless, forever)
211+-- > import Control.Monad.IO.Class (liftIO)
212+-- > import Control.Distributed.Process (Process, expect)
213+-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
214+-- > import Control.Distributed.Process.Backend.Azure
215+-- >
216+-- > echoRemote :: () -> Backend -> Process ()
217+-- > echoRemote () _backend = forever $ do
218+-- > str <- expect
219+-- > remoteSend (str :: String)
220+-- >
221+-- > remotable ['echoRemote]
222+-- >
223+-- > echoLocal :: LocalProcess ()
224+-- > echoLocal = do
225+-- > str <- liftIO $ putStr "# " >> hFlush stdout >> getLine
226+-- > unless (null str) $ do
227+-- > localSend str
228+-- > liftIO $ putStr "Echo: " >> hFlush stdout
229+-- > echo <- localExpect
230+-- > liftIO $ putStrLn echo
231+-- > echoLocal
232+-- >
233+-- > main :: IO ()
234+-- > main = do
235+-- > args <- getArgs
236+-- > case args of
237+-- > "onvm":args' ->
238+-- > -- Pass execution to 'onVmMain' if we are running on the VM
239+-- > -- ('callOnVM' will provide the right arguments)
240+-- > onVmMain __remoteTable args'
241+-- >
242+-- > sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
243+-- > -- Initialize the Azure backend
244+-- > params <- defaultAzureParameters sid x509 pkey
245+-- > let params' = params { azureSshUserName = user }
246+-- > backend <- initializeBackend params' cloudService
247+-- >
248+-- > -- Find the specified virtual machine
249+-- > Just vm <- findNamedVM backend virtualMachine
250+-- >
251+-- > -- Run the echo client proper
252+-- > callOnVM backend vm port $
253+-- > ProcessPair ($(mkClosure 'echoRemote) ())
254+-- > echoLocal
255+--
256+-- The most important part of this code is the last three lines
257+--
258+-- > callOnVM backend vm port $
259+-- > ProcessPair ($(mkClosure 'echoRemote) ())
260+-- > echoLocal
261+--
262+-- 'callOnVM' creats a new Cloud Haskell node on the specified virtual machine,
263+-- then runs @echoRemote@ on the remote machine and @echoLocal@ on the local
264+-- machine.
265+--
266+-- [Second Example: Ping]
267+--
268+-- The second example differs from the @echo@ demo in that it uses both
269+-- 'callOnVM' and 'spawnOnVM'. It uses the latter to
270+-- install a ping server which keeps running in the background; it uses the
271+-- former to run a ping client which sends a request to the ping server and
272+-- outputs the response.
273+--
274+-- As with the @echo@ demo, make sure to copy the executable to the remote server first.
275+-- Once that is done, you can start a ping server on a virtual machine using
276+--
277+-- > cloud-haskell-azure-ping server \
278+-- > <<subscription ID>> \
279+-- > /path/to/credentials.x509 \
280+-- > /path/to/credentials.private \
281+-- > <<remote username>> \
282+-- > <<cloud service name>> \
283+-- > <<virtual machine name>> \
284+-- > <<port number>>
285+--
286+-- As before, when we execute this on our local machine, it starts a new Cloud
287+-- Haskell node on the specified remote virtual machine and then executes the
288+-- ping server. Unlike with the echo example, however, this command will
289+-- terminate once the Cloud Haskell node has been set up, leaving the ping
290+-- server running in the background.
291+--
292+-- Once the ping server is running we can run the ping client:
293+--
294+-- > cloud-haskell-azure-ping client \
295+-- > <<subscription ID>> \
296+-- > /path/to/credentials.x509 \
297+-- > /path/to/credentials.private \
298+-- > <<remote username>> \
299+-- > <<cloud service name>> \
300+-- > <<virtual machine name>> \
301+-- > <<DIFFERENT port number>>
302+-- > Ping server at pid://10.59.224.122:8080:0:2 ok
303+--
304+-- Note that we must pass a different port number, because the client will run
305+-- within its own Cloud Haskell instance.
306+--
307+-- The code for the @ping@ demo is similar to the @echo@ demo, but uses both
308+-- 'callOnVM' and 'spawnOnVM' and demonstrates a way to discover processes (in
309+-- this case, through a PID file).
310+--
311+-- > {-# LANGUAGE TemplateHaskell #-}
312+-- >
313+-- > import System.Environment (getArgs)
314+-- > import Data.Binary (encode, decode)
315+-- > import Control.Monad (forever)
316+-- > import Control.Monad.IO.Class (liftIO)
317+-- > import Control.Exception (try, IOException)
318+-- > import Control.Distributed.Process
319+-- > ( Process
320+-- > , getSelfPid
321+-- > , expect
322+-- > , send
323+-- > , monitor
324+-- > , receiveWait
325+-- > , match
326+-- > , ProcessMonitorNotification(..)
327+-- > )
328+-- > import Control.Distributed.Process.Closure (remotable, mkClosure)
329+-- > import Control.Distributed.Process.Backend.Azure
330+-- > import qualified Data.ByteString.Lazy as BSL (readFile, writeFile)
331+-- >
332+-- > pingServer :: () -> Backend -> Process ()
333+-- > pingServer () _backend = do
334+-- > us <- getSelfPid
335+-- > liftIO $ BSL.writeFile "pingServer.pid" (encode us)
336+-- > forever $ do
337+-- > them <- expect
338+-- > send them ()
339+-- >
340+-- > pingClientRemote :: () -> Backend -> Process ()
341+-- > pingClientRemote () _backend = do
342+-- > mPingServerEnc <- liftIO $ try (BSL.readFile "pingServer.pid")
343+-- > case mPingServerEnc of
344+-- > Left err ->
345+-- > remoteSend $ "Ping server not found: " ++ show (err :: IOException)
346+-- > Right pingServerEnc -> do
347+-- > let pingServerPid = decode pingServerEnc
348+-- > pid <- getSelfPid
349+-- > _ref <- monitor pingServerPid
350+-- > send pingServerPid pid
351+-- > gotReply <- receiveWait
352+-- > [ match (\() -> return True)
353+-- > , match (\(ProcessMonitorNotification {}) -> return False)
354+-- > ]
355+-- > if gotReply
356+-- > then remoteSend $ "Ping server at " ++ show pingServerPid ++ " ok"
357+-- > else remoteSend $ "Ping server at " ++ show pingServerPid ++ " failure"
358+-- >
359+-- > remotable ['pingClientRemote, 'pingServer]
360+-- >
361+-- > pingClientLocal :: LocalProcess ()
362+-- > pingClientLocal = localExpect >>= liftIO . putStrLn
363+-- >
364+-- > main :: IO ()
365+-- > main = do
366+-- > args <- getArgs
367+-- > case args of
368+-- > "onvm":args' ->
369+-- > -- Pass execution to 'onVmMain' if we are running on the VM
370+-- > onVmMain __remoteTable args'
371+-- >
372+-- > "list":sid:x509:pkey:_ -> do
373+-- > -- List all available cloud services
374+-- > -- (useful, but not strictly necessary for the example)
375+-- > params <- defaultAzureParameters sid x509 pkey
376+-- > css <- cloudServices (azureSetup params)
377+-- > mapM_ print css
378+-- >
379+-- > cmd:sid:x509:pkey:user:cloudService:virtualMachine:port:_ -> do
380+-- > -- Initialize the backend and find the right VM
381+-- > params <- defaultAzureParameters sid x509 pkey
382+-- > let params' = params { azureSshUserName = user }
383+-- > backend <- initializeBackend params' cloudService
384+-- > Just vm <- findNamedVM backend virtualMachine
385+-- >
386+-- > -- The same binary can behave as the client or the server,
387+-- > -- depending on the command line arguments
388+-- > case cmd of
389+-- > "server" -> spawnOnVM backend vm port ($(mkClosure 'pingServer) ())
390+-- > "client" -> callOnVM backend vm port $
391+-- > ProcessPair ($(mkClosure 'pingClientRemote) ())
392+-- > pingClientLocal
393+module Control.Distributed.Process.Backend.Azure
394+ ( -- * Initialization
395+ Backend(..)
396+ , AzureParameters(..)
397+ , defaultAzureParameters
398+ , initializeBackend
399+ -- * Utilities
400+ , findNamedVM
401+ -- * On-VM main
402+ , onVmMain
403+ -- * Re-exports from Azure Service Management
404+ , CloudService(..)
405+ , VirtualMachine(..)
406+ , Endpoint(..)
407+ , AzureSetup
408+ , Azure.cloudServices
409+ -- * Remote and local processes
410+ , ProcessPair(..)
411+ , RemoteProcess
412+ , LocalProcess
413+ , localSend
414+ , localExpect
415+ , remoteSend
416+ -- * High-level API
417+ , spawnNodeOnVM
418+ , terminateNode
419+ ) where
420+
421+import Prelude hiding (catch)
422+import System.Environment (getEnv)
423+import System.FilePath ((</>), takeFileName)
424+import System.Environment.Executable (getExecutablePath)
425+import System.IO
426+ ( stdout
427+ , hFlush
428+ , hSetBinaryMode
429+ , stdin
430+ , stdout
431+ , stderr
432+ , Handle
433+ , hClose
434+ )
435+import qualified System.Posix.Process as Posix (forkProcess, createSession)
436+import Data.Maybe (listToMaybe)
437+import Data.Binary (Binary(get, put), encode, decode, getWord8, putWord8)
438+import Data.Digest.Pure.MD5 (md5, MD5Digest)
439+import qualified Data.ByteString as BSS
440+ ( ByteString
441+ , length
442+ , concat
443+ , hPut
444+ , hGet
445+ )
446+import qualified Data.ByteString.Char8 as BSSC (pack)
447+import qualified Data.ByteString.Lazy as BSL
448+ ( ByteString
449+ , readFile
450+ , length
451+ , fromChunks
452+ , toChunks
453+ , hPut
454+ , hGet
455+ )
456+import Data.Typeable (Typeable)
457+import Data.Foldable (forM_)
458+import Control.Applicative ((<$>), (<*>))
459+import Control.Monad (void, when)
460+import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
461+import Control.Exception
462+ ( Exception
463+ , catches
464+ , Handler(Handler)
465+ , throwIO
466+ , SomeException
467+ )
468+import Control.Monad.IO.Class (MonadIO, liftIO)
469+import Control.Concurrent.MVar (MVar, newEmptyMVar, readMVar, putMVar)
470+
471+-- Azure
472+import Network.Azure.ServiceManagement
473+ ( CloudService(..)
474+ , VirtualMachine(..)
475+ , Endpoint(..)
476+ , AzureSetup
477+ )
478+import qualified Network.Azure.ServiceManagement as Azure
479+ ( cloudServices
480+ , azureSetup
481+ , vmSshEndpoint
482+ )
483+
484+-- SSH
485+import qualified Network.SSH.Client.LibSSH2 as SSH
486+ ( withSSH2
487+ , scpSendFile
488+ , withChannelBy
489+ , Session
490+ , readAllChannel
491+ , writeAllChannel
492+ , Channel
493+ )
494+import qualified Network.SSH.Client.LibSSH2.Foreign as SSH
495+ ( openChannelSession
496+ , channelExecute
497+ , writeChannel
498+ , readChannel
499+ , channelSendEOF
500+ )
501+import qualified Network.SSH.Client.LibSSH2.Errors as SSH
502+ ( ErrorCode
503+ , NULL_POINTER
504+ , getLastError
505+ )
506+
507+-- CH
508+import Control.Distributed.Process
509+ ( Process
510+ , Closure
511+ , RemoteTable
512+ , catch
513+ , unClosure
514+ , ProcessId
515+ , getSelfPid
516+ , NodeId
517+ , processNodeId
518+ , register
519+ , expect
520+ , nsendRemote
521+ )
522+import Control.Distributed.Process.Serializable (Serializable)
523+import qualified Control.Distributed.Process.Internal.Types as CH
524+ ( LocalNode
525+ , LocalProcess(processQueue)
526+ , Message
527+ , payloadToMessage
528+ , messageToPayload
529+ , createMessage
530+ )
531+import Control.Distributed.Process.Node
532+ ( runProcess
533+ , forkProcess
534+ , newLocalNode
535+ , initRemoteTable
536+ )
537+import Control.Distributed.Process.Internal.CQueue (CQueue, enqueue)
538+import Network.Transport.TCP (createTransport, defaultTCPParameters)
539+import Network.Transport.Internal (encodeInt32, decodeInt32, prependLength)
540+
541+-- Static
542+import Control.Distributed.Static
543+ ( Static
544+ , registerStatic
545+ , staticClosure
546+ , staticLabel
547+ )
548+import Data.Rank1Dynamic (toDynamic)
549+
550+-- | Azure backend
551+data Backend = Backend {
552+ -- | Find virtual machines
553+ findVMs :: IO [VirtualMachine]
554+ -- | Copy the executable to a virtual machine
555+ , copyToVM :: VirtualMachine -> IO ()
556+ -- | Check the MD5 hash of the remote executable
557+ , checkMD5 :: VirtualMachine -> IO Bool
558+ -- | @runOnVM vm port pp@ starts a new CH node on machine @vm@ and then
559+ -- runs the specified process pair. The CH node will shut down when the
560+ -- /local/ process exists. @callOnVM@ returns the returned by the local
561+ -- process on exit.
562+ , callOnVM :: forall a. VirtualMachine -> String -> ProcessPair a -> IO a
563+ -- | Create a new CH node and run the specified process.
564+ -- The CH node will shut down when the /remote/ process exists. @spawnOnVM@
565+ -- returns as soon as the process has been spawned.
566+ , spawnOnVM :: VirtualMachine -> String -> RemoteProcess () -> IO ProcessId
567+ } deriving (Typeable)
568+
569+-- | Azure connection parameters
570+data AzureParameters = AzureParameters {
571+ azureSetup :: AzureSetup
572+ , azureSshUserName :: FilePath
573+ , azureSshPublicKey :: FilePath
574+ , azureSshPrivateKey :: FilePath
575+ , azureSshPassphrase :: String
576+ , azureSshKnownHosts :: FilePath
577+ , azureSshRemotePath :: FilePath
578+ , azureSshLocalPath :: FilePath
579+ }
580+
581+instance Binary AzureParameters where
582+ put params = do
583+ put (azureSetup params)
584+ put (azureSshUserName params)
585+ put (azureSshPublicKey params)
586+ put (azureSshPrivateKey params)
587+ put (azureSshPassphrase params)
588+ put (azureSshKnownHosts params)
589+ put (azureSshRemotePath params)
590+ put (azureSshLocalPath params)
591+ get =
592+ AzureParameters <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get
593+
594+-- | Create default azure parameters
595+defaultAzureParameters :: String -- ^ Azure subscription ID
596+ -> FilePath -- ^ Path to X509 certificate
597+ -> FilePath -- ^ Path to private key
598+ -> IO AzureParameters
599+defaultAzureParameters sid x509 pkey = do
600+ home <- getEnv "HOME"
601+ user <- getEnv "USER"
602+ self <- getExecutablePath
603+ setup <- Azure.azureSetup sid x509 pkey
604+ return AzureParameters
605+ { azureSetup = setup
606+ , azureSshUserName = user
607+ , azureSshPublicKey = home </> ".ssh" </> "id_rsa.pub"
608+ , azureSshPrivateKey = home </> ".ssh" </> "id_rsa"
609+ , azureSshPassphrase = ""
610+ , azureSshKnownHosts = home </> ".ssh" </> "known_hosts"
611+ , azureSshRemotePath = takeFileName self
612+ , azureSshLocalPath = self
613+ }
614+
615+-- | Initialize the backend
616+initializeBackend :: AzureParameters -- ^ Connection parameters
617+ -> String -- ^ Cloud service name
618+ -> IO Backend
619+initializeBackend params cloudService =
620+ return Backend {
621+ findVMs = apiFindVMs params cloudService
622+ , copyToVM = apiCopyToVM params
623+ , checkMD5 = apiCheckMD5 params
624+ , callOnVM = apiCallOnVM params cloudService
625+ , spawnOnVM = apiSpawnOnVM params cloudService
626+ }
627+
628+-- | Find virtual machines
629+apiFindVMs :: AzureParameters -> String -> IO [VirtualMachine]
630+apiFindVMs params cloudService = do
631+ css <- Azure.cloudServices (azureSetup params)
632+ case filter ((== cloudService) . cloudServiceName) css of
633+ [cs] -> return $ cloudServiceVMs cs
634+ _ -> return []
635+
636+-- | Start a CH node on the given virtual machine
637+apiCopyToVM :: AzureParameters -> VirtualMachine -> IO ()
638+apiCopyToVM params vm =
639+ void . withSSH2 params vm $ \s -> catchSshError s $
640+ SSH.scpSendFile s 0o700 (azureSshLocalPath params) (azureSshRemotePath params)
641+
642+-- | Call a process on a VM
643+apiCallOnVM :: AzureParameters
644+ -> String
645+ -> VirtualMachine
646+ -> String
647+ -> ProcessPair a
648+ -> IO a
649+apiCallOnVM = runOnVM False
650+
651+apiSpawnOnVM :: AzureParameters
652+ -> String
653+ -> VirtualMachine
654+ -> String
655+ -> Closure (Backend -> Process ())
656+ -> IO ProcessId
657+apiSpawnOnVM params cloudService vm port rproc =
658+ runOnVM True params cloudService vm port $
659+ ProcessPair rproc localExpect
660+
661+-- | Internal generalization of 'spawnOnVM' and 'callOnVM'
662+runOnVM :: Bool
663+ -> AzureParameters
664+ -> String
665+ -> VirtualMachine
666+ -> String
667+ -> ProcessPair a
668+ -> IO a
669+runOnVM bg params cloudService vm port ppair =
670+ withSSH2 params vm $ \s -> do
671+ -- TODO: reduce duplication with apiCallOnVM
672+ let exe = "PATH=. " ++ azureSshRemotePath params
673+ ++ " onvm"
674+ ++ " " ++ vmIpAddress vm
675+ ++ " " ++ port
676+ ++ " " ++ cloudService
677+ ++ " " ++ show bg
678+ ++ " 2>&1"
679+ let paramsEnc = encode params
680+ let rprocEnc = encode (ppairRemote ppair)
681+ (status, r) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
682+ SSH.channelExecute ch exe
683+ SSH.writeChannel ch (encodeInt32 (BSL.length rprocEnc))
684+ SSH.writeAllChannel ch rprocEnc
685+ SSH.writeChannel ch (encodeInt32 (BSL.length paramsEnc))
686+ SSH.writeAllChannel ch paramsEnc
687+ runLocalProcess (ppairLocal ppair) ch
688+ if status == 0
689+ then return r
690+ else error "runOnVM: Non-zero exit status" -- This would a bug
691+
692+-- | Check the MD5 hash of the executable on the remote machine
693+apiCheckMD5 :: AzureParameters -> VirtualMachine -> IO Bool
694+apiCheckMD5 params vm = do
695+ hash <- localHash params
696+ withSSH2 params vm $ \s -> do
697+ (r, _) <- SSH.withChannelBy (SSH.openChannelSession s) id $ \ch -> do
698+ SSH.channelExecute ch "md5sum -c --status"
699+ SSH.writeChannel ch . BSSC.pack $ show hash ++ " " ++ azureSshRemotePath params
700+ SSH.channelSendEOF ch
701+ SSH.readAllChannel ch
702+ return (r == 0)
703+
704+withSSH2 :: AzureParameters -> VirtualMachine -> (SSH.Session -> IO a) -> IO a
705+withSSH2 params (Azure.vmSshEndpoint -> Just ep) =
706+ SSH.withSSH2 (azureSshKnownHosts params)
707+ (azureSshPublicKey params)
708+ (azureSshPrivateKey params)
709+ (azureSshPassphrase params)
710+ (azureSshUserName params)
711+ (endpointVip ep)
712+ (read $ endpointPort ep)
713+withSSH2 _ vm =
714+ error $ "withSSH2: No SSH endpoint for virtual machine " ++ vmName vm
715+
716+catchSshError :: SSH.Session -> IO a -> IO a
717+catchSshError s io =
718+ catches io [ Handler handleErrorCode
719+ , Handler handleNullPointer
720+ ]
721+ where
722+ handleErrorCode :: SSH.ErrorCode -> IO a
723+ handleErrorCode _ = do
724+ (_, str) <- SSH.getLastError s
725+ error str
726+
727+ handleNullPointer :: SSH.NULL_POINTER -> IO a
728+ handleNullPointer _ = do
729+ (_, str) <- SSH.getLastError s
730+ error str
731+
732+localHash :: AzureParameters -> IO MD5Digest
733+localHash params = md5 <$> BSL.readFile (azureSshLocalPath params)
734+
735+--------------------------------------------------------------------------------
736+-- Utilities --
737+--------------------------------------------------------------------------------
738+
739+-- | Find a virtual machine with a particular name
740+findNamedVM :: Backend -> String -> IO (Maybe VirtualMachine)
741+findNamedVM backend vm =
742+ listToMaybe . filter ((== vm) . vmName) <$> findVMs backend
743+
744+--------------------------------------------------------------------------------
745+-- Local and remote processes --
746+--------------------------------------------------------------------------------
747+
748+-- | A process pair consists of a remote process and a local process. The local
749+-- process can send messages to the remote process using 'localSend' and wait
750+-- for messages from the remote process using 'localExpect'. The remote process
751+-- can send messages to the local process using 'remoteSend', and wait for
752+-- messages from the local process using the standard Cloud Haskell primitives.
753+--
754+-- See also 'callOnVM'.
755+data ProcessPair a = ProcessPair {
756+ ppairRemote :: RemoteProcess ()
757+ , ppairLocal :: LocalProcess a
758+ }
759+
760+-- | The process to run on the remote node (see 'ProcessPair' and 'callOnVM').
761+type RemoteProcess a = Closure (Backend -> Process a)
762+
763+-- | The process to run on the local node (see 'ProcessPair' and 'callOnVM').
764+newtype LocalProcess a = LocalProcess { unLocalProcess :: ReaderT SSH.Channel IO a }
765+ deriving (Functor, Monad, MonadIO, MonadReader SSH.Channel)
766+
767+runLocalProcess :: LocalProcess a -> SSH.Channel -> IO a
768+runLocalProcess = runReaderT . unLocalProcess
769+
770+-- | Send a messages from the local process to the remote process
771+-- (see 'ProcessPair')
772+localSend :: Serializable a => a -> LocalProcess ()
773+localSend x = LocalProcess $ do
774+ ch <- ask
775+ liftIO $ mapM_ (SSH.writeChannel ch)
776+ . prependLength
777+ . CH.messageToPayload
778+ . CH.createMessage
779+ $ x
780+
781+-- | Wait for a message from the remote process (see 'ProcessPair').
782+-- Note that unlike for the standard Cloud Haskell 'expect' it will result in a
783+-- runtime error if the remote process sends a message of type other than @a@.
784+--
785+-- Since it is relatively easy for the remote process to mess up the
786+-- communication protocol (for instance, by doing a putStr) we ask for the
787+-- length twice, as some sort of sanity check.
788+localExpect :: Serializable a => LocalProcess a
789+localExpect = LocalProcess $ do
790+ ch <- ask
791+ liftIO $ do
792+ isE <- readIntChannel ch
793+ len <- readIntChannel ch
794+ lenAgain <- readIntChannel ch
795+ when (len /= lenAgain) $ throwIO (userError "Internal error: protocol violation (perhaps the remote binary is not installed correctly?)")
796+ msg <- readSizeChannel ch len
797+ if isE /= 0
798+ then error (decode msg)
799+ else return (decode msg)
800+
801+-- | Send a message from the remote process to the local process (see
802+-- 'ProcessPair'). Note that the remote process can use the standard Cloud
803+-- Haskell primitives to /receive/ messages from the local process.
804+remoteSend :: Serializable a => a -> Process ()
805+remoteSend = liftIO . remoteSend'
806+
807+remoteSend' :: Serializable a => a -> IO ()
808+remoteSend' = remoteSendFlagged 0
809+
810+-- | If the remote process encounters an error it can use 'remoteThrow'. This
811+-- will cause the exception to be raised (as a user-exception, not as the
812+-- original type) in the local process (as well as in the remote process).
813+remoteThrow :: Exception e => e -> IO ()
814+remoteThrow e = remoteSendFlagged 1 (show e) >> throwIO e
815+
816+remoteSendFlagged :: Serializable a => Int -> a -> IO ()
817+remoteSendFlagged flags x = do
818+ let enc = encode x
819+ BSS.hPut stdout (encodeInt32 flags)
820+ -- See 'localExpect' for why we send the length twice
821+ BSS.hPut stdout (encodeInt32 (BSL.length enc))
822+ BSS.hPut stdout (encodeInt32 (BSL.length enc))
823+ BSL.hPut stdout enc
824+ hFlush stdout
825+
826+--------------------------------------------------------------------------------
827+-- On-VM main --
828+--------------------------------------------------------------------------------
829+
830+-- | Program main when run on the VM. A typical 'main' function looks like
831+--
832+-- > main :: IO ()
833+-- > main = do
834+-- > args <- getArgs
835+-- > case args of
836+-- > "onvm":args' -> onVmMain __remoteTable args'
837+-- > _ -> -- your normal main
838+onVmMain :: (RemoteTable -> RemoteTable) -> [String] -> IO ()
839+onVmMain rtable [host, port, cloudService, bg] = do
840+ hSetBinaryMode stdin True
841+ hSetBinaryMode stdout True
842+ Just rprocEnc <- getWithLength stdin
843+ Just paramsEnc <- getWithLength stdin
844+ backend <- initializeBackend (decode paramsEnc) cloudService
845+ let rproc = decode rprocEnc
846+ lprocMVar <- newEmptyMVar :: IO (MVar CH.LocalProcess)
847+ if read bg
848+ then
849+ void . Posix.forkProcess $ do
850+ -- We inherit the file descriptors from the parent, so the SSH
851+ -- session will not be terminated until we close them
852+ void Posix.createSession
853+ startCH rproc lprocMVar backend
854+ (\node proc -> runProcess node $ do
855+ us <- getSelfPid
856+ liftIO $ do
857+ remoteSend' us
858+ mapM_ hClose [stdin, stdout, stderr]
859+ proc)
860+ else do
861+ startCH rproc lprocMVar backend forkProcess
862+ lproc <- readMVar lprocMVar
863+ queueFromHandle stdin (CH.processQueue lproc)
864+ where
865+ startCH :: RemoteProcess ()
866+ -> MVar CH.LocalProcess
867+ -> Backend
868+ -> (CH.LocalNode -> Process () -> IO a)
869+ -> IO ()
870+ startCH rproc lprocMVar backend go = do
871+ mTransport <- createTransport host port defaultTCPParameters
872+ case mTransport of
873+ Left err -> remoteThrow err
874+ Right transport -> do
875+ node <- newLocalNode transport (rtable . __remoteTable $ initRemoteTable)
876+ void . go node $ do
877+ ask >>= liftIO . putMVar lprocMVar
878+ proc <- unClosure rproc :: Process (Backend -> Process ())
879+ catch (proc backend)
880+ (liftIO . (remoteThrow :: SomeException -> IO ()))
881+onVmMain _ _
882+ = error "Invalid arguments passed on onVmMain"
883+
884+-- | Read a 4-byte length @l@ and then an @l@-byte payload
885+--
886+-- Returns Nothing on EOF
887+getWithLength :: Handle -> IO (Maybe BSL.ByteString)
888+getWithLength h = do
889+ lenEnc <- BSS.hGet h 4
890+ if BSS.length lenEnc < 4
891+ then return Nothing
892+ else do
893+ let len = decodeInt32 lenEnc
894+ bs <- BSL.hGet h len
895+ if BSL.length bs < fromIntegral len
896+ then return Nothing
897+ else return (Just bs)
898+
899+queueFromHandle :: Handle -> CQueue CH.Message -> IO ()
900+queueFromHandle h q = do
901+ mPayload <- getWithLength stdin
902+ forM_ mPayload $ \payload -> do
903+ enqueue q $ CH.payloadToMessage (BSL.toChunks payload)
904+ queueFromHandle h q
905+
906+--------------------------------------------------------------------------------
907+-- SSH utilities --
908+--------------------------------------------------------------------------------
909+
910+readSizeChannel :: SSH.Channel -> Int -> IO BSL.ByteString
911+readSizeChannel ch = go []
912+ where
913+ go :: [BSS.ByteString] -> Int -> IO BSL.ByteString
914+ go acc 0 = return (BSL.fromChunks $ reverse acc)
915+ go acc size = do
916+ bs <- SSH.readChannel ch (fromIntegral (0x400 `min` size))
917+ go (bs : acc) (size - BSS.length bs)
918+
919+readIntChannel :: SSH.Channel -> IO Int
920+readIntChannel ch =
921+ decodeInt32 . BSS.concat . BSL.toChunks <$> readSizeChannel ch 4
922+
923+--------------------------------------------------------------------------------
924+-- High-level API --
925+--------------------------------------------------------------------------------
926+
927+data ServiceProcessMsg =
928+ ServiceProcessTerminate
929+ deriving Typeable
930+
931+instance Binary ServiceProcessMsg where
932+ put ServiceProcessTerminate = putWord8 0
933+ get = do
934+ header <- getWord8
935+ case header of
936+ 0 -> return ServiceProcessTerminate
937+ _ -> fail "ServiceProcessMsg.get"
938+
939+serviceProcess :: Backend -> Process ()
940+serviceProcess _backend = do
941+ us <- getSelfPid
942+ register "$azureBackendServiceProcess" us
943+ go
944+ where
945+ go = do
946+ msg <- expect
947+ case msg of
948+ ServiceProcessTerminate ->
949+ return ()
950+
951+serviceProcessStatic :: Static (Backend -> Process ())
952+serviceProcessStatic = staticLabel "serviceProcess"
953+
954+-- | Start a new Cloud Haskell node on the given virtual machine
955+spawnNodeOnVM :: Backend -> VirtualMachine -> String -> IO NodeId
956+spawnNodeOnVM backend vm port =
957+ processNodeId <$> spawnOnVM backend vm port (staticClosure serviceProcessStatic)
958+
959+-- | Terminate a node started with 'spawnNodeOnVM'
960+terminateNode :: NodeId -> Process ()
961+terminateNode nid = nsendRemote nid "$azureBackendServiceProcess" ServiceProcessTerminate
962+
963+__remoteTable :: RemoteTable -> RemoteTable
964+__remoteTable = registerStatic "serviceProcess" (toDynamic serviceProcess)
distributed-process-demos/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2012, Edsko de Vries
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Edsko de Vries nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
distributed-process-demos/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-process-demos/distributed-process-demos.cabalnew file+109
@@ -0,0 +1,109 @@
1+-- Initial distributed-process-demos.cabal generated by cabal init. For
2+-- further documentation, see http://haskell.org/cabal/users-guide/
3+
4+name: distributed-process-demos
5+version: 0.1.0.0
6+synopsis: Cloud Haskell Demo Applications
7+-- description:
8+homepage: http://github.com/haskell-distributed/distributed-process
9+license: BSD3
10+license-file: LICENSE
11+author: Edsko de Vries
12+maintainer: edsko@well-typed.com
13+copyright: Well-Typed LLP
14+category: Control
15+build-type: Simple
16+cabal-version: >=1.8
17+
18+flag use-simplelocalnet
19+ description: Use the SimpleLocalnet backend
20+ default: True
21+
22+flag use-azure
23+ description: Use the Azure backend
24+ default: True
25+
26+executable distributed-process-simplelocalnet-masterslave
27+ main-is: SimpleLocalnet.hs
28+ hs-source-dirs: src/MasterSlave
29+ build-depends: base ==4.5.*,
30+ distributed-process >= 0.3.2
31+ extensions: BangPatterns, CPP, TemplateHaskell
32+ ghc-options: -Wall
33+ if flag(use-simplelocalnet)
34+ build-depends: distributed-process-simplelocalnet
35+ cpp-options: -DUSE_SIMPLELOCALNET
36+ else
37+ buildable: False
38+
39+executable distributed-process-azure-masterslave
40+ main-is: Azure.hs
41+ hs-source-dirs: src/MasterSlave
42+ build-depends: base ==4.5.*,
43+ distributed-process >= 0.3.2
44+ extensions: BangPatterns, CPP, TemplateHaskell
45+ ghc-options: -Wall
46+ if flag(use-azure)
47+ build-depends: distributed-process-azure
48+ cpp-options: -DUSE_AZURE
49+ else
50+ buildable: False
51+
52+executable distributed-process-simplelocalnet-workpushing
53+ main-is: SimpleLocalnet.hs
54+ hs-source-dirs: src/WorkPushing
55+ build-depends: base ==4.5.*,
56+ distributed-process >= 0.3.2
57+ extensions: BangPatterns, CPP, TemplateHaskell
58+ ghc-options: -Wall
59+ if flag(use-simplelocalnet)
60+ build-depends: distributed-process-simplelocalnet
61+ cpp-options: -DUSE_SIMPLELOCALNET
62+ else
63+ buildable: False
64+
65+executable distributed-process-azure-workpushing
66+ main-is: Azure.hs
67+ hs-source-dirs: src/WorkPushing
68+ build-depends: base ==4.5.*,
69+ distributed-process >= 0.3.2
70+ extensions: BangPatterns, CPP, TemplateHaskell
71+ ghc-options: -Wall
72+ if flag(use-azure)
73+ build-depends: distributed-process-azure
74+ cpp-options: -DUSE_AZURE
75+ else
76+ buildable: False
77+
78+executable distributed-process-simplelocalnet-workstealing
79+ main-is: SimpleLocalnet.hs
80+ hs-source-dirs: src/WorkStealing
81+ build-depends: base ==4.5.*,
82+ distributed-process >= 0.3.2
83+ extensions: BangPatterns, CPP, TemplateHaskell
84+ ghc-options: -Wall
85+ if flag(use-simplelocalnet)
86+ build-depends: distributed-process-simplelocalnet
87+ cpp-options: -DUSE_SIMPLELOCALNET
88+ else
89+ buildable: False
90+
91+executable distributed-process-simplelocalnet-mapreduce
92+ main-is: SimpleLocalnet.hs
93+ hs-source-dirs: src/MapReduce
94+ build-depends: base ==4.5.*,
95+ distributed-process >= 0.3.2,
96+ distributed-static >= 0.2.0 && < 0.3,
97+ containers >= 0.4 && < 0.6,
98+ bytestring >= 0.9 && < 0.11,
99+ binary >= 0.5 && < 0.6,
100+ array >= 0.4 && < 0.5,
101+ random >= 1.0 && < 1.1
102+ extensions: BangPatterns, CPP, TemplateHaskell, ScopedTypeVariables
103+ ghc-options: -Wall
104+ if flag(use-simplelocalnet)
105+ build-depends: distributed-process-simplelocalnet
106+ cpp-options: -DUSE_SIMPLELOCALNET
107+ else
108+ buildable: False
109+
distributed-process-demos/src/MapReduce/CountWords.hsnew file+43
@@ -0,0 +1,43 @@
1+{-# LANGUAGE TupleSections #-}
2+module CountWords
3+ ( Document
4+ , localCountWords
5+ , distrCountWords
6+ , __remoteTable
7+ ) where
8+
9+import Control.Distributed.Process
10+import Control.Distributed.Process.Closure
11+import MapReduce hiding (__remoteTable)
12+
13+type Document = String
14+type Word = String
15+type Frequency = Int
16+
17+countWords :: MapReduce FilePath Document Word Frequency Frequency
18+countWords = MapReduce {
19+ mrMap = const (map (, 1) . words)
20+ , mrReduce = const sum
21+ }
22+
23+localCountWords :: Map FilePath Document -> Map Word Frequency
24+localCountWords = localMapReduce countWords
25+
26+dictIn :: SerializableDict (FilePath, Document)
27+dictIn = SerializableDict
28+
29+dictOut :: SerializableDict [(Word, Frequency)]
30+dictOut = SerializableDict
31+
32+countWords_ :: () -> MapReduce FilePath Document Word Frequency Frequency
33+countWords_ () = countWords
34+
35+remotable ['dictIn, 'dictOut, 'countWords_]
36+
37+distrCountWords :: [NodeId] -> Map FilePath Document -> Process (Map Word Frequency)
38+distrCountWords mappers input =
39+ distrMapReduce $(mkStatic 'dictIn)
40+ $(mkStatic 'dictOut)
41+ ($(mkClosure 'countWords_) ())
42+ mappers
43+ (\iteration -> iteration input)
distributed-process-demos/src/MapReduce/KMeans.hsnew file+126
@@ -0,0 +1,126 @@
1+{-# LANGUAGE TupleSections #-}
2+module KMeans
3+ ( Point
4+ , Cluster
5+ , localKMeans
6+ , distrKMeans
7+ , createGnuPlot
8+ , __remoteTable
9+ ) where
10+
11+import System.IO
12+import Data.List (minimumBy)
13+import Data.Function (on)
14+import Data.Array (Array, (!), bounds)
15+import qualified Data.Map as Map (fromList, elems, toList, size)
16+import Control.Distributed.Process
17+import Control.Distributed.Process.Closure
18+import MapReduce hiding (__remoteTable)
19+
20+type Point = (Double, Double)
21+type Cluster = (Double, Double)
22+
23+average :: Fractional a => [a] -> a
24+average xs = sum xs / fromIntegral (length xs)
25+
26+distanceSq :: Point -> Point -> Double
27+distanceSq (x1, y1) (x2, y2) = a * a + b * b
28+ where
29+ a = x2 - x1
30+ b = y2 - y1
31+
32+nearest :: Point -> [Cluster] -> Cluster
33+nearest p = minimumBy (compare `on` distanceSq p)
34+
35+center :: [Point] -> Point
36+center ps = let (xs, ys) = unzip ps in (average xs, average ys)
37+
38+kmeans :: Array Int Point -> MapReduce (Int, Int) [Cluster] Cluster Point ([Point], Point)
39+kmeans points = MapReduce {
40+ mrMap = \(lo, hi) cs -> [ let p = points ! i in (nearest p cs, p)
41+ | i <- [lo .. hi]
42+ ]
43+ , mrReduce = \_ ps -> (ps, center ps)
44+ }
45+
46+localKMeans :: Array Int Point
47+ -> [Cluster]
48+ -> Int
49+ -> Map Cluster ([Point], Point)
50+localKMeans points cs iterations = go (iterations - 1)
51+ where
52+ mr :: [Cluster] -> Map Cluster ([Point], Point)
53+ mr = localMapReduce (kmeans points) . trivialSegmentation
54+
55+ go :: Int -> Map Cluster ([Point], Point)
56+ go 0 = mr cs
57+ go n = mr . map snd . Map.elems . go $ n - 1
58+
59+ trivialSegmentation :: [Cluster] -> Map (Int, Int) [Cluster]
60+ trivialSegmentation cs' = Map.fromList [(bounds points, cs')]
61+
62+dictIn :: SerializableDict ((Int, Int), [Cluster])
63+dictIn = SerializableDict
64+
65+dictOut :: SerializableDict [(Cluster, Point)]
66+dictOut = SerializableDict
67+
68+remotable ['kmeans, 'dictIn, 'dictOut]
69+
70+distrKMeans :: Array Int Point
71+ -> [Cluster]
72+ -> [NodeId]
73+ -> Int
74+ -> Process (Map Cluster ([Point], Point))
75+distrKMeans points cs mappers iterations =
76+ distrMapReduce $(mkStatic 'dictIn)
77+ $(mkStatic 'dictOut)
78+ ($(mkClosure 'kmeans) points)
79+ mappers
80+ (go (iterations - 1))
81+ where
82+ go :: Int
83+ -> (Map (Int, Int) [Cluster] -> Process (Map Cluster ([Point], Point)))
84+ -> Process (Map Cluster ([Point], Point))
85+ go 0 iteration =
86+ iteration (Map.fromList $ map (, cs) segments)
87+ go n iteration = do
88+ clusters <- go (n - 1) iteration
89+ let centers = map snd $ Map.elems clusters
90+ iteration (Map.fromList $ map (, centers) segments)
91+
92+ segments :: [(Int, Int)]
93+ segments = let (lo, _) = bounds points in dividePoints numPoints lo
94+
95+ dividePoints :: Int -> Int -> [(Int, Int)]
96+ dividePoints pointsLeft offset
97+ | pointsLeft <= pointsPerMapper = [(offset, offset + pointsLeft - 1)]
98+ | otherwise = let offset' = offset + pointsPerMapper in
99+ (offset, offset' - 1)
100+ : dividePoints (pointsLeft - pointsPerMapper) offset'
101+
102+ pointsPerMapper :: Int
103+ pointsPerMapper =
104+ ceiling (toRational numPoints / toRational (length mappers))
105+
106+ numPoints :: Int
107+ numPoints = let (lo, hi) = bounds points in hi - lo + 1
108+
109+-- | Create a gnuplot data file for the output of the k-means algorithm
110+--
111+-- To plot the data, use
112+--
113+-- > plot "<<filename>>" u 1:2:3 with points palette
114+createGnuPlot :: Map KMeans.Cluster ([KMeans.Point], KMeans.Point) -> Handle -> IO ()
115+createGnuPlot clusters h =
116+ mapM_ printPoint . flatten . zip colors . Map.toList $ clusters
117+ where
118+ printPoint (x, y, color) =
119+ hPutStrLn h $ show x ++ " " ++ show y ++ " " ++ show color
120+
121+ flatten :: [(Float, (KMeans.Cluster, ([KMeans.Point], KMeans.Point)))]
122+ -> [(Double, Double, Float)]
123+ flatten = concatMap (\(color, (_, (points, _))) -> map (\(x, y) -> (x, y, color)) points)
124+
125+ colors :: [Float]
126+ colors = [0, 1 / fromIntegral (Map.size clusters) .. 1]
distributed-process-demos/src/MapReduce/MapReduce.hsnew file+162
@@ -0,0 +1,162 @@
1+{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable, GADTs #-}
2+module MapReduce
3+ ( -- * Map-reduce skeleton and implementation
4+ MapReduce(..)
5+ , localMapReduce
6+ , distrMapReduce
7+ , __remoteTable
8+ -- * Re-exports from Data.Map
9+ , Map
10+ ) where
11+
12+import Data.Typeable (Typeable)
13+import Data.Binary (encode)
14+import Data.ByteString.Lazy (ByteString)
15+import Data.Map (Map)
16+import qualified Data.Map as Map (mapWithKey, fromListWith, toList, size)
17+import Control.Arrow (second)
18+import Control.Monad (forM_, replicateM, replicateM_)
19+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
20+import qualified Control.Concurrent.Chan as Chan
21+import Control.Distributed.Process
22+import Control.Distributed.Process.Serializable (Serializable)
23+import Control.Distributed.Process.Closure
24+import Control.Distributed.Static (closureApply, staticCompose, staticApply)
25+
26+--------------------------------------------------------------------------------
27+-- Definition of a MapReduce skeleton and a local implementation --
28+--------------------------------------------------------------------------------
29+
30+-- | MapReduce skeleton
31+data MapReduce k1 v1 k2 v2 v3 = MapReduce {
32+ mrMap :: k1 -> v1 -> [(k2, v2)]
33+ , mrReduce :: k2 -> [v2] -> v3
34+ } deriving (Typeable)
35+
36+-- | Local (non-distributed) implementation of the map-reduce algorithm
37+--
38+-- This can be regarded as the specification of map-reduce; see
39+-- /Google’s MapReduce Programming Model---Revisited/ by Ralf Laemmel
40+-- (<http://userpages.uni-koblenz.de/~laemmel/MapReduce/>).
41+localMapReduce :: forall k1 k2 v1 v2 v3. Ord k2 =>
42+ MapReduce k1 v1 k2 v2 v3
43+ -> Map k1 v1
44+ -> Map k2 v3
45+localMapReduce mr = reducePerKey mr . groupByKey . mapPerKey mr
46+
47+reducePerKey :: MapReduce k1 v1 k2 v2 v3 -> Map k2 [v2] -> Map k2 v3
48+reducePerKey mr = Map.mapWithKey (mrReduce mr)
49+
50+groupByKey :: Ord k2 => [(k2, v2)] -> Map k2 [v2]
51+groupByKey = Map.fromListWith (++) . map (second return)
52+
53+mapPerKey :: MapReduce k1 v1 k2 v2 v3 -> Map k1 v1 -> [(k2, v2)]
54+mapPerKey mr = concatMap (uncurry (mrMap mr)) . Map.toList
55+
56+--------------------------------------------------------------------------------
57+-- Simple distributed implementation --
58+--------------------------------------------------------------------------------
59+
60+matchDict :: forall a b. SerializableDict a -> (a -> Process b) -> Match b
61+matchDict SerializableDict = match
62+
63+sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
64+sendDict SerializableDict = send
65+
66+sdictProcessIdPair :: SerializableDict (ProcessId, ProcessId)
67+sdictProcessIdPair = SerializableDict
68+
69+mapperProcess :: forall k1 v1 k2 v2 v3.
70+ SerializableDict (k1, v1)
71+ -> SerializableDict [(k2, v2)]
72+ -> (ProcessId, ProcessId)
73+ -> MapReduce k1 v1 k2 v2 v3
74+ -> Process ()
75+mapperProcess dictIn dictOut (master, workQueue) mr = getSelfPid >>= go
76+ where
77+ go us = do
78+ -- Ask the queue for work
79+ send workQueue us
80+
81+ -- Wait for a reply; if there is work, do it and repeat; otherwise, exit
82+ receiveWait
83+ [ matchDict dictIn $ \(key, val) -> do
84+ sendDict dictOut master (mrMap mr key val)
85+ go us
86+ , match $ \() ->
87+ return ()
88+ ]
89+
90+remotable ['mapperProcess, 'sdictProcessIdPair]
91+
92+mapperProcessClosure :: forall k1 v1 k2 v2 v3.
93+ (Typeable k1, Typeable v1, Typeable k2, Typeable v2, Typeable v3)
94+ => Static (SerializableDict (k1, v1))
95+ -> Static (SerializableDict [(k2, v2)])
96+ -> Closure (MapReduce k1 v1 k2 v2 v3)
97+ -> ProcessId
98+ -> ProcessId
99+ -> Closure (Process ())
100+mapperProcessClosure dictIn dictOut mr master workQueue =
101+ closure decoder (encode (master, workQueue)) `closureApply` mr
102+ where
103+ decoder :: Static (ByteString -> MapReduce k1 v1 k2 v2 v3 -> Process ())
104+ decoder =
105+ ($(mkStatic 'mapperProcess) `staticApply` dictIn `staticApply` dictOut)
106+ `staticCompose`
107+ staticDecode $(mkStatic 'sdictProcessIdPair)
108+
109+distrMapReduce :: forall k1 k2 v1 v2 v3 a.
110+ (Serializable k1, Serializable v1, Serializable k2, Serializable v2, Serializable v3, Ord k2)
111+ => Static (SerializableDict (k1, v1))
112+ -> Static (SerializableDict [(k2, v2)])
113+ -> Closure (MapReduce k1 v1 k2 v2 v3)
114+ -> [NodeId]
115+ -> ((Map k1 v1 -> Process (Map k2 v3)) -> Process a)
116+ -> Process a
117+distrMapReduce dictIn dictOut mr mappers p = do
118+ mr' <- unClosure mr
119+ us <- getSelfPid
120+ queue <- liftIO Chan.newChan
121+ slavesTerminated <- liftIO newEmptyMVar
122+
123+ workQueue <- spawnLocal $ do
124+ let go :: Process ()
125+ go = do
126+ mWork <- liftIO $ Chan.readChan queue
127+ case mWork of
128+ Just (key, val) -> do
129+ -- As long there is work, make it available to the mappers
130+ them <- expect
131+ send them (key, val)
132+ go
133+ Nothing -> do
134+ -- Tell the mappers to terminate
135+ replicateM_ (length mappers) $ do
136+ them <- expect
137+ send them ()
138+ liftIO $ putMVar slavesTerminated ()
139+ go
140+
141+ -- Start the mappers
142+ forM_ mappers $ \nid -> spawn nid (mapperProcessClosure dictIn dictOut mr us workQueue)
143+
144+ let iteration :: Map k1 v1 -> Process (Map k2 v3)
145+ iteration input = do
146+ -- Make work available to the mappers
147+ liftIO $ mapM_ (Chan.writeChan queue . Just) (Map.toList input)
148+
149+ -- Wait for the partial results
150+ partials <- replicateM (Map.size input) expect
151+
152+ -- We reduce on this node
153+ return (reducePerKey mr' . groupByKey . concat $ partials)
154+
155+ result <- p iteration
156+
157+ -- Terminate the wrappers
158+ liftIO $ do
159+ Chan.writeChan queue Nothing
160+ takeMVar slavesTerminated
161+
162+ return result
distributed-process-demos/src/MapReduce/SimpleLocalnet.hsnew file+73
@@ -0,0 +1,73 @@
1+import System.Environment (getArgs)
2+import System.IO
3+import Control.Applicative
4+import Control.Monad
5+import System.Random
6+import Control.Distributed.Process
7+import Control.Distributed.Process.Node (initRemoteTable)
8+import Control.Distributed.Process.Backend.SimpleLocalnet
9+import Data.Map (Map)
10+import Data.Array (Array, listArray)
11+import qualified Data.Map as Map (fromList)
12+
13+import qualified CountWords
14+import qualified MapReduce
15+import qualified KMeans
16+
17+rtable :: RemoteTable
18+rtable = MapReduce.__remoteTable
19+ . CountWords.__remoteTable
20+ . KMeans.__remoteTable
21+ $ initRemoteTable
22+
23+main :: IO ()
24+main = do
25+ args <- getArgs
26+
27+ case args of
28+ -- Local word count
29+ "local" : "count" : files -> do
30+ input <- constructInput files
31+ print $ CountWords.localCountWords input
32+
33+ -- Distributed word count
34+ "master" : "count" : host : port : files -> do
35+ input <- constructInput files
36+ backend <- initializeBackend host port rtable
37+ startMaster backend $ \slaves -> do
38+ result <- CountWords.distrCountWords slaves input
39+ liftIO $ print result
40+
41+ -- Local k-means
42+ "local" : "kmeans" : [] -> do
43+ points <- replicateM 1000 randomPoint
44+ withFile "plot.data" WriteMode $ KMeans.createGnuPlot $
45+ KMeans.localKMeans (arrayFromList points) (take 5 points) 5
46+
47+ -- Distributed k-means
48+ "master" : "kmeans" : host : port : [] -> do
49+ points <- replicateM 1000 randomPoint
50+ backend <- initializeBackend host port rtable
51+ startMaster backend $ \slaves -> do
52+ result <- KMeans.distrKMeans (arrayFromList points) (take 5 points) slaves 5
53+ liftIO $ withFile "plot.data" WriteMode $ KMeans.createGnuPlot result
54+
55+ -- Generic slave for distributed examples
56+ "slave" : host : port : [] -> do
57+ backend <- initializeBackend host port rtable
58+ startSlave backend
59+
60+--------------------------------------------------------------------------------
61+-- Auxiliary --
62+--------------------------------------------------------------------------------
63+
64+constructInput :: [FilePath] -> IO (Map FilePath CountWords.Document)
65+constructInput files = do
66+ contents <- mapM readFile files
67+ return . Map.fromList $ zip files contents
68+
69+randomPoint :: IO KMeans.Point
70+randomPoint = (,) <$> randomIO <*> randomIO
71+
72+arrayFromList :: [e] -> Array Int e
73+arrayFromList xs = listArray (0, length xs - 1) xs
distributed-process-demos/src/MasterSlave/Azure.hsnew file+37
@@ -0,0 +1,37 @@
1+import System.Environment (getArgs)
2+import Control.Monad
3+import Control.Distributed.Process
4+import Control.Distributed.Process.Closure
5+import Control.Distributed.Process.Backend.Azure
6+import qualified MasterSlave
7+
8+azureMaster :: ([NodeId], Integer) -> Backend -> Process ()
9+azureMaster (slaves, n) _backend = do
10+ result <- MasterSlave.master n slaves
11+ mapM_ terminateNode slaves
12+ remoteSend result
13+
14+remotable ['azureMaster]
15+
16+printResult :: LocalProcess ()
17+printResult = do
18+ result <- localExpect :: LocalProcess Integer
19+ liftIO $ print result
20+
21+main :: IO ()
22+main = do
23+ args <- getArgs
24+ case args of
25+ "onvm":args' -> onVmMain (__remoteTable . MasterSlave.__remoteTable) args'
26+ [sid, x509, pkey, user, cloudService, n] -> do
27+ params <- defaultAzureParameters sid x509 pkey
28+ let params' = params { azureSshUserName = user }
29+ backend <- initializeBackend params' cloudService
30+ vms <- findVMs backend
31+ print vms
32+ nids <- forM vms $ \vm -> spawnNodeOnVM backend vm "8080"
33+ callOnVM backend (head vms) "8081" $
34+ ProcessPair ($(mkClosure 'azureMaster) (nids, read n :: Integer))
35+ printResult
36+ _ ->
37+ error "Invalid command line arguments"
distributed-process-demos/src/MasterSlave/MasterSlave.hsnew file+30
@@ -0,0 +1,30 @@
1+module MasterSlave where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+
7+fib :: Integer -> Integer
8+fib = go (0, 1)
9+ where
10+ go (!a, !b) !n | n == 0 = a
11+ | otherwise = go (b, a + b) (n - 1)
12+
13+slave :: (ProcessId, Integer) -> Process ()
14+slave (pid, n) = send pid (fib n)
15+
16+remotable ['slave]
17+
18+master :: Integer -> [NodeId] -> Process Integer
19+master n slaves = do
20+ us <- getSelfPid
21+
22+ -- Distribute 1 .. n amongst the slave processes
23+ forM_ (zip [1 .. n] (cycle slaves)) $ \(m, them) ->
24+ spawn them ($(mkClosure 'slave) (us, m))
25+
26+ -- Wait for the result
27+ partials <- replicateM (fromIntegral n) (expect :: Process Integer)
28+
29+ -- And return the sum
30+ return (sum partials)
distributed-process-demos/src/MasterSlave/SimpleLocalnet.hsnew file+22
@@ -0,0 +1,22 @@
1+import System.Environment (getArgs)
2+import Control.Distributed.Process
3+import Control.Distributed.Process.Node (initRemoteTable)
4+import Control.Distributed.Process.Backend.SimpleLocalnet
5+import qualified MasterSlave
6+
7+rtable :: RemoteTable
8+rtable = MasterSlave.__remoteTable initRemoteTable
9+
10+main :: IO ()
11+main = do
12+ args <- getArgs
13+
14+ case args of
15+ ["master", host, port, n] -> do
16+ backend <- initializeBackend host port rtable
17+ startMaster backend $ \slaves -> do
18+ result <- MasterSlave.master (read n) slaves
19+ liftIO $ print result
20+ ["slave", host, port] -> do
21+ backend <- initializeBackend host port rtable
22+ startSlave backend
distributed-process-demos/src/WorkPushing/Azure.hsnew file+37
@@ -0,0 +1,37 @@
1+import System.Environment (getArgs)
2+import Control.Monad
3+import Control.Distributed.Process
4+import Control.Distributed.Process.Closure
5+import Control.Distributed.Process.Backend.Azure
6+import qualified MasterSlave
7+
8+azureMaster :: ([NodeId], Integer) -> Backend -> Process ()
9+azureMaster (slaves, n) _backend = do
10+ result <- MasterSlave.master n slaves
11+ mapM_ terminateNode slaves
12+ remoteSend result
13+
14+remotable ['azureMaster]
15+
16+printResult :: LocalProcess ()
17+printResult = do
18+ result <- localExpect :: LocalProcess Integer
19+ liftIO $ print result
20+
21+main :: IO ()
22+main = do
23+ args <- getArgs
24+ case args of
25+ "onvm":args' -> onVmMain (__remoteTable . MasterSlave.__remoteTable) args'
26+ [sid, x509, pkey, user, cloudService, n] -> do
27+ params <- defaultAzureParameters sid x509 pkey
28+ let params' = params { azureSshUserName = user }
29+ backend <- initializeBackend params' cloudService
30+ vms <- findVMs backend
31+ print vms
32+ nids <- forM vms $ \vm -> spawnNodeOnVM backend vm "8080"
33+ callOnVM backend (head vms) "8081" $
34+ ProcessPair ($(mkClosure 'azureMaster) (nids, read n :: Integer))
35+ printResult
36+ _ ->
37+ error "Invalid command line arguments"
distributed-process-demos/src/WorkPushing/SimpleLocalnet.hsnew file+22
@@ -0,0 +1,22 @@
1+import System.Environment (getArgs)
2+import Control.Distributed.Process
3+import Control.Distributed.Process.Node (initRemoteTable)
4+import Control.Distributed.Process.Backend.SimpleLocalnet
5+import qualified WorkPushing
6+
7+rtable :: RemoteTable
8+rtable = WorkPushing.__remoteTable initRemoteTable
9+
10+main :: IO ()
11+main = do
12+ args <- getArgs
13+
14+ case args of
15+ ["master", host, port, n] -> do
16+ backend <- initializeBackend host port rtable
17+ startMaster backend $ \slaves -> do
18+ result <- WorkPushing.master (read n) slaves
19+ liftIO $ print result
20+ ["slave", host, port] -> do
21+ backend <- initializeBackend host port rtable
22+ startSlave backend
distributed-process-demos/src/WorkPushing/WorkPushing.hsnew file+34
@@ -0,0 +1,34 @@
1+module WorkPushing where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+
7+fib :: Integer -> Integer
8+fib = go (0, 1)
9+ where
10+ go (!a, !b) !n | n == 0 = a
11+ | otherwise = go (b, a + b) (n - 1)
12+
13+slave :: ProcessId -> Process ()
14+slave them = forever $ do
15+ n <- expect
16+ send them (fib n)
17+
18+remotable ['slave]
19+
20+master :: Integer -> [NodeId] -> Process Integer
21+master n slaves = do
22+ us <- getSelfPid
23+
24+ -- Start processes on the slaves that compute Fibonacci numbers
25+ slaveProcesses <- forM slaves $ \nid -> spawn nid ($(mkClosure 'slave) us)
26+
27+ -- Distribute 1 .. n amongst the slave processes
28+ forM_ (zip [1 .. n] (cycle slaveProcesses)) $ \(m, them) -> send them m
29+
30+ -- Wait for the result
31+ partials <- replicateM (fromIntegral n) (expect :: Process Integer)
32+
33+ -- And return the sum
34+ return (sum partials)
distributed-process-demos/src/WorkStealing/SimpleLocalnet.hsnew file+22
@@ -0,0 +1,22 @@
1+import System.Environment (getArgs)
2+import Control.Distributed.Process
3+import Control.Distributed.Process.Node (initRemoteTable)
4+import Control.Distributed.Process.Backend.SimpleLocalnet
5+import qualified WorkStealing
6+
7+rtable :: RemoteTable
8+rtable = WorkStealing.__remoteTable initRemoteTable
9+
10+main :: IO ()
11+main = do
12+ args <- getArgs
13+
14+ case args of
15+ ["master", host, port, n] -> do
16+ backend <- initializeBackend host port rtable
17+ startMaster backend $ \slaves -> do
18+ result <- WorkStealing.master (read n) slaves
19+ liftIO $ print result
20+ ["slave", host, port] -> do
21+ backend <- initializeBackend host port rtable
22+ startSlave backend
distributed-process-demos/src/WorkStealing/WorkStealing.hsnew file+52
@@ -0,0 +1,52 @@
1+module WorkStealing where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+
7+fib :: Integer -> Integer
8+fib = go (0, 1)
9+ where
10+ go (!a, !b) !n | n == 0 = a
11+ | otherwise = go (b, a + b) (n - 1)
12+
13+slave :: (ProcessId, ProcessId) -> Process ()
14+slave (master, workQueue) = do
15+ us <- getSelfPid
16+ go us
17+ where
18+ go us = do
19+ -- Ask the queue for work
20+ send workQueue us
21+
22+ -- If there is work, do it, otherwise terminate
23+ receiveWait
24+ [ match $ \n -> send master (fib n) >> go us
25+ , match $ \() -> return ()
26+ ]
27+
28+remotable ['slave]
29+
30+master :: Integer -> [NodeId] -> Process Integer
31+master n slaves = do
32+ us <- getSelfPid
33+
34+ workQueue <- spawnLocal $ do
35+ -- As long as there is work, return the next Fib to compute
36+ forM_ [1 .. n] $ \m -> do
37+ them <- expect
38+ send them m
39+
40+ -- After that, just report that the work is done
41+ forever $ do
42+ pid <- expect
43+ send pid ()
44+
45+ -- Start processes on the slaves that compute Fibonacci numbers
46+ forM_ slaves $ \nid -> spawn nid ($(mkClosure 'slave) (us, workQueue))
47+
48+ -- Wait for the result
49+ partials <- replicateM (fromIntegral n) (expect :: Process Integer)
50+
51+ -- And return the sum
52+ return (sum partials)
distributed-process-simplelocalnet/ChangeLognew file+27
@@ -0,0 +1,27 @@
1+<<date>> Edsko de Vries <edsko@well-typed.com> 0.2.0.6
2+
3+* Use new version of network-transport
4+
5+2012-08-22 Edsko de Vries <edsko@well-typed.com> 0.2.0.5
6+
7+* Don't assume slaves are still alive in findSlaves
8+
9+2012-08-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.4
10+
11+* Relax version bounds for distributed-process
12+
13+2012-07-17 Edsko de Vries <edsko@well-typed.com> 0.2.0.3
14+
15+* Improve documentation
16+
17+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.0.2
18+
19+* Relax contraints on bytestring and containers
20+
21+2012-07-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
22+
23+* Bugfix: Documentation referred to old module name
24+
25+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
26+
27+* Initial release.
LICENSE → distributed-process-simplelocalnet/LICENSErenamed−1
@@ -1,5 +1,4 @@
1Copyright Well-Typed LLP, 2011-2012
1Copyright Well-Typed LLP, 2011-2012
2−Copyright Tweag I/O Limited, 2015
3
2
4All rights reserved.
3All rights reserved.
5
4
distributed-process-simplelocalnet/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-process-simplelocalnet/distributed-process-simplelocalnet.cabalnew file+63
@@ -0,0 +1,63 @@
1+Name: distributed-process-simplelocalnet
2+Version: 0.2.0.6
3+Cabal-Version: >=1.8
4+Build-Type: Simple
5+License: BSD3
6+License-File: LICENSE
7+Copyright: Well-Typed LLP
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: edsko@well-typed.com, dcoutts@well-typed.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com
13+Synopsis: Simple zero-configuration backend for Cloud Haskell
14+Description: Simple backend based on the TCP transport which offers node
15+ discovery based on UDP multicast. This is a zero-configuration
16+ backend designed to get you going with Cloud Haskell quickly
17+ without imposing any structure on your application.
18+Tested-With: GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
19+Category: Control
20+
21+Source-Repository head
22+ Type: git
23+ Location: https://github.com/haskell-distributed/distributed-process
24+ SubDir: distributed-process-simplelocalnet
25+
26+Library
27+ Build-Depends: base >= 4.4 && < 5,
28+ bytestring >= 0.9 && < 0.11,
29+ network >= 2.3 && < 2.4,
30+ network-multicast >= 0.0 && < 0.1,
31+ data-accessor >= 0.2 && < 0.3,
32+ binary >= 0.5 && < 0.6,
33+ containers >= 0.4 && < 0.6,
34+ transformers >= 0.2 && < 0.4,
35+ network-transport >= 0.3 && < 0.4,
36+ network-transport-tcp >= 0.3 && < 0.4,
37+ distributed-process >= 0.2 && < 0.4
38+ Exposed-modules: Control.Distributed.Process.Backend.SimpleLocalnet,
39+ Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast
40+ Extensions: RankNTypes,
41+ DeriveDataTypeable
42+ ghc-options: -Wall
43+ HS-Source-Dirs: src
44+
45+-- Not a proper test, but we want to use cabal to compile it
46+Test-Suite TestSimpleLocalnet
47+ Type: exitcode-stdio-1.0
48+ Main-Is: TestSimpleLocalnet.hs
49+ Build-Depends: base >= 4.4 && < 5,
50+ bytestring >= 0.9 && < 0.11,
51+ network >= 2.3 && < 2.4,
52+ network-multicast >= 0.0 && < 0.1,
53+ data-accessor >= 0.2 && < 0.3,
54+ binary >= 0.5 && < 0.6,
55+ containers >= 0.4 && < 0.6,
56+ transformers >= 0.2 && < 0.4,
57+ network-transport >= 0.3 && < 0.4,
58+ network-transport-tcp >= 0.3 && < 0.4,
59+ distributed-process >= 0.2 && < 0.4
60+ Extensions: RankNTypes,
61+ DeriveDataTypeable
62+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
63+ HS-Source-Dirs: tests src
distributed-process-simplelocalnet/src/Control/Distributed/Process/Backend/SimpleLocalnet.hsnew file+355
@@ -0,0 +1,355 @@
1+-- | Simple backend based on the TCP transport which offers node discovery
2+-- based on UDP multicast. This is a zero-configuration backend designed to
3+-- get you going with Cloud Haskell quickly without imposing any structure
4+-- on your application.
5+--
6+-- To simplify getting started we provide special support for /master/ and
7+-- /slave/ nodes (see 'startSlave' and 'startMaster'). Use of these functions
8+-- is completely optional; you can use the local backend without making use
9+-- of the predefined master and slave nodes.
10+--
11+-- [Minimal example]
12+--
13+-- > import System.Environment (getArgs)
14+-- > import Control.Distributed.Process
15+-- > import Control.Distributed.Process.Node (initRemoteTable)
16+-- > import Control.Distributed.Process.Backend.SimpleLocalnet
17+-- >
18+-- > master :: Backend -> [NodeId] -> Process ()
19+-- > master backend slaves = do
20+-- > -- Do something interesting with the slaves
21+-- > liftIO . putStrLn $ "Slaves: " ++ show slaves
22+-- > -- Terminate the slaves when the master terminates (this is optional)
23+-- > terminateAllSlaves backend
24+-- >
25+-- > main :: IO ()
26+-- > main = do
27+-- > args <- getArgs
28+-- >
29+-- > case args of
30+-- > ["master", host, port] -> do
31+-- > backend <- initializeBackend host port initRemoteTable
32+-- > startMaster backend (master backend)
33+-- > ["slave", host, port] -> do
34+-- > backend <- initializeBackend host port initRemoteTable
35+-- > startSlave backend
36+--
37+-- [Compiling and Running]
38+--
39+-- Save to @example.hs@ and compile using
40+--
41+-- > ghc -threaded example.hs
42+--
43+-- Fire up some slave nodes (for the example, we run them on a single machine):
44+--
45+-- > ./example slave localhost 8080 &
46+-- > ./example slave localhost 8081 &
47+-- > ./example slave localhost 8082 &
48+-- > ./example slave localhost 8083 &
49+--
50+-- And start the master node:
51+--
52+-- > ./example master localhost 8084
53+--
54+-- which should then output:
55+--
56+-- > Slaves: [nid://localhost:8083:0,nid://localhost:8082:0,nid://localhost:8081:0,nid://localhost:8080:0]
57+--
58+-- at which point the slaves should exit.
59+--
60+-- To run the example on multiple machines, you could run
61+--
62+-- > ./example slave 198.51.100.1 8080 &
63+-- > ./example slave 198.51.100.2 8080 &
64+-- > ./example slave 198.51.100.3 8080 &
65+-- > ./example slave 198.51.100.4 8080 &
66+--
67+-- on four different machines (with IP addresses 198.51.100.1..4), and run the
68+-- master on a fifth node (or on any of the four machines that run the slave
69+-- nodes).
70+--
71+-- It is important that every node has a unique (hostname, port number) pair,
72+-- and that the hostname you use to initialize the node can be resolved by
73+-- peer nodes. In other words, if you start a node and pass hostname @localhost@
74+-- then peer nodes won't be able to reach it because @localhost@ will resolve
75+-- to a different IP address for them.
76+{-# OPTIONS_GHC -fno-warn-orphans #-}
77+module Control.Distributed.Process.Backend.SimpleLocalnet
78+ ( -- * Initialization
79+ Backend(..)
80+ , initializeBackend
81+ -- * Slave nodes
82+ , startSlave
83+ , terminateSlave
84+ , findSlaves
85+ , terminateAllSlaves
86+ -- * Master nodes
87+ , startMaster
88+ ) where
89+
90+import System.IO (fixIO)
91+import Data.Maybe (catMaybes)
92+import Data.Binary (Binary(get, put), getWord8, putWord8)
93+import Data.Accessor (Accessor, accessor, (^:), (^.))
94+import Data.Set (Set)
95+import qualified Data.Set as Set (insert, empty, toList)
96+import Data.Foldable (forM_)
97+import Data.Typeable (Typeable)
98+import Control.Applicative ((<$>))
99+import Control.Exception (throw)
100+import Control.Monad (forever, forM, replicateM)
101+import Control.Monad.IO.Class (liftIO)
102+import Control.Concurrent (forkIO, threadDelay, ThreadId)
103+import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
104+import Control.Distributed.Process
105+ ( RemoteTable
106+ , NodeId
107+ , Process
108+ , WhereIsReply(..)
109+ , whereis
110+ , whereisRemoteAsync
111+ , registerRemote
112+ , getSelfPid
113+ , register
114+ , expect
115+ , nsendRemote
116+ , receiveWait
117+ , match
118+ , matchIf
119+ , processNodeId
120+ , monitorNode
121+ , unmonitor
122+ , NodeMonitorNotification(..)
123+ )
124+import qualified Control.Distributed.Process.Node as Node
125+ ( LocalNode
126+ , newLocalNode
127+ , localNodeId
128+ , runProcess
129+ )
130+import qualified Network.Transport.TCP as NT
131+ ( createTransport
132+ , defaultTCPParameters
133+ )
134+import qualified Network.Transport as NT (Transport)
135+import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
136+import Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast (initMulticast)
137+
138+-- | Local backend
139+data Backend = Backend {
140+ -- | Create a new local node
141+ newLocalNode :: IO Node.LocalNode
142+ -- | @findPeers t@ broadcasts a /who's there?/ message on the local
143+ -- network, waits 't' msec, and then collects and returns the answers.
144+ -- You can use this to dynamically discover peer nodes.
145+ , findPeers :: Int -> IO [NodeId]
146+ -- | Make sure that all log messages are printed by the logger on the
147+ -- current node
148+ , redirectLogsHere :: Process ()
149+ }
150+
151+data BackendState = BackendState {
152+ _localNodes :: [Node.LocalNode]
153+ , _peers :: Set NodeId
154+ , discoveryDaemon :: ThreadId
155+ }
156+
157+-- | Initialize the backend
158+initializeBackend :: N.HostName -> N.ServiceName -> RemoteTable -> IO Backend
159+initializeBackend host port rtable = do
160+ mTransport <- NT.createTransport host port NT.defaultTCPParameters
161+ (recv, send) <- initMulticast "224.0.0.99" 9999 1024
162+ (_, backendState) <- fixIO $ \ ~(tid, _) -> do
163+ backendState <- newMVar BackendState
164+ { _localNodes = []
165+ , _peers = Set.empty
166+ , discoveryDaemon = tid
167+ }
168+ tid' <- forkIO $ peerDiscoveryDaemon backendState recv send
169+ return (tid', backendState)
170+ case mTransport of
171+ Left err -> throw err
172+ Right transport ->
173+ let backend = Backend {
174+ newLocalNode = apiNewLocalNode transport rtable backendState
175+ , findPeers = apiFindPeers send backendState
176+ , redirectLogsHere = apiRedirectLogsHere backend
177+ }
178+ in return backend
179+
180+-- | Create a new local node
181+apiNewLocalNode :: NT.Transport
182+ -> RemoteTable
183+ -> MVar BackendState
184+ -> IO Node.LocalNode
185+apiNewLocalNode transport rtable backendState = do
186+ localNode <- Node.newLocalNode transport rtable
187+ modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
188+ return localNode
189+
190+-- | Peer discovery
191+apiFindPeers :: (PeerDiscoveryMsg -> IO ())
192+ -> MVar BackendState
193+ -> Int
194+ -> IO [NodeId]
195+apiFindPeers send backendState delay = do
196+ send PeerDiscoveryRequest
197+ threadDelay delay
198+ Set.toList . (^. peers) <$> readMVar backendState
199+
200+data PeerDiscoveryMsg =
201+ PeerDiscoveryRequest
202+ | PeerDiscoveryReply NodeId
203+
204+instance Binary PeerDiscoveryMsg where
205+ put PeerDiscoveryRequest = putWord8 0
206+ put (PeerDiscoveryReply nid) = putWord8 1 >> put nid
207+ get = do
208+ header <- getWord8
209+ case header of
210+ 0 -> return PeerDiscoveryRequest
211+ 1 -> PeerDiscoveryReply <$> get
212+ _ -> fail "PeerDiscoveryMsg.get: invalid"
213+
214+-- | Respond to peer discovery requests sent by other nodes
215+peerDiscoveryDaemon :: MVar BackendState
216+ -> IO (PeerDiscoveryMsg, N.SockAddr)
217+ -> (PeerDiscoveryMsg -> IO ())
218+ -> IO ()
219+peerDiscoveryDaemon backendState recv send = forever go
220+ where
221+ go = do
222+ (msg, _) <- recv
223+ case msg of
224+ PeerDiscoveryRequest -> do
225+ nodes <- (^. localNodes) <$> readMVar backendState
226+ forM_ nodes $ send . PeerDiscoveryReply . Node.localNodeId
227+ PeerDiscoveryReply nid ->
228+ modifyMVar_ backendState $ return . (peers ^: Set.insert nid)
229+
230+--------------------------------------------------------------------------------
231+-- Back-end specific primitives --
232+--------------------------------------------------------------------------------
233+
234+-- | Make sure that all log messages are printed by the logger on this node
235+apiRedirectLogsHere :: Backend -> Process ()
236+apiRedirectLogsHere backend = do
237+ mLogger <- whereis "logger"
238+ forM_ mLogger $ \logger -> do
239+ nids <- liftIO $ findPeers backend 1000000
240+ forM_ nids $ \nid -> registerRemote nid "logger" logger
241+
242+--------------------------------------------------------------------------------
243+-- Slaves --
244+--------------------------------------------------------------------------------
245+
246+-- | Messages to slave nodes
247+--
248+-- This datatype is not exposed; instead, we expose primitives for dealing
249+-- with slaves.
250+data SlaveControllerMsg =
251+ SlaveTerminate
252+ deriving (Typeable, Show)
253+
254+instance Binary SlaveControllerMsg where
255+ put SlaveTerminate = putWord8 0
256+ get = do
257+ header <- getWord8
258+ case header of
259+ 0 -> return SlaveTerminate
260+ _ -> fail "SlaveControllerMsg.get: invalid"
261+
262+-- | Calling 'slave' sets up a new local node and then waits. You start
263+-- processes on the slave by calling 'spawn' from other nodes.
264+--
265+-- This function does not return. The only way to exit the slave is to CTRL-C
266+-- the process or call terminateSlave from another node.
267+startSlave :: Backend -> IO ()
268+startSlave backend = do
269+ node <- newLocalNode backend
270+ Node.runProcess node slaveController
271+
272+-- | The slave controller interprets 'SlaveControllerMsg's
273+slaveController :: Process ()
274+slaveController = do
275+ pid <- getSelfPid
276+ register "slaveController" pid
277+ go
278+ where
279+ go = do
280+ msg <- expect
281+ case msg of
282+ SlaveTerminate -> return ()
283+
284+-- | Terminate the slave at the given node ID
285+terminateSlave :: NodeId -> Process ()
286+terminateSlave nid = nsendRemote nid "slaveController" SlaveTerminate
287+
288+-- | Find slave nodes
289+findSlaves :: Backend -> Process [NodeId]
290+findSlaves backend = do
291+ nodes <- liftIO $ findPeers backend 1000000
292+ -- Fire of asynchronous requests for the slave controller
293+ refs <- forM nodes $ \nid -> do
294+ whereisRemoteAsync nid "slaveController"
295+ ref <- monitorNode nid
296+ return (nid, ref)
297+ -- Wait for the replies
298+ catMaybes <$> replicateM (length nodes) (
299+ receiveWait
300+ [ matchIf (\(WhereIsReply label _) -> label == "slaveController")
301+ (\(WhereIsReply _ mPid) ->
302+ case mPid of
303+ Nothing ->
304+ return Nothing
305+ Just pid -> do
306+ let nid = processNodeId pid
307+ Just ref = lookup nid refs
308+ unmonitor ref
309+ return (Just nid))
310+ , match (\(NodeMonitorNotification {}) -> return Nothing)
311+ ])
312+
313+-- | Terminate all slaves
314+terminateAllSlaves :: Backend -> Process ()
315+terminateAllSlaves backend = do
316+ slaves <- findSlaves backend
317+ forM_ slaves terminateSlave
318+ liftIO $ threadDelay 1000000
319+
320+--------------------------------------------------------------------------------
321+-- Master nodes
322+--------------------------------------------------------------------------------
323+
324+-- | 'startMaster' finds all slaves /currently/ available on the local network,
325+-- redirects all log messages to itself, and then calls the specified process,
326+-- passing the list of slaves nodes.
327+--
328+-- Terminates when the specified process terminates. If you want to terminate
329+-- the slaves when the master terminates, you should manually call
330+-- 'terminateAllSlaves'.
331+--
332+-- If you start more slave nodes after having started the master node, you can
333+-- discover them with later calls to 'findSlaves', but be aware that you will
334+-- need to call 'redirectLogHere' to redirect their logs to the master node.
335+--
336+-- Note that you can use functionality of "SimpleLocalnet" directly (through
337+-- 'Backend'), instead of using 'startMaster'/'startSlave', if the master/slave
338+-- distinction does not suit your application.
339+startMaster :: Backend -> ([NodeId] -> Process ()) -> IO ()
340+startMaster backend proc = do
341+ node <- newLocalNode backend
342+ Node.runProcess node $ do
343+ slaves <- findSlaves backend
344+ redirectLogsHere backend
345+ proc slaves
346+
347+--------------------------------------------------------------------------------
348+-- Accessors --
349+--------------------------------------------------------------------------------
350+
351+localNodes :: Accessor BackendState [Node.LocalNode]
352+localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
353+
354+peers :: Accessor BackendState (Set NodeId)
355+peers = accessor _peers (\ps st -> st { _peers = ps })
distributed-process-simplelocalnet/src/Control/Distributed/Process/Backend/SimpleLocalnet/Internal/Multicast.hsnew file+126
@@ -0,0 +1,126 @@
1+-- | Multicast utilities
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast (initMulticast) where
4+
5+import Data.Function (on)
6+import Data.Map (Map)
7+import qualified Data.Map as Map (empty)
8+import Data.Binary (Binary, decode, encode)
9+import Data.IORef (IORef, newIORef, readIORef, modifyIORef)
10+import qualified Data.ByteString as BSS (ByteString, concat)
11+import qualified Data.ByteString.Lazy as BSL
12+ ( ByteString
13+ , empty
14+ , append
15+ , fromChunks
16+ , toChunks
17+ , length
18+ , splitAt
19+ )
20+import Data.Accessor (Accessor, (^:), (^.), (^=))
21+import qualified Data.Accessor.Container as DAC (mapDefault)
22+import Control.Applicative ((<$>))
23+import Network.Socket (HostName, PortNumber, Socket, SockAddr)
24+import qualified Network.Socket.ByteString as NBS (recvFrom, sendManyTo)
25+import Network.Transport.Internal (decodeInt32, encodeInt32)
26+import Network.Multicast (multicastSender, multicastReceiver)
27+
28+--------------------------------------------------------------------------------
29+-- Top-level API --
30+--------------------------------------------------------------------------------
31+
32+-- | Given a hostname and a port number, initialize the multicast system.
33+--
34+-- Note: it is important that you never send messages larger than the maximum
35+-- message size; if you do, all subsequent communication will probably fail.
36+--
37+-- Returns a reader and a writer.
38+--
39+-- NOTE: By rights the two functions should be "locally" polymorphic in 'a',
40+-- but this requires impredicative types.
41+initMulticast :: forall a. Binary a
42+ => HostName -- ^ Multicast IP
43+ -> PortNumber -- ^ Port number
44+ -> Int -- ^ Maximum message size
45+ -> IO (IO (a, SockAddr), a -> IO ())
46+initMulticast host port bufferSize = do
47+ (sendSock, sendAddr) <- multicastSender host port
48+ readSock <- multicastReceiver host port
49+ st <- newIORef Map.empty
50+ return (recvBinary readSock st bufferSize, writer sendSock sendAddr)
51+ where
52+ writer :: forall a. Binary a => Socket -> SockAddr -> a -> IO ()
53+ writer sock addr val = do
54+ let bytes = encode val
55+ len = encodeInt32 (BSL.length bytes)
56+ NBS.sendManyTo sock (len : BSL.toChunks bytes) addr
57+
58+--------------------------------------------------------------------------------
59+-- UDP multicast read, dealing with multiple senders --
60+--------------------------------------------------------------------------------
61+
62+type UDPState = Map SockAddr BSL.ByteString
63+
64+-- TODO: This is inefficient and an orphan instance.
65+-- Requested official instance (https://github.com/haskell/network/issues/38)
66+instance Ord SockAddr where
67+ compare = compare `on` show
68+
69+bufferFor :: SockAddr -> Accessor UDPState BSL.ByteString
70+bufferFor = DAC.mapDefault BSL.empty
71+
72+bufferAppend :: SockAddr -> BSS.ByteString -> UDPState -> UDPState
73+bufferAppend addr bytes =
74+ bufferFor addr ^: flip BSL.append (BSL.fromChunks [bytes])
75+
76+recvBinary :: Binary a => Socket -> IORef UDPState -> Int -> IO (a, SockAddr)
77+recvBinary sock st bufferSize = do
78+ (bytes, addr) <- recvWithLength sock st bufferSize
79+ return (decode bytes, addr)
80+
81+recvWithLength :: Socket
82+ -> IORef UDPState
83+ -> Int
84+ -> IO (BSL.ByteString, SockAddr)
85+recvWithLength sock st bufferSize = do
86+ (len, addr) <- recvExact sock 4 st bufferSize
87+ let n = decodeInt32 . BSS.concat . BSL.toChunks $ len
88+ bytes <- recvExactFrom addr sock n st bufferSize
89+ return (bytes, addr)
90+
91+-- Receive all bytes currently in the buffer
92+recvAll :: Socket -> IORef UDPState -> Int -> IO SockAddr
93+recvAll sock st bufferSize = do
94+ (bytes, addr) <- NBS.recvFrom sock bufferSize
95+ modifyIORef st $ bufferAppend addr bytes
96+ return addr
97+
98+recvExact :: Socket
99+ -> Int
100+ -> IORef UDPState
101+ -> Int
102+ -> IO (BSL.ByteString, SockAddr)
103+recvExact sock n st bufferSize = do
104+ addr <- recvAll sock st bufferSize
105+ bytes <- recvExactFrom addr sock n st bufferSize
106+ return (bytes, addr)
107+
108+recvExactFrom :: SockAddr
109+ -> Socket
110+ -> Int
111+ -> IORef UDPState
112+ -> Int
113+ -> IO BSL.ByteString
114+recvExactFrom addr sock n st bufferSize = go
115+ where
116+ go :: IO BSL.ByteString
117+ go = do
118+ accAddr <- (^. bufferFor addr) <$> readIORef st
119+ if BSL.length accAddr >= fromIntegral n
120+ then do
121+ let (bytes, accAddr') = BSL.splitAt (fromIntegral n) accAddr
122+ modifyIORef st $ bufferFor addr ^= accAddr'
123+ return bytes
124+ else do
125+ _ <- recvAll sock st bufferSize
126+ go
distributed-process-simplelocalnet/tests/TestSimpleLocalnet.hsnew file+24
@@ -0,0 +1,24 @@
1+import System.Environment (getArgs, getProgName)
2+import Control.Distributed.Process (NodeId, Process, liftIO)
3+import Control.Distributed.Process.Node (initRemoteTable)
4+import Control.Distributed.Process.Backend.SimpleLocalnet
5+
6+master :: Backend -> [NodeId] -> Process ()
7+master backend slaves = do
8+ liftIO . putStrLn $ "Slaves: " ++ show slaves
9+ terminateAllSlaves backend
10+
11+main :: IO ()
12+main = do
13+ prog <- getProgName
14+ args <- getArgs
15+
16+ case args of
17+ ["master", host, port] -> do
18+ backend <- initializeBackend host port initRemoteTable
19+ startMaster backend (master backend)
20+ ["slave", host, port] -> do
21+ backend <- initializeBackend host port initRemoteTable
22+ startSlave backend
23+ _ ->
24+ putStrLn $ "usage: " ++ prog ++ " (master | slave) host port"
distributed-process-simplelocalnet/tests/runTestSimpleLocalnet.hsnew file+11
@@ -0,0 +1,11 @@
1+#!/bin/bash
2+TestSimpleLocalnet=dist/build/TestSimpleLocalnet/TestSimpleLocalnet
3+$TestSimpleLocalnet slave 127.0.0.1 8080 &
4+sleep 1
5+$TestSimpleLocalnet slave 127.0.0.1 8081 &
6+sleep 1
7+$TestSimpleLocalnet slave 127.0.0.1 8082 &
8+sleep 1
9+$TestSimpleLocalnet slave 127.0.0.1 8083 &
10+sleep 1
11+$TestSimpleLocalnet master 127.0.0.1 8084
distributed-process-tests/.gitignoredeleted−4
@@ -1,4 +0,0 @@
1−dist/
2−.cabal-sandbox
3−cabal.sandbox.config
4−.stack*
distributed-process-tests/CONTRIBUTING.mddeleted−1
@@ -1 +0,0 @@
1−See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
distributed-process-tests/README.mddeleted−14
@@ -1,14 +0,0 @@
1−# distributed-process-tests
2−[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process-tests.png)](http://travis-ci.org/haskell-distributed/distributed-process-tests)
3−[![Release](https://img.shields.io/hackage/v/distributed-process-tests.svg)](https://hackage.haskell.org/package/distributed-process-tests)
4−
5−See http://haskell-distributed.github.com for documentation, user guides,
6−tutorials and assistance.
7−
8−## Getting Help / Raising Issues
9−
10−Please visit the [bug tracker](https://github.com/haskell-distributed/distributed-process-tests/issues) to submit issues. You can contact the distributed-haskell@googlegroups.com mailing list for help and comments.
11−
12−## License
13−
14−This package is made available under a 3-clause BSD-style license.
distributed-process-tests/distributed-process-tests.cabaldeleted−158
@@ -1,158 +0,0 @@
1−name: distributed-process-tests
2−version: 0.4.11
3−synopsis: Tests and test support tools for distributed-process.
4−homepage: http://github.com/haskell-distributed/distributed-process/tree/master/distributed-process-tests
5−description: Tests and test suite for Cloud Haskell libraries.
6−license: BSD3
7−license-file: LICENSE
8−Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9−Maintainer: Tim Watson <watson.timothy@gmail.com>
10−copyright: Well-Typed LLP
11−category: Control, Cloud Haskell
12−build-type: Simple
13−cabal-version: >=1.8
14−
15−flag tcp
16− Description: build and run TCP tests
17− Default: False
18−
19−library
20− exposed-modules: Network.Transport.Test
21− Control.Distributed.Process.Tests.CH
22− Control.Distributed.Process.Tests.Closure
23− Control.Distributed.Process.Tests.Mx
24− Control.Distributed.Process.Tests.Receive
25− Control.Distributed.Process.Tests.Stats
26− Control.Distributed.Process.Tests.Tracing
27− Control.Distributed.Process.Tests.Internal.Utils
28− Build-Depends: base >= 4.4 && < 5,
29− ansi-terminal >= 0.5,
30− binary >= 0.5 && < 0.9,
31− bytestring >= 0.9 && < 0.12,
32− distributed-process >= 0.6.0 && < 0.8,
33− distributed-static,
34− exceptions >= 0.5,
35− HUnit >= 1.2 && < 1.7,
36− network-transport >= 0.4.1.0 && < 0.6,
37− network >= 2.5 && < 2.9,
38− random >= 1.0 && < 1.2,
39− rematch >= 0.1.2.1,
40− setenv >= 0.1.1.3,
41− test-framework >= 0.6 && < 0.9,
42− test-framework-hunit >= 0.2.0 && < 0.4,
43− stm
44− hs-source-dirs: src
45− ghc-options: -Wall -fno-warn-unused-do-bind
46− extensions: CPP,
47− ExistentialQuantification,
48− FlexibleInstances,
49− DeriveDataTypeable,
50− DeriveGeneric,
51− GeneralizedNewtypeDeriving,
52− RankNTypes,
53− RecordWildCards,
54− ScopedTypeVariables
55− if impl(ghc <= 7.4.2)
56− Build-Depends: ghc-prim == 0.2.0.0
57−
58−Test-Suite TestCHInMemory
59− Type: exitcode-stdio-1.0
60− Main-Is: runInMemory.hs
61− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
62− Build-Depends: base >= 4.4 && < 5,
63− distributed-process-tests,
64− network >= 2.3 && < 2.9,
65− network-transport >= 0.4.1.0 && < 0.6,
66− network-transport-inmemory >= 0.5,
67− test-framework >= 0.6 && < 0.9
68− Extensions: CPP
69− ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
70− HS-Source-Dirs: tests
71−
72−Test-Suite TestCHInTCP
73− Type: exitcode-stdio-1.0
74− Main-Is: runTCP.hs
75− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
76− if flag(tcp)
77− Build-Depends: base >= 4.4 && < 5,
78− distributed-process-tests,
79− network >= 2.3 && < 2.9,
80− network-transport >= 0.4.1.0 && < 0.6,
81− network-transport-tcp >= 0.5 && < 0.7,
82− test-framework >= 0.6 && < 0.9
83− else
84− Buildable: False
85− Extensions: CPP
86− ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
87− HS-Source-Dirs: tests
88−
89−
90−Test-Suite TestClosure
91− Type: exitcode-stdio-1.0
92− Main-Is: runInMemory.hs
93− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Closure
94− Build-Depends: base >= 4.4 && < 5,
95− distributed-process-tests,
96− network >= 2.3 && < 2.9,
97− network-transport >= 0.4.1.0 && < 0.6,
98− network-transport-inmemory >= 0.5,
99− test-framework >= 0.6 && < 0.9
100− Extensions: CPP
101− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
102− HS-Source-Dirs: tests
103−
104−Test-Suite TestStats
105− Type: exitcode-stdio-1.0
106− Main-Is: runInMemory.hs
107− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Stats
108− Build-Depends: base >= 4.4 && < 5,
109− distributed-process-tests,
110− network >= 2.3 && < 2.9,
111− network-transport >= 0.4.1.0 && < 0.6,
112− network-transport-inmemory >= 0.5,
113− test-framework >= 0.6 && < 0.9
114− Extensions: CPP
115− ghc-options: -Wall -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
116− HS-Source-Dirs: tests
117−
118−Test-Suite TestMxInMemory
119− Type: exitcode-stdio-1.0
120− Main-Is: runInMemory.hs
121− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
122− Build-Depends: base >= 4.4 && < 5,
123− distributed-process-tests,
124− network >= 2.3 && < 2.9,
125− network-transport >= 0.4.1.0 && < 0.6,
126− network-transport-inmemory >= 0.5,
127− test-framework >= 0.6 && < 0.9
128− Extensions: CPP
129− ghc-options: -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
130− HS-Source-Dirs: tests
131−
132−Test-Suite TestTracingInMemory
133− Type: exitcode-stdio-1.0
134− Main-Is: runInMemory.hs
135− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Tracing
136− Build-Depends: base >= 4.4 && < 5,
137− distributed-process-tests,
138− network >= 2.3 && < 2.9,
139− network-transport >= 0.4.1.0 && < 0.6,
140− network-transport-inmemory >= 0.5,
141− test-framework >= 0.6 && < 0.9
142− Extensions: CPP
143− ghc-options: -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
144− HS-Source-Dirs: tests
145−
146−Test-Suite TestMxInTCP
147− Type: exitcode-stdio-1.0
148− Main-Is: runInMemory.hs
149− CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
150− Build-Depends: base >= 4.4 && < 5,
151− distributed-process-tests,
152− network >= 2.3 && < 2.9,
153− network-transport >= 0.4.1.0 && < 0.6,
154− network-transport-inmemory >= 0.5,
155− test-framework >= 0.6 && < 0.9
156− Extensions: CPP
157− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
158− HS-Source-Dirs: tests
distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hsdeleted−1879
@@ -1,1879 +0,0 @@
1−module Control.Distributed.Process.Tests.CH (tests) where
2−
3−#if ! MIN_VERSION_base(4,6,0)
4−import Prelude hiding (catch)
5−#endif
6−
7−import Network.Transport.Test (TestTransport(..))
8−
9−import Data.Binary (Binary(..))
10−import Data.Typeable (Typeable)
11−import Data.Foldable (forM_)
12−import Data.Function (fix)
13−import Data.IORef
14− ( readIORef
15− , writeIORef
16− , newIORef
17− )
18−import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId, yield)
19−import Control.Concurrent.MVar
20− ( MVar
21− , newEmptyMVar
22− , putMVar
23− , takeMVar
24− , readMVar
25− )
26−import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
27−import Control.Exception (SomeException, throwIO, ErrorCall(..))
28−import Control.Monad.Catch (try, catch, finally, mask, onException)
29−import Control.Applicative ((<|>))
30−import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
31−import Control.Distributed.Process hiding
32− ( try
33− , catch
34− , finally
35− , mask
36− , onException
37− )
38−import Control.Distributed.Process.Internal.Types
39− ( LocalNode(localEndPoint)
40− , ProcessExitException(..)
41− , nullProcessId
42− , createUnencodedMessage
43− )
44−import Control.Distributed.Process.Node
45−import Control.Distributed.Process.Debug
46−import Control.Distributed.Process.Management.Internal.Types
47−import Control.Distributed.Process.Tests.Internal.Utils (shouldBe, pause)
48−import Control.Distributed.Process.Serializable (Serializable)
49−import Data.Maybe (isNothing, isJust)
50−import Test.HUnit (Assertion, assertBool, assertFailure)
51−import Test.Framework (Test, testGroup)
52−import Test.Framework.Providers.HUnit (testCase)
53−import Control.Rematch hiding (match, isNothing, isJust)
54−import Control.Rematch.Run (Match(..))
55−
56−newtype Ping = Ping ProcessId
57− deriving (Typeable, Binary, Show)
58−
59−newtype Pong = Pong ProcessId
60− deriving (Typeable, Binary, Show)
61−
62−--------------------------------------------------------------------------------
63−-- Supporting definitions --
64−--------------------------------------------------------------------------------
65−
66−expectThat :: a -> Matcher a -> Assertion
67−expectThat a matcher = case res of
68− MatchSuccess -> return ()
69− (MatchFailure msg) -> assertFailure msg
70− where res = runMatch matcher a
71−
72−-- | Like fork, but throw exceptions in the child thread to the parent
73−forkTry :: IO () -> IO ThreadId
74−forkTry p = do
75− tid <- myThreadId
76− forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
77−
78−-- | The ping server from the paper
79−ping :: Process ()
80−ping = do
81− Pong partner <- expect
82− self <- getSelfPid
83− send partner (Ping self)
84− ping
85−
86−verifyClient :: String -> MVar Bool -> IO ()
87−verifyClient s b = takeMVar b >>= assertBool s
88−
89−expectPing :: MVar Bool -> Process ()
90−expectPing mv = expect >>= liftIO . putMVar mv . checkPing
91− where
92− checkPing (Ping _) = True
93−
94−-- | Quick and dirty synchronous version of whereisRemoteAsync
95−whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
96−whereisRemote nid string = do
97− whereisRemoteAsync nid string
98− receiveWait [
99− match (\(WhereIsReply _ mPid) -> return mPid)
100− ]
101−
102−verifyWhereIsRemote :: NodeId -> String -> Process ProcessId
103−verifyWhereIsRemote n s = whereisRemote n s >>= maybe (die "remote name not found") return
104−
105−waitForExit :: ProcessId -> Process DiedReason
106−waitForExit pid = monitor pid >>= waitForDown
107−
108−waitForDown :: MonitorRef -> Process DiedReason
109−waitForDown ref =
110− receiveWait [ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
111− (\(ProcessMonitorNotification _ _ dr) -> return dr) ]
112−
113−syncBreakConnection :: (NT.EndPointAddress -> NT.EndPointAddress -> IO ()) -> LocalNode -> LocalNode -> IO ()
114−syncBreakConnection breakConnection nid0 nid1 = do
115− m <- newEmptyMVar
116− _ <- forkProcess nid1 $ getSelfPid >>= liftIO . putMVar m
117− runProcess nid0 $ do
118− them <- liftIO $ takeMVar m
119− pinger <- spawnLocal $ forever $ send them ()
120− _ <- monitorNode (localNodeId nid1)
121− liftIO $ breakConnection (nodeAddress $ localNodeId nid0)
122− (nodeAddress $ localNodeId nid1)
123− NodeMonitorNotification _ _ _ <- expect
124− kill pinger "finished"
125− return ()
126−
127−data Add = Add ProcessId Double Double deriving (Typeable)
128−data Divide = Divide ProcessId Double Double deriving (Typeable)
129−data DivByZero = DivByZero deriving (Typeable)
130−
131−instance Binary Add where
132− put (Add pid x y) = put pid >> put x >> put y
133− get = Add <$> get <*> get <*> get
134−
135−instance Binary Divide where
136− put (Divide pid x y) = put pid >> put x >> put y
137− get = Divide <$> get <*> get <*> get
138−
139−instance Binary DivByZero where
140− put DivByZero = return ()
141− get = return DivByZero
142−
143−-- The math server from the paper
144−math :: Process ()
145−math = do
146− receiveWait
147− [ match (\(Add pid x y) -> send pid (x + y))
148− , matchIf (\(Divide _ _ y) -> y /= 0)
149− (\(Divide pid x y) -> send pid (x / y))
150− , match (\(Divide pid _ _) -> send pid DivByZero)
151− ]
152− math
153−
154−-- | Monitor or link to a remote node
155−monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
156− -> ProcessId -- ^ Process to monitor/link to
157− -> Maybe (MVar ()) -- ^ MVar to signal on once the monitor has been set up
158− -> Process (Maybe MonitorRef)
159−monitorOrLink mOrL pid mSignal = do
160− result <- if mOrL then Just <$> monitor pid
161− else link pid >> return Nothing
162− -- Monitor is asynchronous, which usually does not matter but if we want a
163− -- *specific* signal then it does. Therefore we wait until the MonitorRef is
164− -- listed in the ProcessInfo and hope that this means the monitor has been set
165− -- up.
166− forM_ mSignal $ \signal -> do
167− self <- getSelfPid
168− spawnLocal $ do
169− let waitForMOrL = do
170− liftIO $ threadDelay 100000
171− mpinfo <- getProcessInfo pid
172− case mpinfo of
173− Nothing -> waitForMOrL
174− Just pinfo ->
175− if mOrL then
176− unless (result == lookup self (infoMonitors pinfo)) waitForMOrL
177− else
178− unless (elem self $ infoLinks pinfo) waitForMOrL
179− waitForMOrL
180− liftIO $ putMVar signal ()
181− return result
182−
183−monitorTestProcess :: ProcessId -- Process to monitor/link to
184− -> Bool -- 'True' for monitor, 'False' for link
185− -> Bool -- Should we unmonitor?
186− -> DiedReason -- Expected cause of death
187− -> Maybe (MVar ()) -- Signal for 'monitor set up'
188− -> MVar () -- Signal for successful termination
189− -> Process ()
190−monitorTestProcess theirAddr mOrL un reason monitorSetup done =
191− catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
192− case (un, mRef) of
193− (True, Nothing) -> do
194− unlink theirAddr
195− liftIO $ putMVar done ()
196− (True, Just ref) -> do
197− unmonitor ref
198− liftIO $ putMVar done ()
199− (False, ref) -> do
200− receiveWait [
201− match (\(ProcessMonitorNotification ref' pid reason') -> do
202− liftIO $ do
203− assertBool "Bad Monitor Signal"
204− (Just ref' == ref && pid == theirAddr &&
205− mOrL && reason == reason')
206− putMVar done ())
207− ]
208− )
209− (\(ProcessLinkException pid reason') -> do
210− (liftIO $ assertBool "link exception unmatched" $
211− pid == theirAddr && not mOrL && not un && reason == reason')
212− liftIO $ putMVar done ()
213− )
214−
215−--------------------------------------------------------------------------------
216−-- The tests proper --
217−--------------------------------------------------------------------------------
218−
219−-- | Basic ping test
220−testPing :: TestTransport -> Assertion
221−testPing TestTransport{..} = do
222− serverAddr <- newEmptyMVar
223− clientDone <- newEmptyMVar
224−
225− -- Server
226− forkIO $ do
227− localNode <- newLocalNode testTransport initRemoteTable
228− addr <- forkProcess localNode ping
229− putMVar serverAddr addr
230−
231− -- Client
232− forkIO $ do
233− localNode <- newLocalNode testTransport initRemoteTable
234− pingServer <- readMVar serverAddr
235−
236− let numPings = 10000
237−
238− runProcess localNode $ do
239− pid <- getSelfPid
240− replicateM_ numPings $ do
241− send pingServer (Pong pid)
242− p <- expectTimeout 3000000
243− case p of
244− Just (Ping _) -> return ()
245− Nothing -> die "Failed to receive Ping"
246−
247− putMVar clientDone ()
248−
249− takeMVar clientDone
250−
251−-- | Monitor a process on an unreachable node
252−testMonitorUnreachable :: TestTransport -> Bool -> Bool -> Assertion
253−testMonitorUnreachable TestTransport{..} mOrL un = do
254− deadProcess <- newEmptyMVar
255− done <- newEmptyMVar
256−
257− forkIO $ do
258− localNode <- newLocalNode testTransport initRemoteTable
259− addr <- forkProcess localNode expect
260− closeLocalNode localNode
261− putMVar deadProcess addr
262−
263− forkIO $ do
264− localNode <- newLocalNode testTransport initRemoteTable
265− theirAddr <- readMVar deadProcess
266− runProcess localNode $
267− monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
268−
269− takeMVar done
270−
271−-- | Monitor a process which terminates normally
272−testMonitorNormalTermination :: TestTransport -> Bool -> Bool -> Assertion
273−testMonitorNormalTermination TestTransport{..} mOrL un = do
274− monitorSetup <- newEmptyMVar
275− monitoredProcess <- newEmptyMVar
276− done <- newEmptyMVar
277−
278− forkIO $ do
279− localNode <- newLocalNode testTransport initRemoteTable
280− addr <- forkProcess localNode $
281− liftIO $ readMVar monitorSetup
282− putMVar monitoredProcess addr
283−
284− forkIO $ do
285− localNode <- newLocalNode testTransport initRemoteTable
286− theirAddr <- readMVar monitoredProcess
287− runProcess localNode $
288− monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
289−
290− takeMVar done
291−
292−-- | Monitor a process which terminates abnormally
293−testMonitorAbnormalTermination :: TestTransport -> Bool -> Bool -> Assertion
294−testMonitorAbnormalTermination TestTransport{..} mOrL un = do
295− monitorSetup <- newEmptyMVar
296− monitoredProcess <- newEmptyMVar
297− done <- newEmptyMVar
298−
299− let err = userError "Abnormal termination"
300−
301− forkIO $ do
302− localNode <- newLocalNode testTransport initRemoteTable
303− addr <- forkProcess localNode . liftIO $ do
304− readMVar monitorSetup
305− throwIO err
306− putMVar monitoredProcess addr
307−
308− forkIO $ do
309− localNode <- newLocalNode testTransport initRemoteTable
310− theirAddr <- readMVar monitoredProcess
311− runProcess localNode $
312− monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
313−
314− takeMVar done
315−
316−-- | Monitor a local process that is already dead
317−testMonitorLocalDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
318−testMonitorLocalDeadProcess TestTransport{..} mOrL un = do
319− processAddr <- newEmptyMVar
320− localNode <- newLocalNode testTransport initRemoteTable
321− done <- newEmptyMVar
322−
323− forkIO $ do
324− addr <- forkProcess localNode $ return ()
325− putMVar processAddr addr
326−
327− forkIO $ do
328− theirAddr <- readMVar processAddr
329− runProcess localNode $ do
330− monitor theirAddr
331− -- wait for the process to die
332− expect :: Process ProcessMonitorNotification
333− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
334−
335− takeMVar done
336−
337−-- | Monitor a remote process that is already dead
338−testMonitorRemoteDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
339−testMonitorRemoteDeadProcess TestTransport{..} mOrL un = do
340− processDead <- newEmptyMVar
341− processAddr <- newEmptyMVar
342− done <- newEmptyMVar
343−
344− forkIO $ do
345− localNode <- newLocalNode testTransport initRemoteTable
346− addr <- forkProcess localNode . liftIO $ putMVar processDead ()
347− putMVar processAddr addr
348−
349− forkIO $ do
350− localNode <- newLocalNode testTransport initRemoteTable
351− theirAddr <- readMVar processAddr
352− readMVar processDead
353− runProcess localNode $ do
354− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
355−
356− takeMVar done
357−
358−-- | Monitor a process that becomes disconnected
359−testMonitorDisconnect :: TestTransport -> Bool -> Bool -> Assertion
360−testMonitorDisconnect TestTransport{..} mOrL un = do
361− processAddr <- newEmptyMVar
362− processAddr2 <- newEmptyMVar
363− monitorSetup <- newEmptyMVar
364− done <- newEmptyMVar
365−
366− forkIO $ do
367− localNode <- newLocalNode testTransport initRemoteTable
368− addr <- forkProcess localNode $ expect
369− addr2 <- forkProcess localNode $ return ()
370− putMVar processAddr addr
371− readMVar monitorSetup
372− NT.closeEndPoint (localEndPoint localNode)
373− putMVar processAddr2 addr2
374−
375− forkIO $ do
376− localNode <- newLocalNode testTransport initRemoteTable
377− theirAddr <- readMVar processAddr
378− forkProcess localNode $ do
379− lc <- liftIO $ readMVar processAddr2
380− send lc ()
381− runProcess localNode $ do
382− monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
383−
384− takeMVar done
385−
386−-- | Test the math server (i.e., receiveWait)
387−testMath :: TestTransport -> Assertion
388−testMath TestTransport{..} = do
389− serverAddr <- newEmptyMVar
390− clientDone <- newEmptyMVar
391−
392− -- Server
393− forkIO $ do
394− localNode <- newLocalNode testTransport initRemoteTable
395− addr <- forkProcess localNode math
396− putMVar serverAddr addr
397−
398− -- Client
399− forkIO $ do
400− localNode <- newLocalNode testTransport initRemoteTable
401− mathServer <- readMVar serverAddr
402−
403− runProcess localNode $ do
404− pid <- getSelfPid
405− send mathServer (Add pid 1 2)
406− three <- expect :: Process Double
407− send mathServer (Divide pid 8 2)
408− four <- expect :: Process Double
409− send mathServer (Divide pid 8 0)
410− divByZ <- expect
411− liftIO $ putMVar clientDone (three, four, divByZ)
412−
413− res <- takeMVar clientDone
414− case res of
415− (3, 4, DivByZero) -> return ()
416− _ -> error $ "Something went horribly wrong"
417−
418−-- | Send first message (i.e. connect) to an already terminated process
419−-- (without monitoring); then send another message to a second process on
420−-- the same remote node (we're checking that the remote node did not die)
421−testSendToTerminated :: TestTransport -> Assertion
422−testSendToTerminated TestTransport{..} = do
423− serverAddr1 <- newEmptyMVar
424− serverAddr2 <- newEmptyMVar
425− clientDone <- newEmptyMVar
426−
427− forkIO $ do
428− terminated <- newEmptyMVar
429− localNode <- newLocalNode testTransport initRemoteTable
430− addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
431− addr2 <- forkProcess localNode $ ping
432− readMVar terminated
433− putMVar serverAddr1 addr1
434− putMVar serverAddr2 addr2
435−
436− forkIO $ do
437− localNode <- newLocalNode testTransport initRemoteTable
438− server1 <- readMVar serverAddr1
439− server2 <- readMVar serverAddr2
440− runProcess localNode $ do
441− pid <- getSelfPid
442− send server1 "Hi"
443− send server2 (Pong pid)
444− expectPing clientDone
445−
446− verifyClient "Expected Ping from server" clientDone
447−
448−-- | Test (non-zero) timeout
449−testTimeout :: TestTransport -> Assertion
450−testTimeout TestTransport{..} = do
451− localNode <- newLocalNode testTransport initRemoteTable
452− done <- newEmptyMVar
453−
454− runProcess localNode $ do
455− res <- receiveTimeout 1000000 [match (\Add{} -> return ())]
456− liftIO $ putMVar done $ res == Nothing
457−
458− verifyClient "Expected receiveTimeout to timeout..." done
459−
460−-- | Test zero timeout
461−testTimeout0 :: TestTransport -> Assertion
462−testTimeout0 TestTransport{..} = do
463− serverAddr <- newEmptyMVar
464− clientDone <- newEmptyMVar
465−
466− forkIO $ do
467− localNode <- newLocalNode testTransport initRemoteTable
468− addr <- forkProcess localNode $ do
469− -- Variation on the venerable ping server which uses a zero timeout
470− partner <- fix $ \loop ->
471− receiveTimeout 0 [match (\(Pong partner) -> return partner)]
472− >>= maybe (liftIO (threadDelay 100000) >> loop) return
473− self <- getSelfPid
474− send partner (Ping self)
475− putMVar serverAddr addr
476−
477− forkIO $ do
478− localNode <- newLocalNode testTransport initRemoteTable
479− server <- readMVar serverAddr
480− runProcess localNode $ do
481− pid <- getSelfPid
482− -- Send a bunch of messages. A large number of messages that the server
483− -- is not interested in, and then a single message that it wants
484− replicateM_ 10000 $ send server "Irrelevant message"
485− send server (Pong pid)
486− expectPing clientDone
487−
488− verifyClient "Expected Ping from server" clientDone
489−
490−-- | Test typed channels
491−testTypedChannels :: TestTransport -> Assertion
492−testTypedChannels TestTransport{..} = do
493− serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
494− clientDone <- newEmptyMVar
495−
496− forkIO $ do
497− localNode <- newLocalNode testTransport initRemoteTable
498− forkProcess localNode $ do
499− (serverSendPort, rport) <- newChan
500− liftIO $ putMVar serverChannel serverSendPort
501− (clientSendPort, i) <- receiveChan rport
502− sendChan clientSendPort (even i)
503− return ()
504−
505− forkIO $ do
506− localNode <- newLocalNode testTransport initRemoteTable
507− serverSendPort <- readMVar serverChannel
508− runProcess localNode $ do
509− (clientSendPort, rport) <- newChan
510− sendChan serverSendPort (clientSendPort, 5)
511− ch <- receiveChan rport
512− liftIO $ putMVar clientDone $ ch == False
513−
514− verifyClient "Expected channel to send 'False'" clientDone
515−
516−-- | Test merging receive ports
517−testMergeChannels :: TestTransport -> Assertion
518−testMergeChannels TestTransport{..} = do
519− localNode <- newLocalNode testTransport initRemoteTable
520− testFlat localNode True "aaabbbccc"
521− testFlat localNode False "abcabcabc"
522− testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
523− testNested localNode True False "adgadgadgbehbehbehcficficfi"
524− testNested localNode False True "abcabcabcdefdefdefghighighi"
525− testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
526− testBlocked localNode True
527− testBlocked localNode False
528− where
529− -- Single layer of merging
530− testFlat :: LocalNode -> Bool -> String -> IO ()
531− testFlat localNode biased expected = do
532− done <- newEmptyMVar
533− forkProcess localNode $ do
534− rs <- mapM charChannel "abc"
535− m <- mergePorts biased rs
536− xs <- replicateM 9 $ receiveChan m
537− liftIO $ putMVar done $ xs == expected
538− verifyClient "Expected single layer merge to match expected ordering" done
539−
540− -- Two layers of merging
541− testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
542− testNested localNode biasedInner biasedOuter expected = do
543− done <- newEmptyMVar
544− forkProcess localNode $ do
545− rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
546− ms <- mapM (mergePorts biasedInner) rss
547− m <- mergePorts biasedOuter ms
548− xs <- replicateM (9 * 3) $ receiveChan m
549− liftIO $ putMVar done $ xs == expected
550− verifyClient "Expected nested channels to match expeted ordering" done
551−
552− -- Test that if no messages are (immediately) available, the scheduler makes no difference
553− testBlocked :: LocalNode -> Bool -> IO ()
554− testBlocked localNode biased = do
555− vs <- replicateM 3 newEmptyMVar
556− done <- newEmptyMVar
557−
558− forkProcess localNode $ do
559− ss <- liftIO $ mapM readMVar vs
560− case ss of
561− [sa, sb, sc] ->
562− mapM_ ((>> pause 10000) . uncurry sendChan)
563− [ -- a, b, c
564− (sa, 'a')
565− , (sb, 'b')
566− , (sc, 'c')
567− -- a, c, b
568− , (sa, 'a')
569− , (sc, 'c')
570− , (sb, 'b')
571− -- b, a, c
572− , (sb, 'b')
573− , (sa, 'a')
574− , (sc, 'c')
575− -- b, c, a
576− , (sb, 'b')
577− , (sc, 'c')
578− , (sa, 'a')
579− -- c, a, b
580− , (sc, 'c')
581− , (sa, 'a')
582− , (sb, 'b')
583− -- c, b, a
584− , (sc, 'c')
585− , (sb, 'b')
586− , (sa, 'a')
587− ]
588− _ -> die "Something went horribly wrong"
589−
590− forkProcess localNode $ do
591− (ss, rs) <- unzip <$> replicateM 3 newChan
592− liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
593− m <- mergePorts biased rs
594− xs <- replicateM (6 * 3) $ receiveChan m
595− liftIO $ putMVar done $ xs == "abcacbbacbcacabcba"
596−
597− verifyClient "Expected merged ports to match expected ordering" done
598−
599− mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
600− mergePorts True = mergePortsBiased
601− mergePorts False = mergePortsRR
602−
603− charChannel :: Char -> Process (ReceivePort Char)
604− charChannel c = do
605− (sport, rport) <- newChan
606− replicateM_ 3 $ sendChan sport c
607− liftIO $ threadDelay 10000 -- Make sure messages have been sent
608− return rport
609−
610−testTerminate :: TestTransport -> Assertion
611−testTerminate TestTransport{..} = do
612− localNode <- newLocalNode testTransport initRemoteTable
613− runProcess localNode $ do
614− e <- try terminate :: Process (Either ProcessTerminationException ())
615− if either show show e == show ProcessTerminationException
616− then return ()
617− else die "Unexpected result from terminate"
618−
619−testMonitorNode :: TestTransport -> Assertion
620−testMonitorNode TestTransport{..} = do
621− [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
622− done <- newEmptyMVar
623−
624− closeLocalNode node1
625−
626− runProcess node2 $ do
627− ref <- monitorNode (localNodeId node1)
628− receiveWait [
629− match (\(NodeMonitorNotification ref' nid DiedDisconnect) ->
630− return $ ref == ref' && nid == localNodeId node1)
631− ] >>= liftIO . putMVar done
632−
633− verifyClient "Expected NodeMonitorNotification with matching ref & nodeId" done
634−
635−testMonitorLiveNode :: TestTransport -> Assertion
636−testMonitorLiveNode TestTransport{..} = do
637− [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
638− ready <- newEmptyMVar
639− readyr <- newEmptyMVar
640− done <- newEmptyMVar
641−
642− p <- forkProcess node1 $ return ()
643− forkProcess node2 $ do
644− ref <- monitorNode (localNodeId node1)
645− liftIO $ putMVar ready ()
646− liftIO $ takeMVar readyr
647− send p ()
648− receiveWait [
649− match (\(NodeMonitorNotification ref' nid _) ->
650− (return $ ref == ref' && nid == localNodeId node1))
651− ] >>= liftIO . putMVar done
652−
653− takeMVar ready
654− closeLocalNode node1
655− putMVar readyr ()
656−
657− verifyClient "Expected NodeMonitorNotification for LIVE node" done
658−
659−testMonitorChannel :: TestTransport -> Assertion
660−testMonitorChannel TestTransport{..} = do
661− [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
662− gotNotification <- newEmptyMVar
663−
664− pid <- forkProcess node1 $ do
665− sport <- expect :: Process (SendPort ())
666− ref <- monitorPort sport
667− receiveWait [
668− -- reason might be DiedUnknownId if the receive port is GCed before the
669− -- monitor is established (TODO: not sure that this is reasonable)
670− match (\(PortMonitorNotification ref' port' reason) ->
671− return $ ref' == ref && port' == sendPortId sport &&
672− (reason == DiedNormal || reason == DiedUnknownId))
673− ] >>= liftIO . putMVar gotNotification
674−
675− runProcess node2 $ do
676− (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
677− send pid sport
678− liftIO $ threadDelay 100000
679−
680− verifyClient "Expected PortMonitorNotification" gotNotification
681−
682−testRegistry :: TestTransport -> Assertion
683−testRegistry TestTransport{..} = do
684− node <- newLocalNode testTransport initRemoteTable
685− done <- newEmptyMVar
686−
687− pingServer <- forkProcess node ping
688− deadProcess <- forkProcess node (return ())
689−
690− runProcess node $ do
691− register "ping" pingServer
692− whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
693− us <- getSelfPid
694− nsend "ping" (Pong us)
695− receiveWait [
696− matchIf (\(Ping pid') -> pingServer == pid') (const $ return ())
697− ]
698− checkRegException "dead" Nothing deadProcess
699− checkRegException "ping" (Just pingServer) deadProcess
700− try (unregister "dead") >>= checkReg "dead" Nothing
701− liftIO $ putMVar done ()
702−
703− takeMVar done
704−
705− where
706− checkRegException name pid dead =
707− try (register name dead) >>= checkReg name pid
708−
709− checkReg name pid res =
710− case res of
711− Left (ProcessRegistrationException name pid) -> return ()
712− _ -> die $ "Unexpected Registration" ++ show res
713−
714−testRegistryRemoteProcess :: TestTransport -> Assertion
715−testRegistryRemoteProcess TestTransport{..} = do
716− node1 <- newLocalNode testTransport initRemoteTable
717− node2 <- newLocalNode testTransport initRemoteTable
718− done <- newEmptyMVar
719−
720− pingServer <- forkProcess node1 ping
721−
722− runProcess node2 $ do
723− register "ping" pingServer
724− whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
725− us <- getSelfPid
726− nsend "ping" (Pong us)
727− receiveWait [
728− matchIf (\(Ping pid') -> pingServer == pid')
729− (const $ liftIO $ putMVar done ())
730− ]
731−
732− takeMVar done
733−
734−testRemoteRegistry :: TestTransport -> Assertion
735−testRemoteRegistry TestTransport{..} = do
736− node1 <- newLocalNode testTransport initRemoteTable
737− node2 <- newLocalNode testTransport initRemoteTable
738−
739− pingServer <- forkProcess node1 ping
740− deadProcess <- forkProcess node1 (return ())
741−
742− runProcess node2 $ do
743− let nid1 = localNodeId node1
744− registerRemoteAsync nid1 "ping" pingServer
745− receiveWait [
746− matchIf (\(RegisterReply label' _ (Just pid)) ->
747− "ping" == label' && pid == pingServer)
748− (\(RegisterReply _ _ _) -> return ()) ]
749−
750− pid <- verifyWhereIsRemote nid1 "ping"
751− liftIO $ assertBool "Expected pindServer to match pid" $ pingServer == pid
752−
753− us <- getSelfPid
754− nsendRemote nid1 "ping" (Pong us)
755− receiveWait [
756− match (\(Ping pid') -> return $ pingServer == pid')
757− ] >>= liftIO . assertBool "Expected Ping with ping server's ProcessId"
758−
759− -- test that if process was not registered Nothing is returned
760− -- in owner field.
761− registerRemoteAsync nid1 "dead" deadProcess
762− receiveWait [ matchIf (\(RegisterReply label' f mPid) -> "dead" == label')
763− (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
764− ] >>= liftIO . assertBool "Expected False Nothing in RegisterReply"
765−
766− registerRemoteAsync nid1 "ping" deadProcess
767− receiveWait [
768− matchIf (\(RegisterReply label' False mPid) ->
769− "ping" == label' && isJust mPid)
770− (\(RegisterReply _ f (Just pid'')) -> return (not f && pid'' == pingServer))
771− ] >>= liftIO . assertBool "Expected False and (Just alreadyRegisteredPid) in RegisterReply"
772−
773− unregisterRemoteAsync nid1 "dead"
774− receiveWait [
775− matchIf (\(RegisterReply label' _ _) -> "dead" == label')
776− (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
777− ] >>= liftIO . assertBool "Expected False and Nothing in RegisterReply"
778−
779−testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
780−testRemoteRegistryRemoteProcess TestTransport{..} = do
781− node1 <- newLocalNode testTransport initRemoteTable
782− node2 <- newLocalNode testTransport initRemoteTable
783− done <- newEmptyMVar
784−
785− pingServer <- forkProcess node2 ping
786−
787− runProcess node2 $ do
788− let nid1 = localNodeId node1
789− registerRemoteAsync nid1 "ping" pingServer
790− receiveWait [
791− matchIf (\(RegisterReply label' _ _) -> "ping" == label')
792− (\(RegisterReply _ _ _) -> return ()) ]
793− pid <- verifyWhereIsRemote nid1 "ping"
794− liftIO $ assertBool "Expected pingServer to match remote name" $ pingServer == pid
795− us <- getSelfPid
796− nsendRemote nid1 "ping" (Pong us)
797− receiveWait [
798− match (\(Ping pid') -> return $ pingServer == pid')
799− ] >>= liftIO . putMVar done
800−
801− verifyClient "Expected Ping with ping server's ProcessId" done
802−
803−testSpawnLocal :: TestTransport -> Assertion
804−testSpawnLocal TestTransport{..} = do
805− node <- newLocalNode testTransport initRemoteTable
806− done <- newEmptyMVar
807−
808− runProcess node $ do
809− us <- getSelfPid
810−
811− pid <- spawnLocal $ do
812− sport <- expect
813− sendChan sport (1234 :: Int)
814−
815− sport <- spawnChannelLocal $
816− \rport -> (receiveChan rport :: Process Int) >>= send us
817−
818− send pid sport
819− expect >>= liftIO . putMVar done
820−
821− res <- takeMVar done
822− assertBool "Expected 1234 :: Int" $ res == (1234 :: Int)
823−
824−testSpawnAsyncStrictness :: TestTransport -> Assertion
825−testSpawnAsyncStrictness TestTransport{..} = do
826− node <- newLocalNode testTransport initRemoteTable
827− done <- newEmptyMVar
828−
829− runProcess node $ do
830− here <-getSelfNode
831−
832− ev <- try $ spawnAsync here (error "boom")
833− liftIO $ case ev of
834− Right _ -> putMVar done (error "Exception didn't fire")
835− Left (_::SomeException) -> putMVar done (return ())
836−
837− join $ takeMVar done
838−
839−testReconnect :: TestTransport -> Assertion
840−testReconnect TestTransport{..} = do
841− [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
842− let nid1 = localNodeId node1
843− processA <- newEmptyMVar
844− [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
845−
846− forkProcess node1 $ do
847− us <- getSelfPid
848− liftIO $ putMVar processA us
849− msg1 <- expect
850− msg2 <- expect
851− liftIO $ do
852− assertBool "messages did not match" $ msg1 == "message 1" && msg2 == "message 3"
853− putMVar sendTestOk ()
854−
855− forkProcess node2 $ do
856− {-
857− - Make sure there is no implicit reconnect on normal message sending
858− -}
859−
860− them <- liftIO $ readMVar processA
861− send them "message 1" >> liftIO (threadDelay 100000)
862−
863− -- Simulate network failure
864− liftIO $ syncBreakConnection testBreakConnection node1 node2
865−
866−
867− -- Should not arrive
868− send them "message 2"
869−
870− -- Should arrive
871− reconnect them
872− send them "message 3"
873−
874− liftIO $ takeMVar sendTestOk
875−
876− {-
877− - Test that there *is* implicit reconnect on node controller messages
878− -}
879−
880− us <- getSelfPid
881− registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
882− receiveWait [
883− matchIf (\(RegisterReply label' _ _) -> "a" == label')
884− (\(RegisterReply _ _ _) -> return ()) ]
885−
886− _ <- whereisRemote nid1 "a"
887−
888−
889− -- Simulate network failure
890− liftIO $ syncBreakConnection testBreakConnection node1 node2
891−
892− -- This will happen due to implicit reconnect
893− registerRemoteAsync nid1 "b" us
894− receiveWait [
895− matchIf (\(RegisterReply label' _ _) -> "b" == label')
896− (\(RegisterReply _ _ _) -> return ()) ]
897−
898− -- Should happen
899− registerRemoteAsync nid1 "c" us
900− receiveWait [
901− matchIf (\(RegisterReply label' _ _) -> "c" == label')
902− (\(RegisterReply _ _ _) -> return ()) ]
903−
904− -- Check
905− mPid <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
906− liftIO $ assertBool "Expected remote name to be lost" $ mPid == Nothing
907− verifyWhereIsRemote nid1 "b" -- this will suceed because the value is set after thereconnect
908− verifyWhereIsRemote nid1 "c"
909−
910− liftIO $ putMVar registerTestOk ()
911−
912− takeMVar registerTestOk
913−
914−-- | Tests that unreliable messages arrive sorted even when there are connection
915−-- failures.
916−testUSend :: (ProcessId -> Int -> Process ())
917− -> TestTransport -> Int -> Assertion
918−testUSend usendPrim TestTransport{..} numMessages = do
919− [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
920− let nid1 = localNodeId node1
921− nid2 = localNodeId node2
922− processA <- newEmptyMVar
923− usendTestOk <- newEmptyMVar
924−
925− forkProcess node1 $ flip catch (\e -> liftIO $ print (e :: SomeException) ) $ do
926− us <- getSelfPid
927− liftIO $ putMVar processA us
928− them <- expect
929− send them ()
930− _ <- monitor them
931− let -- Collects messages from 'them' until the sender dies.
932− -- Disconnection notifications are ignored.
933− receiveMessages :: Process [Int]
934− receiveMessages = receiveWait
935− [ match $ \mn -> case mn of
936− ProcessMonitorNotification _ _ DiedDisconnect -> do
937− monitor them
938− receiveMessages
939− _ -> return []
940− , match $ \i -> fmap (i :) receiveMessages
941− ]
942− msgs <- receiveMessages
943− let -- Checks that the input list is sorted.
944− isSorted :: [Int] -> Bool
945− isSorted (x : xs@(y : _)) = x <= y && isSorted xs
946− isSorted _ = True
947− -- The list can't be null since there are no failures after sending
948− -- the last message.
949− liftIO $ putMVar usendTestOk $ isSorted msgs && not (null msgs)
950−
951− forkProcess node2 $ do
952− them <- liftIO $ readMVar processA
953− getSelfPid >>= send them
954− expect :: Process ()
955− forM_ [1..numMessages] $ \i -> do
956− liftIO $ testBreakConnection (nodeAddress nid1) (nodeAddress nid2)
957− usendPrim them i
958− liftIO (threadDelay 30000)
959−
960− res <- takeMVar usendTestOk
961− assertBool "Unexpected failure after sending last msg" res
962−
963−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
964−-- in between
965−testMatchAny :: TestTransport -> Assertion
966−testMatchAny TestTransport{..} = do
967− proxyAddr <- newEmptyMVar
968− clientDone <- newEmptyMVar
969−
970− -- Math server
971− forkIO $ do
972− localNode <- newLocalNode testTransport initRemoteTable
973− mathServer <- forkProcess localNode math
974− proxyServer <- forkProcess localNode $ forever $ do
975− msg <- receiveWait [ matchAny return ]
976− forward msg mathServer
977− putMVar proxyAddr proxyServer
978−
979− -- Client
980− forkIO $ do
981− localNode <- newLocalNode testTransport initRemoteTable
982− mathServer <- readMVar proxyAddr
983−
984− runProcess localNode $ do
985− pid <- getSelfPid
986− send mathServer (Add pid 1 2)
987− three <- expect :: Process Double
988− send mathServer (Divide pid 8 2)
989− four <- expect :: Process Double
990− send mathServer (Divide pid 8 0)
991− divByZ <- expect
992− liftIO $ putMVar clientDone (three, four, divByZ)
993−
994− res <- takeMVar clientDone
995− case res of
996− (3, 4, DivByZero) -> return ()
997− _ -> error "Unexpected result"
998−
999−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
1000−-- in between, however we block 'Divide' requests ....
1001−testMatchAnyHandle :: TestTransport -> Assertion
1002−testMatchAnyHandle TestTransport{..} = do
1003− proxyAddr <- newEmptyMVar
1004− clientDone <- newEmptyMVar
1005−
1006− -- Math server
1007− forkIO $ do
1008− localNode <- newLocalNode testTransport initRemoteTable
1009− mathServer <- forkProcess localNode math
1010− proxyServer <- forkProcess localNode $ forever $ do
1011− receiveWait [
1012− matchAny (maybeForward mathServer)
1013− ]
1014− putMVar proxyAddr proxyServer
1015−
1016− -- Client
1017− forkIO $ do
1018− localNode <- newLocalNode testTransport initRemoteTable
1019− mathServer <- readMVar proxyAddr
1020−
1021− runProcess localNode $ do
1022− pid <- getSelfPid
1023− send mathServer (Add pid 1 2)
1024− three <- expect :: Process Double
1025− send mathServer (Divide pid 8 2)
1026− res <- (expectTimeout 100000) :: Process (Maybe Double)
1027− liftIO $ putMVar clientDone $ three == 3 && res == Nothing
1028−
1029− verifyClient "Expected Nothing (i.e. timeout)" clientDone
1030−
1031− where maybeForward :: ProcessId -> Message -> Process (Maybe ())
1032− maybeForward s msg =
1033− handleMessage msg (\m@(Add _ _ _) -> send s m)
1034−
1035−testMatchAnyNoHandle :: TestTransport -> Assertion
1036−testMatchAnyNoHandle TestTransport{..} = do
1037− addr <- newEmptyMVar
1038− clientDone <- newEmptyMVar
1039− serverDone <- newEmptyMVar
1040−
1041− -- Math server
1042− forkIO $ do
1043− localNode <- newLocalNode testTransport initRemoteTable
1044− server <- forkProcess localNode $ forever $ do
1045− receiveWait [
1046− matchAnyIf
1047− -- the condition has type `Add -> Bool`
1048− (\(Add _ _ _) -> True)
1049− -- the match `AbstractMessage -> Process ()` will succeed!
1050− (\m -> do
1051− -- `String -> Process ()` does *not* match the input types however
1052− r <- (handleMessage m (\(_ :: String) -> die "NONSENSE" ))
1053− case r of
1054− Nothing -> return ()
1055− Just _ -> die "NONSENSE")
1056− ]
1057− -- we *must* have removed the message from our mailbox though!!!
1058− res <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
1059− liftIO $ do
1060− assertBool "Expected timeout!" $ res == Nothing
1061− putMVar serverDone ()
1062− putMVar addr server
1063−
1064− -- Client
1065− forkIO $ do
1066− localNode <- newLocalNode testTransport initRemoteTable
1067− server <- readMVar addr
1068−
1069− runProcess localNode $ do
1070− pid <- getSelfPid
1071− send server (Add pid 1 2)
1072− -- we only care about the client having sent a message, so we're done
1073− liftIO $ putMVar clientDone ()
1074−
1075− takeMVar clientDone
1076− takeMVar serverDone
1077−
1078−-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
1079−-- unless the text body @/= "bar"@ - this case should time out rather than
1080−-- removing the message from the process mailbox.
1081−testMatchAnyIf :: TestTransport -> Assertion
1082−testMatchAnyIf TestTransport{..} = do
1083− echoAddr <- newEmptyMVar
1084− clientDone <- newEmptyMVar
1085−
1086− -- echo server
1087− forkIO $ do
1088− localNode <- newLocalNode testTransport initRemoteTable
1089− echoServer <- forkProcess localNode $ forever $ do
1090− receiveWait [
1091− matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
1092− tryHandleMessage
1093− ]
1094− putMVar echoAddr echoServer
1095−
1096− -- Client
1097− forkIO $ do
1098− localNode <- newLocalNode testTransport initRemoteTable
1099− server <- readMVar echoAddr
1100−
1101− runProcess localNode $ do
1102− pid <- getSelfPid
1103− send server (pid, "foo")
1104− foo <- expect
1105− -- provoking what would be the wrong ordering is informative here...
1106−
1107− send server (pid, "bar")
1108− bar <- (expectTimeout 100000) :: Process (Maybe String) -- was Double o_O !?
1109−
1110− send server (pid, "baz")
1111− baz <- expect
1112−
1113− liftIO $ putMVar clientDone (foo, bar, baz)
1114−
1115− res <- takeMVar clientDone
1116− let res' = res == ("foo", Nothing, "baz")
1117− assertBool "Expected timeout due to type mismatch" res'
1118−
1119− where tryHandleMessage :: Message -> Process (Maybe ())
1120− tryHandleMessage msg =
1121− handleMessage msg (\(pid :: ProcessId, (m :: String))
1122− -> do { send pid m; return () })
1123−
1124−testMatchMessageWithUnwrap :: TestTransport -> Assertion
1125−testMatchMessageWithUnwrap TestTransport{..} = do
1126− echoAddr <- newEmptyMVar
1127− clientDone <- newEmptyMVar
1128−
1129− -- echo server
1130− forkIO $ do
1131− localNode <- newLocalNode testTransport initRemoteTable
1132− echoServer <- forkProcess localNode $ forever $ do
1133− msg <- receiveWait [
1134− matchMessage (\(m :: Message) -> do
1135− return m)
1136− ]
1137− unwrapped <- unwrapMessage msg :: Process (Maybe (ProcessId, Message))
1138− case unwrapped of
1139− (Just (p, msg')) -> forward msg' p
1140− Nothing -> die "unable to unwrap the message"
1141− putMVar echoAddr echoServer
1142−
1143− -- Client
1144− forkIO $ do
1145− localNode <- newLocalNode testTransport initRemoteTable
1146− server <- readMVar echoAddr
1147−
1148− runProcess localNode $ do
1149− pid <- getSelfPid
1150− send server (pid, wrapMessage ("foo" :: String))
1151− foo <- expect
1152− send server (pid, wrapMessage ("baz" :: String))
1153− baz <- expect
1154− liftIO $ putMVar clientDone (foo, baz)
1155−
1156− res <- takeMVar clientDone
1157− assertBool "Unexpected unwrapped results" $ res == ("foo", "baz")
1158−
1159−-- Test 'receiveChanTimeout'
1160−testReceiveChanTimeout :: TestTransport -> Assertion
1161−testReceiveChanTimeout TestTransport{..} = do
1162− done <- newEmptyMVar
1163− mvSender <- newEmptyMVar
1164− sendPort <- newEmptyMVar
1165−
1166− forkTry $ do
1167− localNode <- newLocalNode testTransport initRemoteTable
1168− runProcess localNode $ do
1169− -- Create a typed channel
1170− (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1171− liftIO $ putMVar sendPort sp
1172−
1173− -- Wait for a message with a delay. No message arrives, we should get
1174− -- Nothing after the delay.
1175− receiveChanTimeout 100000 rp >>= maybe (return ()) (const $ die "Expected Timeout")
1176−
1177− -- Let the sender know that it can send a message.
1178− liftIO $ putMVar mvSender ()
1179−
1180− -- Wait for a message with a delay again. Now a message arrives after
1181− -- 0.1 seconds
1182− res <- receiveChanTimeout 20000000 rp >>= maybe (die "Timeout") return
1183− liftIO $ assertBool "Expected result to be 'True'" res
1184−
1185− -- Wait for a message with zero timeout: non-blocking check. No message is
1186− -- available, we get Nothing
1187− receiveChanTimeout 0 rp >>= maybe (return ()) (const $ die "Expected Timeout")
1188−
1189− -- Let the sender know that it can send a message.
1190− liftIO $ putMVar mvSender ()
1191−
1192− -- Again, but now there is a message available
1193− fix $ \loop -> do
1194− liftIO $ threadDelay 100000
1195− mb <- receiveChanTimeout 0 rp
1196− case mb of
1197− Just b -> do liftIO $ assertBool "Unexpected Message" $ not b
1198− _ -> loop
1199−
1200− liftIO $ putMVar done ()
1201−
1202− forkTry $ do
1203− localNode <- newLocalNode testTransport initRemoteTable
1204− runProcess localNode $ do
1205− sp <- liftIO $ readMVar sendPort
1206−
1207− liftIO $ takeMVar mvSender
1208− liftIO $ threadDelay 100000
1209− sendChan sp True
1210−
1211− liftIO $ takeMVar mvSender
1212− sendChan sp False
1213−
1214− takeMVar done
1215−
1216−-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
1217−testReceiveChanFeatures :: TestTransport -> Assertion
1218−testReceiveChanFeatures TestTransport{..} = do
1219− done <- newEmptyMVar
1220−
1221− forkTry $ do
1222− localNode <- newLocalNode testTransport initRemoteTable
1223− runProcess localNode $ do
1224− (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
1225− (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1226−
1227− -- Test Functor instance
1228−
1229− sendChan spInt 2
1230− sendChan spBool False
1231−
1232− rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
1233−
1234− receiveChan rp1 >>= liftIO . assertBool "Expected True"
1235− receiveChan rp1 >>= liftIO . assertBool "Expected False" . not
1236−
1237− -- Test Applicative instance
1238−
1239− sendChan spInt 3
1240− sendChan spInt 4
1241−
1242− let rp2 = pure (+) <*> rpInt <*> rpInt
1243−
1244− receiveChan rp2 >>= liftIO . assertBool "Expected 7" . (== 7)
1245−
1246− -- Test Alternative instance
1247−
1248− sendChan spInt 3
1249− sendChan spBool True
1250−
1251− let rp3 = (even <$> rpInt) <|> rpBool
1252−
1253− receiveChan rp3 >>= liftIO . assertBool "Expected False" . not
1254− receiveChan rp3 >>= liftIO . assertBool "Expected True"
1255−
1256− -- Test Monad instance
1257−
1258− sendChan spBool True
1259− sendChan spBool False
1260− sendChan spInt 5
1261−
1262− let rp4 :: ReceivePort Int
1263− rp4 = do b <- rpBool
1264− if b
1265− then rpInt
1266− else return 7
1267−
1268− receiveChan rp4 >>= liftIO . assertBool "Expected 5" . (== 5)
1269− receiveChan rp4 >>= liftIO . assertBool "Expected 7" . (== 7)
1270−
1271− liftIO $ putMVar done ()
1272−
1273− takeMVar done
1274−
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
1281−
1282− pid <- spawnLocal $ do tCh <- newChan :: Process (SendPort (), ReceivePort ())
1283− liftIO $ putMVar tchMV tCh
1284− expect :: Process ()
1285− let (sp, _) = tCh
1286− sendChan sp ()
1287− expect :: Process ()
1288−
1289− mRefPid <- monitor pid
1290−
1291− cPid <- spawnLocal $ do
1292− (sp', rp) <- liftIO $ takeMVar tchMV
1293− -- say "picked up our test channel"
1294− send pid ()
1295− -- say "told pid to continue"
1296− res <- receiveChanTimeout delay rp
1297− case res of
1298− Nothing -> say "initial chan () missing!" >> (liftIO $ putMVar result False)
1299− Just () -> do mr <- monitor pid
1300− pause 10000
1301− -- say "sending pid a second () will cause it to exit"
1302− send pid ()
1303−
1304− -- say "make sure we see a DOWN notification for pid having stopped"
1305− receiveWait [ match (\(_ :: ProcessMonitorNotification) -> return ()) ]
1306−
1307− -- now that pid has died, the send port should be useless...
1308− liftIO $ putMVar tchMV (sp', rp)
1309−
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
1315−
1316− mRefCPid <- monitor cPid
1317−
1318− receiveWait
1319− [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefPid)
1320− (\_ -> return ())
1321− ]
1322−
1323− -- say "seen first pid die..."
1324−
1325− (sendPort, _) <- liftIO $ takeMVar tchMV
1326− sendChan sendPort ()
1327− -- say "sent () after owning pid died"
1328−
1329− -- let cPid know we've written to the channel...
1330− send cPid ()
1331−
1332− receiveWait
1333− [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefCPid)
1334− (\_ -> return ())
1335− ]
1336−
1337− -- say "seen both pids die now..."
1338−
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
1343−
1344−
1345−testKillLocal :: TestTransport -> Assertion
1346−testKillLocal TestTransport{..} = do
1347− localNode <- newLocalNode testTransport initRemoteTable
1348−
1349− pid <- forkProcess localNode $ do
1350− liftIO $ threadDelay 1000000
1351−
1352− runProcess localNode $ do
1353− ref <- monitor pid
1354− us <- getSelfPid
1355− kill pid "TestKill"
1356− mn <- expect
1357− case mn of
1358− ProcessMonitorNotification ref' pid' (DiedException ex) ->
1359− case ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill" of
1360− True -> return ()
1361− False -> die "Invalid ProcessMonitorNotification received"
1362− _ -> die "Something went horribly wrong"
1363−
1364−testKillRemote :: TestTransport -> Assertion
1365−testKillRemote TestTransport{..} = do
1366− node1 <- newLocalNode testTransport initRemoteTable
1367− node2 <- newLocalNode testTransport initRemoteTable
1368−
1369− pid <- forkProcess node1 $ do
1370− liftIO $ threadDelay 1000000
1371−
1372− runProcess node2 $ do
1373− ref <- monitor pid
1374− us <- getSelfPid
1375− kill pid "TestKill"
1376− mn <- expect
1377− case mn of
1378− ProcessMonitorNotification ref' pid' (DiedException reason) ->
1379− case (ref == ref', pid == pid', reason == "killed-by=" ++ show us ++ ",reason=TestKill") of
1380− (True, True, True) -> return ()
1381− (a, b, c) -> do
1382− let a' = if a then "" else "Invalid ref"
1383− let b' = if b then "" else "Invalid pid"
1384− let c' = if c then "" else "Invalid message"
1385− die $ unwords [a', b', c']
1386− _ -> die "Received unexpected message"
1387−
1388−testCatchesExit :: TestTransport -> Assertion
1389−testCatchesExit TestTransport{..} = do
1390− localNode <- newLocalNode testTransport initRemoteTable
1391− done <- newEmptyMVar
1392−
1393− _ <- forkProcess localNode $ do
1394− (die ("foobar", 123 :: Int))
1395− `catchesExit` [
1396− (\_ m -> handleMessage m (\(_ :: String) -> return ()))
1397− , (\_ m -> handleMessage m (\(_ :: Maybe Int) -> return ()))
1398− , (\_ m -> handleMessage m (\(_ :: String, _ :: Int)
1399− -> (liftIO $ putMVar done ()) >> return ()))
1400− ]
1401−
1402− takeMVar done
1403−
1404−testHandleMessageIf :: TestTransport -> Assertion
1405−testHandleMessageIf TestTransport{..} = do
1406− localNode <- newLocalNode testTransport initRemoteTable
1407− done <- newEmptyMVar
1408− _ <- forkProcess localNode $ do
1409− self <- getSelfPid
1410− send self (5 :: Integer, 10 :: Integer)
1411− msg <- receiveWait [ matchMessage return ]
1412− handleMessageIf msg
1413− (\() -> True)
1414− (\() -> die "whoops") >>= maybe (return ())
1415− (const $ die "Expected Mismatch")
1416− handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1417− (\input -> liftIO $ putMVar done input)
1418− return ()
1419−
1420− result <- takeMVar done
1421− expectThat result $ equalTo (5, 10)
1422−
1423−testCatches :: TestTransport -> Assertion
1424−testCatches TestTransport{..} = do
1425− localNode <- newLocalNode testTransport initRemoteTable
1426− done <- newEmptyMVar
1427−
1428− _ <- forkProcess localNode $ do
1429− node <- getSelfNode
1430− (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1431− `catches` [
1432− Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1433− ]
1434−
1435− takeMVar done
1436−
1437−testMaskRestoreScope :: TestTransport -> Assertion
1438−testMaskRestoreScope TestTransport{..} = do
1439− localNode <- newLocalNode testTransport initRemoteTable
1440− parentPid <- newEmptyMVar :: IO (MVar ProcessId)
1441− spawnedPid <- newEmptyMVar :: IO (MVar ProcessId)
1442−
1443− void $ runProcess localNode $ mask $ \unmask -> do
1444− getSelfPid >>= liftIO . putMVar parentPid
1445− void $ spawnLocal $ unmask (getSelfPid >>= liftIO . putMVar spawnedPid)
1446−
1447− parent <- liftIO $ takeMVar parentPid
1448− child <- liftIO $ takeMVar spawnedPid
1449− expectThat parent $ isNot $ equalTo child
1450−
1451−testDie :: TestTransport -> Assertion
1452−testDie TestTransport{..} = do
1453− localNode <- newLocalNode testTransport initRemoteTable
1454− done <- newEmptyMVar
1455−
1456− _ <- forkProcess localNode $ do
1457− (die ("foobar", 123 :: Int))
1458− `catchExit` \_from reason -> do
1459− -- TODO: should verify that 'from' has the right value
1460− let res = reason == ("foobar", 123 :: Int)
1461− liftIO $ putMVar done ()
1462− if res
1463− then return ()
1464− else die "Something went horribly wrong"
1465−
1466− takeMVar done
1467−
1468−testPrettyExit :: TestTransport -> Assertion
1469−testPrettyExit TestTransport{..} = do
1470− localNode <- newLocalNode testTransport initRemoteTable
1471− done <- newEmptyMVar
1472−
1473− _ <- forkProcess localNode $ do
1474− (die "timeout")
1475− `catch` \ex@(ProcessExitException from _) ->
1476− let expected = "exit-from=" ++ (show from)
1477− in do
1478− let res = (show ex) == expected
1479− liftIO $ putMVar done ()
1480− if res
1481− then return ()
1482− else die "Something went horribly wrong"
1483−
1484− takeMVar done
1485−
1486−testExitLocal :: TestTransport -> Assertion
1487−testExitLocal TestTransport{..} = do
1488− localNode <- newLocalNode testTransport initRemoteTable
1489− supervisedDone <- newEmptyMVar
1490− supervisorDone <- newEmptyMVar
1491− -- XXX: we guarantee that exception handler will be set up
1492− -- regardless if forkProcess preserve masking state or not.
1493− handlerSetUp <- newEmptyMVar
1494−
1495− pid <- forkProcess localNode $ do
1496− (liftIO (putMVar handlerSetUp ()) >> expect) `catchExit` \_from reason -> do
1497− -- TODO: should verify that 'from' has the right value
1498− res <- return $ reason == "TestExit"
1499− liftIO $ putMVar supervisedDone ()
1500− if res
1501− then return ()
1502− else die "Something went horribly wrong"
1503−
1504− runProcess localNode $ do
1505− liftIO $ takeMVar handlerSetUp
1506− ref <- monitor pid
1507− exit pid "TestExit"
1508− -- This time the client catches the exception, so it dies normally
1509− mn <- expect
1510− case mn of
1511− ProcessMonitorNotification ref' pid' DiedNormal -> do
1512− let res = ref == ref' && pid == pid'
1513− liftIO $ putMVar supervisorDone ()
1514− if res
1515− then return ()
1516− else die "Something went horribly wrong"
1517− _ -> die "Something went horribly wrong"
1518−
1519− takeMVar supervisedDone
1520− takeMVar supervisorDone
1521−
1522−testExitRemote :: TestTransport -> Assertion
1523−testExitRemote TestTransport{..} = do
1524− node1 <- newLocalNode testTransport initRemoteTable
1525− node2 <- newLocalNode testTransport initRemoteTable
1526− supervisedDone <- newEmptyMVar
1527− supervisorDone <- newEmptyMVar
1528−
1529− pid <- forkProcess node1 $ do
1530− (receiveWait [] :: Process ()) -- block forever
1531− `catchExit` \_from reason -> do
1532− -- TODO: should verify that 'from' has the right value
1533− res <- return $ reason == "TestExit"
1534− liftIO $ putMVar supervisedDone ()
1535− if res
1536− then return ()
1537− else die "Something went horribly wrong"
1538−
1539− runProcess node2 $ do
1540− ref <- monitor pid
1541− exit pid "TestExit"
1542− mn <- expect
1543− case mn of
1544− ProcessMonitorNotification ref' pid' DiedNormal -> do
1545− res' <- return $ ref == ref' && pid == pid'
1546− liftIO $ putMVar supervisorDone ()
1547− if res'
1548− then return ()
1549− else die "Something went horribly wrong"
1550− _ -> die "Something went horribly wrong"
1551−
1552− takeMVar supervisedDone
1553− takeMVar supervisorDone
1554−
1555−testRegistryMonitoring :: TestTransport -> Assertion
1556−testRegistryMonitoring TestTransport{..} = do
1557− node1 <- newLocalNode testTransport initRemoteTable
1558− node2 <- newLocalNode testTransport initRemoteTable
1559−
1560− let nid = localNodeId node2
1561− pid <- forkProcess node1 $ do
1562− self <- getSelfPid
1563− runUntilRegistered nid self
1564− say $ (show self) ++ " registered as " ++ regName
1565− expect :: Process ()
1566− say $ (show self) ++ " exiting normally"
1567−
1568− runProcess node2 $ do
1569− register regName pid
1570− say $ regName ++ " registered to " ++ show pid
1571− res <- whereis regName
1572− send pid ()
1573− say $ " sent finish signal to " ++ show pid
1574− us <- getSelfPid
1575− liftIO $ assertBool "expected (Just pid)" $ res == (Just pid)
1576−
1577−
1578− -- This delay isn't essential!
1579− -- The test case passes perfectly fine without it (feel free to comment out
1580− -- and see), however waiting a few seconds here, makes it much more likely
1581− -- that in delayUntilMaybeUnregistered we will hit the match case right
1582− -- away, and thus not be subjected to a 20 second delay. The value of 4
1583− -- seconds appears to work optimally on osx and across several linux distros
1584− -- running in virtual machines (which is essentially what we do in CI)
1585− void $ receiveTimeout 4000000 [ matchAny return ]
1586−
1587− -- This delay doesn't serve much purpose in the happy path, however if some
1588− -- future patch breaks the cooperative behaviour of node controllers viz
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
1595− -- in either case.
1596− runProcess node2 $ delayUntilMaybeUnregistered nid pid
1597−
1598− regHere <- newEmptyMVar
1599− runProcess node2 $ whereis regName >>= liftIO . putMVar regHere
1600− res <- takeMVar regHere
1601− case res of
1602− Nothing -> return ()
1603− Just pid -> assertBool ("expected Nothing, but got " ++ show pid) False
1604−
1605− where
1606− runUntilRegistered nid us = do
1607− whereisRemoteAsync nid regName
1608− receiveWait [
1609− matchIf (\(WhereIsReply n (Just p)) -> n == regName && p == us)
1610− (const $ return ())
1611− ]
1612−
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 ())
1618− ]
1619− case res of
1620− Just () -> return ()
1621− Nothing -> delayUntilMaybeUnregistered nid p
1622−
1623− regName = "testRegisterRemote"
1624−
1625−testUnsafeSend :: TestTransport -> Assertion
1626−testUnsafeSend TestTransport{..} = do
1627− serverAddr <- newEmptyMVar
1628− clientDone <- newEmptyMVar
1629−
1630− localNode <- newLocalNode testTransport initRemoteTable
1631− void $ forkProcess localNode $ do
1632− self <- getSelfPid
1633− liftIO $ putMVar serverAddr self
1634− clientAddr <- expect
1635− unsafeSend clientAddr ()
1636−
1637− void $ forkProcess localNode $ do
1638− serverPid <- liftIO $ takeMVar serverAddr
1639− getSelfPid >>= unsafeSend serverPid
1640− expect >>= liftIO . putMVar clientDone
1641−
1642− takeMVar clientDone
1643−
1644−testUnsafeUSend :: TestTransport -> Assertion
1645−testUnsafeUSend TestTransport{..} = do
1646− serverAddr <- newEmptyMVar
1647− clientDone <- newEmptyMVar
1648−
1649− localNode <- newLocalNode testTransport initRemoteTable
1650− void $ forkProcess localNode $ do
1651− self <- getSelfPid
1652− liftIO $ putMVar serverAddr self
1653− clientAddr <- expect
1654− unsafeUSend clientAddr ()
1655−
1656− void $ forkProcess localNode $ do
1657− serverPid <- liftIO $ takeMVar serverAddr
1658− getSelfPid >>= unsafeUSend serverPid
1659− expect >>= liftIO . putMVar clientDone
1660−
1661− takeMVar clientDone
1662−
1663−testUnsafeNSend :: TestTransport -> Assertion
1664−testUnsafeNSend TestTransport{..} = do
1665− clientDone <- newEmptyMVar
1666−
1667− localNode <- newLocalNode testTransport initRemoteTable
1668−
1669− pid <- forkProcess localNode $ do
1670− expect >>= liftIO . putMVar clientDone
1671−
1672− void $ runProcess localNode $ do
1673− register "foobar" pid
1674− unsafeNSend "foobar" ()
1675−
1676− takeMVar clientDone
1677−
1678−testUnsafeNSendRemote :: TestTransport -> Assertion
1679−testUnsafeNSendRemote TestTransport{..} = do
1680− clientDone <- newEmptyMVar
1681−
1682− localNode1 <- newLocalNode testTransport initRemoteTable
1683− localNode2 <- newLocalNode testTransport initRemoteTable
1684−
1685− _ <- forkProcess localNode1 $ do
1686− getSelfPid >>= register "foobar"
1687− liftIO $ putMVar clientDone ()
1688− expect >>= liftIO . putMVar clientDone
1689−
1690− takeMVar clientDone
1691− void $ runProcess localNode2 $ do
1692− unsafeNSendRemote (localNodeId localNode1) "foobar" ()
1693−
1694− takeMVar clientDone
1695−
1696−testUnsafeSendChan :: TestTransport -> Assertion
1697−testUnsafeSendChan TestTransport{..} = do
1698− serverAddr <- newEmptyMVar
1699− clientDone <- newEmptyMVar
1700−
1701− localNode <- newLocalNode testTransport initRemoteTable
1702− void $ forkProcess localNode $ do
1703− self <- getSelfPid
1704− liftIO $ putMVar serverAddr self
1705− sp <- expect
1706− unsafeSendChan sp ()
1707−
1708− void $ forkProcess localNode $ do
1709− serverPid <- liftIO $ takeMVar serverAddr
1710− (sp, rp) <- newChan
1711− unsafeSend serverPid sp
1712− receiveChan rp :: Process ()
1713− liftIO $ putMVar clientDone ()
1714−
1715− takeMVar clientDone
1716−
1717−testCallLocal :: TestTransport -> Assertion
1718−testCallLocal TestTransport{..} = do
1719− node <- newLocalNode testTransport initRemoteTable
1720−
1721− -- Testing that (/=) <$> getSelfPid <*> callLocal getSelfPid.
1722− result <- newEmptyMVar
1723− runProcess node $ do
1724− r <- (/=) <$> getSelfPid <*> callLocal getSelfPid
1725− liftIO $ putMVar result r
1726−
1727− takeMVar result >>= assertBool "Expected 'True'"
1728−
1729− -- Testing that when callLocal is interrupted, the worker is interrupted.
1730− ibox <- newIORef False
1731− runProcess node $ do
1732− keeper <- getSelfPid
1733− spawnLocal $ do
1734− caller <- getSelfPid
1735− send keeper caller
1736− onException
1737− (callLocal $ do
1738− onException (do send keeper caller
1739− expect)
1740− (do liftIO $ writeIORef ibox True))
1741− (send keeper ())
1742− caller <- expect
1743− exit caller "test"
1744− expect :: Process ()
1745−
1746− readIORef ibox >>= assertBool "Expected 'True'"
1747−
1748− -- Testing that when the worker raises an exception, the exception is propagated to the parent.
1749− ibox2 <- newIORef False
1750− runProcess node $ do
1751− r <- try (callLocal $ error "e" >> return ())
1752− liftIO $ writeIORef ibox2 $ case r of
1753− Left (ErrorCall "e") -> True
1754− _ -> False
1755−
1756− readIORef ibox >>= assertBool "Expected 'True'"
1757−
1758− -- Test that caller waits for the worker in correct situation
1759− ibox3 <- newIORef False
1760− result3 <- newEmptyMVar
1761− runProcess node $ do
1762− keeper <- getSelfPid
1763− spawnLocal $ do
1764− callLocal $
1765− (do us <- getSelfPid
1766− send keeper us
1767− () <- expect
1768− liftIO yield)
1769− `finally` (liftIO $ writeIORef ibox3 True)
1770− liftIO $ putMVar result3 =<< readIORef ibox3
1771− worker <- expect
1772− send worker ()
1773−
1774− takeMVar result3 >>= assertBool "Expected 'True'"
1775−
1776− -- Test that caller waits for the worker in case when caller gets an exception
1777− ibox4 <- newIORef False
1778− result4 <- newEmptyMVar
1779− runProcess node $ do
1780− keeper <- getSelfPid
1781− spawnLocal $ do
1782− caller <- getSelfPid
1783− callLocal
1784− ((do send keeper caller
1785− expect)
1786− `finally` (liftIO $ writeIORef ibox4 True))
1787− `finally` (liftIO $ putMVar result4 =<< readIORef ibox4)
1788− caller <- expect
1789− exit caller "hi!"
1790−
1791− takeMVar result4 >>= assertBool "Expected 'True'"
1792− -- XXX: Testing that when mask_ $ callLocal p runs p in masked state.
1793−
1794−tests :: TestTransport -> IO [Test]
1795−tests testtrans = return [
1796− testGroup "Basic features" [
1797− testCase "Ping" (testPing testtrans)
1798− , testCase "Math" (testMath testtrans)
1799− , testCase "Timeout" (testTimeout testtrans)
1800− , testCase "Timeout0" (testTimeout0 testtrans)
1801− , testCase "SendToTerminated" (testSendToTerminated testtrans)
1802− , testCase "TypedChannnels" (testTypedChannels testtrans)
1803− , testCase "Terminate" (testTerminate testtrans)
1804− , testCase "RegistryRemoteProcess" (testRegistryRemoteProcess testtrans)
1805− , testCase "RemoteRegistry" (testRemoteRegistry testtrans)
1806− , testCase "RemoteRegistryRemoteProcess" (testRemoteRegistryRemoteProcess testtrans)
1807− , testCase "SpawnLocal" (testSpawnLocal testtrans)
1808− , testCase "SpawnAsyncStrictness" (testSpawnAsyncStrictness testtrans)
1809− , testCase "HandleMessageIf" (testHandleMessageIf testtrans)
1810− , testCase "MatchAny" (testMatchAny testtrans)
1811− , testCase "MatchAnyHandle" (testMatchAnyHandle testtrans)
1812− , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle testtrans)
1813− , testCase "MatchAnyIf" (testMatchAnyIf testtrans)
1814− , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1815− , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1816− , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1817− , testCase "ChanLifecycle" (testChanLifecycle testtrans)
1818− , testCase "KillLocal" (testKillLocal testtrans)
1819− , testCase "KillRemote" (testKillRemote testtrans)
1820− , testCase "Die" (testDie testtrans)
1821− , testCase "PrettyExit" (testPrettyExit testtrans)
1822− , testCase "CatchesExit" (testCatchesExit testtrans)
1823− , testCase "Catches" (testCatches testtrans)
1824− , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1825− , testCase "ExitLocal" (testExitLocal testtrans)
1826− , testCase "ExitRemote" (testExitRemote testtrans)
1827− , testCase "RegistryMonitoring" (testRegistryMonitoring testtrans)
1828− , testCase "TextCallLocal" (testCallLocal testtrans)
1829− -- Unsafe Primitives
1830− , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1831− , testCase "TestUnsafeUSend" (testUnsafeUSend testtrans)
1832− , testCase "TestUnsafeNSend" (testUnsafeNSend testtrans)
1833− , testCase "TestUnsafeNSendRemote" (testUnsafeNSendRemote testtrans)
1834− , testCase "TestUnsafeSendChan" (testUnsafeSendChan testtrans)
1835− -- usend
1836− , testCase "USend" (testUSend usend testtrans 50)
1837− , testCase "UForward"
1838− (testUSend (\p m -> uforward (createUnencodedMessage m) p)
1839− testtrans 50
1840− )
1841− ]
1842− , testGroup "Monitoring and Linking" [
1843− -- Monitoring processes
1844− --
1845− -- The "missing" combinations in the list below don't make much sense, as
1846− -- we cannot guarantee that the monitor reply or link exception will not
1847− -- happen before the unmonitor or unlink
1848− testCase "MonitorNormalTermination" (testMonitorNormalTermination testtrans True False)
1849− , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True False)
1850− , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess testtrans True False)
1851− , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans True False)
1852− , testCase "MonitorDisconnect" (testMonitorDisconnect testtrans True False)
1853− , testCase "LinkUnreachable" (testMonitorUnreachable testtrans False False)
1854− , testCase "LinkNormalTermination" (testMonitorNormalTermination testtrans False False)
1855− , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False False)
1856− , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess testtrans False False)
1857− , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans False False)
1858− , testCase "LinkDisconnect" (testMonitorDisconnect testtrans False False)
1859− , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination testtrans True True)
1860− , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True True)
1861− , testCase "UnmonitorDisconnect" (testMonitorDisconnect testtrans True True)
1862− , testCase "UnlinkNormalTermination" (testMonitorNormalTermination testtrans False True)
1863− , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False True)
1864− , testCase "UnlinkDisconnect" (testMonitorDisconnect testtrans False True)
1865− -- Monitoring nodes and channels
1866− , testCase "MonitorNode" (testMonitorNode testtrans)
1867− , testCase "MonitorLiveNode" (testMonitorLiveNode testtrans)
1868− , testCase "MonitorChannel" (testMonitorChannel testtrans)
1869− -- Reconnect
1870− ]
1871−
1872− -- Tests that fail occasionally and should be revised
1873− , testGroup "Flaky" [
1874− testCase "Reconnect" (testReconnect testtrans)
1875− , testCase "Registry" (testRegistry testtrans)
1876− , testCase "MergeChannels" (testMergeChannels testtrans)
1877− , testCase "MonitorUnreachable" (testMonitorUnreachable testtrans True False)
1878− ]
1879− ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hsdeleted−589
@@ -1,589 +0,0 @@
1−{-# LANGUAGE TemplateHaskell, KindSignatures #-}
2−module Control.Distributed.Process.Tests.Closure (tests) where
3−
4−import Network.Transport.Test (TestTransport(..))
5−
6−import Data.ByteString.Lazy (empty)
7−import Data.IORef
8−import Data.Typeable (Typeable)
9−import Data.Maybe
10−import Control.Monad (join, replicateM, forever, replicateM_, void, when, unless)
11−import Control.Exception (IOException, throw)
12−import Control.Concurrent (forkIO, threadDelay)
13−import Control.Concurrent.MVar
14− ( MVar
15− , newEmptyMVar
16− , readMVar
17− , takeMVar
18− , putMVar
19− , modifyMVar_
20− , newMVar
21− )
22−import Control.Applicative ((<$>))
23−import System.Random (randomIO)
24−import Control.Distributed.Process
25−import Control.Distributed.Process.Closure
26−import Control.Distributed.Process.Node
27−import Control.Distributed.Process.Internal.Types
28− ( NodeId(nodeAddress)
29− , createMessage
30− , messageToPayload
31− )
32−import Control.Distributed.Static (staticLabel, staticClosure)
33−import qualified Network.Transport as NT
34−
35−import Test.HUnit (Assertion)
36−import Test.Framework (Test)
37−import Test.Framework.Providers.HUnit (testCase)
38−
39−--------------------------------------------------------------------------------
40−-- Supporting definitions --
41−--------------------------------------------------------------------------------
42−
43−quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
44−quintuple a b c d e = (a, b, c, d, e)
45−
46−sdictInt :: SerializableDict Int
47−sdictInt = SerializableDict
48−
49−factorial :: Int -> Process Int
50−factorial 0 = return 1
51−factorial n = (n *) <$> factorial (n - 1)
52−
53−addInt :: Int -> Int -> Int
54−addInt x y = x + y
55−
56−putInt :: Int -> MVar Int -> IO ()
57−putInt = flip putMVar
58−
59−sendPid :: ProcessId -> Process ()
60−sendPid toPid = do
61− fromPid <- getSelfPid
62− send toPid fromPid
63−
64−wait :: Int -> Process ()
65−wait = liftIO . threadDelay
66−
67−expectUnit :: Process ()
68−expectUnit = expect
69−
70−isPrime :: Integer -> Process Bool
71−isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
72− where
73− sieve :: [Integer] -> [Integer]
74− sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
75− sieve [] = error "Uh oh -- we've run out of primes"
76−
77−-- | First argument indicates empty closure environment
78−typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
79−typedPingServer () rport = forever $ do
80− sport <- receiveChan rport
81− sendChan sport ()
82−
83−signal :: ProcessId -> Process ()
84−signal pid = send pid ()
85−
86−remotable [ 'factorial
87− , 'addInt
88− , 'putInt
89− , 'sendPid
90− , 'sdictInt
91− , 'wait
92− , 'expectUnit
93− , 'typedPingServer
94− , 'isPrime
95− , 'quintuple
96− , 'signal
97− ]
98−
99−randomElement :: [a] -> IO a
100−randomElement xs = do
101− ix <- randomIO
102− return (xs !! (ix `mod` length xs))
103−
104−remotableDecl [
105− [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
106− dfib (_, reply, 0) = sendChan reply 0
107− dfib (_, reply, 1) = sendChan reply 1
108− dfib (nids, reply, n) = do
109− nid1 <- liftIO $ randomElement nids
110− nid2 <- liftIO $ randomElement nids
111− (sport, rport) <- newChan
112− spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
113− spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
114− n1 <- receiveChan rport
115− n2 <- receiveChan rport
116− sendChan reply $ n1 + n2
117− |]
118− ]
119−
120−-- Just try creating a static polymorphic value
121−staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
122− => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
123−staticQuintuple = $(mkStatic 'quintuple)
124−
125−factorialClosure :: Int -> Closure (Process Int)
126−factorialClosure = $(mkClosure 'factorial)
127−
128−addIntClosure :: Int -> Closure (Int -> Int)
129−addIntClosure = $(mkClosure 'addInt)
130−
131−putIntClosure :: Int -> Closure (MVar Int -> IO ())
132−putIntClosure = $(mkClosure 'putInt)
133−
134−sendPidClosure :: ProcessId -> Closure (Process ())
135−sendPidClosure = $(mkClosure 'sendPid)
136−
137−sendFac :: Int -> ProcessId -> Closure (Process ())
138−sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
139−
140−factorialOf :: Closure (Int -> Process Int)
141−factorialOf = staticClosure $(mkStatic 'factorial)
142−
143−factorial' :: Int -> Closure (Process Int)
144−factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
145−
146−waitClosure :: Int -> Closure (Process ())
147−waitClosure = $(mkClosure 'wait)
148−
149−simulateNetworkFailure :: TestTransport -> LocalNode -> LocalNode -> Process ()
150−simulateNetworkFailure TestTransport{..} from to = liftIO $ do
151− m <- newEmptyMVar
152− _ <- forkProcess to $ getSelfPid >>= liftIO . putMVar m
153− runProcess from $ do
154− them <- liftIO $ takeMVar m
155− pinger <- spawnLocal $ forever $ send them ()
156− _ <- monitorNode (localNodeId to)
157− liftIO $ testBreakConnection (nodeAddress $ localNodeId from)
158− (nodeAddress $ localNodeId to)
159− NodeMonitorNotification _ _ _ <- expect
160− kill pinger "finished"
161− return ()
162−
163−--------------------------------------------------------------------------------
164−-- The tests proper --
165−--------------------------------------------------------------------------------
166−
167−testUnclosure :: TestTransport -> RemoteTable -> Assertion
168−testUnclosure TestTransport{..} rtable = do
169− node <- newLocalNode testTransport rtable
170− done <- newEmptyMVar
171− forkProcess node $ do
172− i <- join . unClosure $ factorialClosure 5
173− liftIO $ putMVar done ()
174− if i == 720
175− then return ()
176− else error "Something went horribly wrong"
177− takeMVar done
178−
179−testBind :: TestTransport -> RemoteTable -> Assertion
180−testBind TestTransport{..} rtable = do
181− node <- newLocalNode testTransport rtable
182− done <- newEmptyMVar
183− runProcess node $ do
184− us <- getSelfPid
185− join . unClosure $ sendFac 6 us
186− (i :: Int) <- expect
187− liftIO $ putMVar done ()
188− if i == 720
189− then return ()
190− else error "Something went horribly wrong"
191− takeMVar done
192−
193−testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
194−testSendPureClosure TestTransport{..} rtable = do
195− serverAddr <- newEmptyMVar
196− serverDone <- newEmptyMVar
197−
198− forkIO $ do
199− node <- newLocalNode testTransport rtable
200− addr <- forkProcess node $ do
201− cl <- expect
202− fn <- unClosure cl :: Process (Int -> Int)
203− (_ :: Int) <- return $ fn 6
204− liftIO $ putMVar serverDone ()
205− putMVar serverAddr addr
206−
207− forkIO $ do
208− node <- newLocalNode testTransport rtable
209− theirAddr <- readMVar serverAddr
210− runProcess node $ send theirAddr (addIntClosure 7)
211−
212− takeMVar serverDone
213−
214−testSendIOClosure :: TestTransport -> RemoteTable -> Assertion
215−testSendIOClosure TestTransport{..} rtable = do
216− serverAddr <- newEmptyMVar
217− serverDone <- newEmptyMVar
218−
219− forkIO $ do
220− node <- newLocalNode testTransport rtable
221− addr <- forkProcess node $ do
222− cl <- expect
223− io <- unClosure cl :: Process (MVar Int -> IO ())
224− liftIO $ do
225− someMVar <- newEmptyMVar
226− io someMVar
227− i <- readMVar someMVar
228− putMVar serverDone ()
229− if i == 5
230− then return ()
231− else error "Something went horribly wrong"
232− putMVar serverAddr addr
233−
234− forkIO $ do
235− node <- newLocalNode testTransport rtable
236− theirAddr <- readMVar serverAddr
237− runProcess node $ send theirAddr (putIntClosure 5)
238−
239− takeMVar serverDone
240−
241−testSendProcClosure :: TestTransport -> RemoteTable -> Assertion
242−testSendProcClosure TestTransport{..} rtable = do
243− serverAddr <- newEmptyMVar
244− clientDone <- newEmptyMVar
245−
246− forkIO $ do
247− node <- newLocalNode testTransport rtable
248− addr <- forkProcess node $ do
249− cl <- expect
250− pr <- unClosure cl :: Process (Int -> Process ())
251− pr 5
252− putMVar serverAddr addr
253−
254− forkIO $ do
255− node <- newLocalNode testTransport rtable
256− theirAddr <- readMVar serverAddr
257− runProcess node $ do
258− pid <- getSelfPid
259− send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
260− i <- expect :: Process Int
261− if i == 5
262− then liftIO $ putMVar clientDone ()
263− else error "Something went horribly wrong"
264−
265− takeMVar clientDone
266−
267−testSpawn :: TestTransport -> RemoteTable -> Assertion
268−testSpawn TestTransport{..} rtable = do
269− serverNodeAddr <- newEmptyMVar
270− clientDone <- newEmptyMVar
271−
272− forkIO $ do
273− node <- newLocalNode testTransport rtable
274− putMVar serverNodeAddr (localNodeId node)
275−
276− forkIO $ do
277− node <- newLocalNode testTransport rtable
278− nid <- readMVar serverNodeAddr
279− runProcess node $ do
280− pid <- getSelfPid
281− pid' <- spawn nid (sendPidClosure pid)
282− pid'' <- expect
283− if pid' == pid''
284− then liftIO $ putMVar clientDone ()
285− else error "Something went horribly wrong"
286−
287− takeMVar clientDone
288−
289−-- | Tests that spawn executes the supplied closure even if the caller dies
290−-- immediately after calling spawn.
291−--
292−-- This situation is of interest because the implementation of spawn has the
293−-- remote peer monitor the caller. See DP-99.
294−--
295−-- The condition is tested by using a transport which refuses to send to the
296−-- remote peer the message that it is waiting to stop monitoring the caller,
297−-- namely @()@.
298−--
299−testSpawnRace :: TestTransport -> RemoteTable -> Assertion
300−testSpawnRace TestTransport{..} rtable = do
301− node1 <- newLocalNode (wrapTransport testTransport) rtable
302− node2 <- newLocalNode testTransport rtable
303−
304− runProcess node1 $ do
305− pid <- getSelfPid
306− spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
307− pid' <- expect :: Process ProcessId
308− pid'' <- expect :: Process ProcessId
309− if pid' == pid''
310− then return ()
311− else error "Something went horribly wrong"
312−
313− where
314−
315− wrapTransport (NT.Transport ne ct) = NT.Transport (fmap (fmap wrapEP) ne) ct
316−
317− wrapEP :: NT.EndPoint -> NT.EndPoint
318− wrapEP e =
319− e { NT.connect = \x y z -> do
320− healthy <- newIORef True
321− fmap (fmap $ wrapConnection healthy e x) $ NT.connect e x y z
322− }
323−
324− wrapConnection :: IORef Bool -> NT.EndPoint -> NT.EndPointAddress
325− -> NT.Connection -> NT.Connection
326− wrapConnection healthy e remoteAddr (NT.Connection s closeC) =
327− flip NT.Connection closeC $ \msg -> do
328− when (msg == messageToPayload (createMessage ())) $ do
329− writeIORef healthy False
330− testBreakConnection (NT.address e) remoteAddr
331− isHealthy <- readIORef healthy
332− if isHealthy then s msg
333− else return $ Left $ NT.TransportError NT.SendFailed ""
334−
335−testCall :: TestTransport -> RemoteTable -> Assertion
336−testCall TestTransport{..} rtable = do
337− serverNodeAddr <- newEmptyMVar
338− clientDone <- newEmptyMVar
339−
340− forkIO $ do
341− node <- newLocalNode testTransport rtable
342− putMVar serverNodeAddr (localNodeId node)
343−
344− forkIO $ do
345− node <- newLocalNode testTransport rtable
346− nid <- readMVar serverNodeAddr
347− runProcess node $ do
348− (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
349− if a == 120
350− then liftIO $ putMVar clientDone ()
351− else error "something went horribly wrong"
352−
353− takeMVar clientDone
354−
355−testCallBind :: TestTransport -> RemoteTable -> Assertion
356−testCallBind TestTransport{..} rtable = do
357− serverNodeAddr <- newEmptyMVar
358− clientDone <- newEmptyMVar
359−
360− forkIO $ do
361− node <- newLocalNode testTransport rtable
362− putMVar serverNodeAddr (localNodeId node)
363−
364− forkIO $ do
365− node <- newLocalNode testTransport rtable
366− nid <- readMVar serverNodeAddr
367− runProcess node $ do
368− (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
369− if a == 120
370− then liftIO $ putMVar clientDone ()
371− else error "Something went horribly wrong"
372−
373− takeMVar clientDone
374−
375−testSeq :: TestTransport -> RemoteTable -> Assertion
376−testSeq TestTransport{..} rtable = do
377− node <- newLocalNode testTransport rtable
378− done <- newEmptyMVar
379− runProcess node $ do
380− us <- getSelfPid
381− join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
382− a :: Int <- expect
383− b :: Int <- expect
384− if a == 120 && b == 720
385− then liftIO $ putMVar done ()
386− else error "Something went horribly wrong"
387− takeMVar done
388−
389−-- Test 'spawnSupervised'
390−--
391−-- Set up a supervisor, spawn a child, then have a third process monitor the
392−-- child. The supervisor then throws an exception, the child dies because it
393−-- was linked to the supervisor, and the third process notices that the child
394−-- dies.
395−testSpawnSupervised :: TestTransport -> RemoteTable -> Assertion
396−testSpawnSupervised TestTransport{..} rtable = do
397− [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
398− [superPid, childPid] <- replicateM 2 $ newEmptyMVar
399− thirdProcessDone <- newEmptyMVar
400− linkUp <- newEmptyMVar
401−
402− forkProcess node1 $ do
403− us <- getSelfPid
404− liftIO $ putMVar superPid us
405− (child, _ref) <- spawnSupervised (localNodeId node2)
406− (sendPidClosure us `seqCP` $(mkStaticClosure 'expectUnit))
407− _ <- expect :: Process ProcessId
408−
409− liftIO $ do putMVar childPid child
410− -- Give the child a chance to link to us
411− takeMVar linkUp
412− throw supervisorDeath
413−
414− forkProcess node2 $ do
415− res <- liftIO $ mapM readMVar [superPid, childPid]
416− case res of
417− [super, child] -> do
418− ref <- monitor child
419− self <- getSelfPid
420− let waitForMOrL = do
421− liftIO $ threadDelay 10000
422− mpinfo <- getProcessInfo child
423− case mpinfo of
424− Nothing -> waitForMOrL
425− Just pinfo ->
426− unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
427− waitForMOrL
428− liftIO $ putMVar linkUp ()
429− -- because monitor message was sent before message to process
430− -- we hope that it will be processed before
431− res' <- expect
432− case res' of
433− (ProcessMonitorNotification ref' pid' (DiedException e)) ->
434− if (ref' == ref && pid' == child &&
435− e == show (ProcessLinkException super
436− (DiedException (show supervisorDeath))))
437− then liftIO $ putMVar thirdProcessDone ()
438− else error "Something went horribly wrong"
439− _ -> error "Something went horribly wrong"
440−
441− _ -> die $ "Something went horribly wrong"
442−
443− takeMVar thirdProcessDone
444− where
445− supervisorDeath :: IOException
446− supervisorDeath = userError "Supervisor died"
447−
448−testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
449−testSpawnInvalid TestTransport{..} rtable = do
450− node <- newLocalNode testTransport rtable
451− done <- newEmptyMVar
452− forkProcess node $ do
453− (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
454− ProcessMonitorNotification ref' pid' _reason <- expect
455− -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
456− res <- return $ ref' == ref && pid == pid'
457− if res == True
458− then liftIO $ putMVar done ()
459− else error "Something went horribly wrong"
460− takeMVar done
461−
462−testClosureExpect :: TestTransport -> RemoteTable -> Assertion
463−testClosureExpect TestTransport{..} rtable = do
464− node <- newLocalNode testTransport rtable
465− done <- newEmptyMVar
466− runProcess node $ do
467− nodeId <- getSelfNode
468− us <- getSelfPid
469− them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
470− send them (1234 :: Int)
471− (res :: Int) <- expect
472− if res == 1234
473− then liftIO $ putMVar done ()
474− else error "Something went horribly wrong"
475− takeMVar done
476−
477−testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
478−testSpawnChannel TestTransport{..} rtable = do
479− done <- newEmptyMVar
480− [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
481−
482− forkProcess node1 $ do
483− pingServer <- spawnChannel
484− (sdictSendPort sdictUnit)
485− (localNodeId node2)
486− ($(mkClosure 'typedPingServer) ())
487− (sendReply, receiveReply) <- newChan
488− sendChan pingServer sendReply
489− receiveChan receiveReply
490− liftIO $ putMVar done ()
491−
492− takeMVar done
493−
494−testTDict :: TestTransport -> RemoteTable -> Assertion
495−testTDict TestTransport{..} rtable = do
496− done <- newEmptyMVar
497− [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
498− forkProcess node1 $ do
499− res <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
500− if res == True
501− then liftIO $ putMVar done ()
502− else error "Something went horribly wrong..."
503− takeMVar done
504−
505−testFib :: TestTransport -> RemoteTable -> Assertion
506−testFib TestTransport{..} rtable = do
507− nodes <- replicateM 4 $ newLocalNode testTransport rtable
508− done <- newEmptyMVar
509−
510− forkProcess (head nodes) $ do
511− (sport, rport) <- newChan
512− spawnLocal $ dfib (map localNodeId nodes, sport, 10)
513− ff <- receiveChan rport :: Process Integer
514− liftIO $ putMVar done ()
515− if ff /= 55
516− then die $ "Something went horribly wrong"
517− else return ()
518−
519− takeMVar done
520−
521−testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
522−testSpawnReconnect testtrans@TestTransport{..} rtable = do
523− [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
524− let nid1 = localNodeId node1
525− -- nid2 = localNodeId node2
526− done <- newEmptyMVar
527− iv <- newMVar (0 :: Int)
528−
529− incr <- forkProcess node1 $ forever $ do
530− () <- expect
531− liftIO $ modifyMVar_ iv (return . (+ 1))
532−
533− forkProcess node2 $ do
534− _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
535− simulateNetworkFailure testtrans node2 node1
536− _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
537− _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
538−
539− liftIO $ threadDelay 100000
540−
541− count <- liftIO $ takeMVar iv
542− res <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
543−
544− liftIO $ putMVar done ()
545− if res /= True
546− then error "Something went horribly wrong"
547− else return ()
548−
549− takeMVar done
550−
551−-- | 'spawn' used to ave a race condition which would be triggered if the
552−-- spawning process terminates immediately after spawning
553−testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
554−testSpawnTerminate TestTransport{..} rtable = do
555− slave <- newLocalNode testTransport rtable
556− master <- newLocalNode testTransport rtable
557− masterDone <- newEmptyMVar
558−
559− runProcess master $ do
560− us <- getSelfPid
561− replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
562− replicateM_ 1000 $ (expect :: Process ())
563− liftIO $ putMVar masterDone ()
564−
565− takeMVar masterDone
566−
567−tests :: TestTransport -> IO [Test]
568−tests testtrans = do
569− let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
570− return
571− [ testCase "Unclosure" (testUnclosure testtrans rtable)
572− , testCase "Bind" (testBind testtrans rtable)
573− , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
574− , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
575− , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
576− , testCase "Spawn" (testSpawn testtrans rtable)
577− , testCase "SpawnRace" (testSpawnRace testtrans rtable)
578− , testCase "Call" (testCall testtrans rtable)
579− , testCase "CallBind" (testCallBind testtrans rtable)
580− , testCase "Seq" (testSeq testtrans rtable)
581− , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
582− , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
583− , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
584− , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
585− , testCase "TDict" (testTDict testtrans rtable)
586− , testCase "Fib" (testFib testtrans rtable)
587− , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
588− , testCase "SpawnReconnect" (testSpawnReconnect testtrans rtable)
589− ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hsdeleted−243
@@ -1,243 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE TemplateHaskell #-}
4−{-# LANGUAGE CPP #-}
5−
6−-----------------------------------------------------------------------------
7−-- |
8−-- Module : Control.Distributed.Process.Tests.Internal.Utils
9−-- Copyright : (c) Tim Watson, Jeff Epstein 2013
10−-- License : BSD3 (see the file LICENSE)
11−--
12−-- Maintainer : Tim Watson
13−-- Stability : experimental
14−-- Portability : non-portable (requires concurrency)
15−--
16−-- This module provides basic building blocks for testing Cloud Haskell programs.
17−-----------------------------------------------------------------------------
18−module Control.Distributed.Process.Tests.Internal.Utils
19− ( TestResult
20− -- ping !
21− , Ping(Ping)
22− , ping
23− , pause
24− , shouldBe
25− , shouldMatch
26− , shouldContain
27− , shouldNotContain
28− , expectThat
29− , synchronisedAssertion
30− -- test process utilities
31− , TestProcessControl
32− , startTestProcess
33− , runTestProcess
34− , testProcessGo
35− , testProcessStop
36− , testProcessReport
37− , delayedAssertion
38− , assertComplete
39− -- logging
40− , Logger()
41− , newLogger
42− , putLogMsg
43− , stopLogger
44− -- runners
45− , tryRunProcess
46− , tryForkProcess
47− , noop
48− , stash
49− ) where
50−
51−#if ! MIN_VERSION_base(4,6,0)
52−import Prelude hiding (catch)
53−#endif
54−import Control.Concurrent
55− ( ThreadId
56− , myThreadId
57− , forkIO
58− )
59−import Control.Concurrent.STM
60− ( TQueue
61− , newTQueueIO
62− , readTQueue
63− , writeTQueue
64− )
65−import Control.Concurrent.MVar
66− ( MVar
67− , newEmptyMVar
68− , takeMVar
69− )
70−
71−import Control.Concurrent
72− ( throwTo
73− )
74−import Control.Concurrent.MVar
75− ( putMVar
76− )
77−import Control.Distributed.Process hiding (finally, catch)
78−import Control.Distributed.Process.Node
79−import Control.Distributed.Process.Serializable()
80−
81−import Control.Exception (AsyncException(ThreadKilled), SomeException)
82−import Control.Monad (forever, void)
83−import Control.Monad.Catch (finally, catch)
84−import Control.Monad.STM (atomically)
85−import Control.Rematch hiding (match)
86−import Control.Rematch.Run
87−import Data.Binary
88−import Data.Typeable (Typeable)
89−
90−import Test.HUnit (Assertion, assertFailure)
91−import Test.HUnit.Base (assertBool)
92−
93−import GHC.Generics
94−import System.Timeout (timeout)
95−
96−-- | A mutable cell containing a test result.
97−type TestResult a = MVar a
98−
99−-- | A simple @Ping@ signal
100−data Ping = Ping
101− deriving (Typeable, Generic, Eq, Show)
102−instance Binary Ping where
103−
104−ping :: ProcessId -> Process ()
105−ping pid = send pid Ping
106−
107−-- | Control signals used to manage /test processes/
108−data TestProcessControl = Stop | Go | Report ProcessId
109− deriving (Typeable, Generic)
110−
111−instance Binary TestProcessControl where
112−
113−data Private = Private
114− deriving (Typeable, Generic)
115−instance Binary Private where
116−
117−-- | Does exactly what it says on the tin, doing so in the @Process@ monad.
118−noop :: Process ()
119−noop = return ()
120−
121−pause :: Int -> Process ()
122−pause delay =
123− void $ receiveTimeout delay [ match (\Private -> return ()) ]
124−
125−synchronisedAssertion :: Eq a
126− => String
127− -> LocalNode
128− -> a
129− -> (TestResult a -> Process ())
130− -> MVar ()
131− -> Assertion
132−synchronisedAssertion note localNode expected testProc lock = do
133− result <- newEmptyMVar
134− _ <- forkProcess localNode $ do
135− acquire lock
136− finally (testProc result)
137− (release lock)
138− assertComplete note result expected
139− where acquire lock' = liftIO $ takeMVar lock'
140− release lock' = liftIO $ putMVar lock' ()
141−
142−stash :: TestResult a -> a -> Process ()
143−stash mvar x = liftIO $ putMVar mvar x
144−
145−expectThat :: a -> Matcher a -> Process ()
146−expectThat a matcher = case res of
147− MatchSuccess -> return ()
148− (MatchFailure msg) -> liftIO $ assertFailure msg
149− where res = runMatch matcher a
150−
151−shouldBe :: a -> Matcher a -> Process ()
152−shouldBe = expectThat
153−
154−shouldContain :: (Show a, Eq a) => [a] -> a -> Process ()
155−shouldContain xs x = expectThat xs $ hasItem (equalTo x)
156−
157−shouldNotContain :: (Show a, Eq a) => [a] -> a -> Process ()
158−shouldNotContain xs x = expectThat xs $ isNot (hasItem (equalTo x))
159−
160−shouldMatch :: a -> Matcher a -> Process ()
161−shouldMatch = expectThat
162−
163−-- | Run the supplied @testProc@ using an @MVar@ to collect and assert
164−-- against its result. Uses the supplied @note@ if the assertion fails.
165−delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
166− (TestResult a -> Process ()) -> Assertion
167−delayedAssertion note localNode expected testProc = do
168− result <- newEmptyMVar
169− _ <- forkProcess localNode $ testProc result
170− assertComplete note result expected
171−
172−-- | Takes the value of @mv@ (using @takeMVar@) and asserts that it matches @a@
173−assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
174−assertComplete msg mv a = do
175− b <- takeMVar mv
176− assertBool msg (a == b)
177−
178−-- synchronised logging
179−
180−data Logger = Logger { _tid :: ThreadId, msgs :: TQueue String }
181−
182−-- | Create a new Logger.
183−-- Logger uses a 'TQueue' to receive and process messages on a worker thread.
184−newLogger :: IO Logger
185−newLogger = do
186− tid <- liftIO $ myThreadId
187− q <- liftIO $ newTQueueIO
188− _ <- forkIO $ logger q
189− return $ Logger tid q
190− where logger q' = forever $ do
191− msg <- atomically $ readTQueue q'
192− putStrLn msg
193−
194−-- | Send a message to the Logger
195−putLogMsg :: Logger -> String -> Process ()
196−putLogMsg logger msg = liftIO $ atomically $ writeTQueue (msgs logger) msg
197−
198−-- | Stop the worker thread for the given Logger
199−stopLogger :: Logger -> IO ()
200−stopLogger = (flip throwTo) ThreadKilled . _tid
201−
202−-- | Starts a test process on the local node.
203−startTestProcess :: Process () -> Process ProcessId
204−startTestProcess proc =
205− spawnLocal $ do
206− getSelfPid >>= register "test-process"
207− runTestProcess proc
208−
209−-- | Runs a /test process/ around the supplied @proc@, which is executed
210−-- whenever the outer process loop receives a 'Go' signal.
211−runTestProcess :: Process () -> Process ()
212−runTestProcess proc = do
213− ctl <- expect
214− case ctl of
215− Stop -> return ()
216− Go -> proc >> runTestProcess proc
217− Report p -> receiveWait [matchAny (\m -> forward m p)] >> runTestProcess proc
218−
219−-- | Tell a /test process/ to continue executing
220−testProcessGo :: ProcessId -> Process ()
221−testProcessGo pid = send pid Go
222−
223−-- | Tell a /test process/ to stop (i.e., 'terminate')
224−testProcessStop :: ProcessId -> Process ()
225−testProcessStop pid = send pid Stop
226−
227−-- | Tell a /test process/ to send a report (message)
228−-- back to the calling process
229−testProcessReport :: ProcessId -> Process ()
230−testProcessReport pid = do
231− self <- getSelfPid
232− send pid $ Report self
233−
234−tryRunProcess :: LocalNode -> Process () -> IO ()
235−tryRunProcess node p = do
236− tid <- liftIO myThreadId
237− void $ timeout (1000000 * 60 * 5 :: Int) $
238− runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
239−
240−tryForkProcess :: LocalNode -> Process () -> IO ProcessId
241−tryForkProcess node p = do
242− tid <- liftIO myThreadId
243− forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hsdeleted−542
@@ -1,542 +0,0 @@
1−{-# LANGUAGE DeriveGeneric #-}
2−{-# LANGUAGE ParallelListComp #-}
3−
4−module Control.Distributed.Process.Tests.Mx (tests) where
5−
6−import Control.Distributed.Process.Tests.Internal.Utils
7−import Network.Transport.Test (TestTransport(..))
8−import Control.Distributed.Process hiding (bracket, finally, try)
9−import Control.Distributed.Process.Internal.Types
10− ( ProcessExitException(..)
11− , unsafeCreateUnencodedMessage
12− )
13−import Control.Distributed.Process.Node
14−import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
15− ( send
16− , nsend
17− , nsendRemote
18− , usend
19− , sendChan
20− )
21−import Control.Distributed.Process.Management
22− ( MxEvent(..)
23− , MxAgentId(..)
24− , mxAgent
25− , mxSink
26− , mxReady
27− , mxReceive
28− , mxDeactivate
29− , liftMX
30− , mxGetLocal
31− , mxSetLocal
32− , mxUpdateLocal
33− , mxNotify
34− , mxBroadcast
35− )
36−import Control.Monad (void, unless)
37−import Control.Monad.Catch(finally, bracket, try)
38−import Control.Rematch (equalTo)
39−import Data.Binary
40−import Data.List (find, sort, intercalate)
41−import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
42−import Data.Typeable
43−import GHC.Generics hiding (from)
44−#if ! MIN_VERSION_base(4,6,0)
45−import Prelude hiding (catch, log)
46−#endif
47−
48−import Test.Framework
49− ( Test
50− , testGroup
51− )
52−import Test.Framework.Providers.HUnit (testCase)
53−
54−data Publish = Publish
55− deriving (Typeable, Generic, Eq)
56−
57−instance Binary Publish where
58−
59−awaitExit :: ProcessId -> Process ()
60−awaitExit pid =
61− withMonitorRef pid $ \ref -> do
62− receiveWait
63− [ matchIf (\(ProcessMonitorNotification r _ _) -> r == ref)
64− (\_ -> return ())
65− ]
66− where
67− withMonitorRef p = bracket (monitor p) unmonitor
68−
69−testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
70−testAgentBroadcast result = do
71− (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
72−
73− publisher <- mxAgent (MxAgentId "publisher-agent") () [
74− mxSink $ \() -> mxBroadcast Publish >> mxReady
75− ]
76−
77− consumer <- mxAgent (MxAgentId "consumer-agent") () [
78− mxSink $ \Publish -> (liftMX $ sendChan resultSP ()) >> mxReady
79− ]
80−
81− mxNotify ()
82− -- Once the publisher has seen our message, it will broadcast the Publish
83− -- and the consumer will see that and send the result to our typed channel.
84− stash result =<< receiveChanTimeout 10000000 resultRP
85−
86− kill publisher "finished"
87− kill consumer "finished"
88−
89−testAgentDualInput :: TestResult (Maybe Int) -> Process ()
90−testAgentDualInput result = do
91− (sp, rp) <- newChan
92− s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
93− mxSink $ (\(i :: Int) -> do
94− mxSetLocal . (+i) =<< mxGetLocal
95− i' <- mxGetLocal
96− if i' == 15
97− then do mxGetLocal >>= liftMX . sendChan sp
98− mxDeactivate "finished"
99− else mxReady)
100− ]
101−
102− mRef <- monitor s
103−
104− mxNotify (1 :: Int)
105− nsend "sum-agent" (3 :: Int)
106− mxNotify (2 :: Int)
107− nsend "sum-agent" (4 :: Int)
108− mxNotify (5 :: Int)
109−
110− stash result =<< receiveChanTimeout 10000000 rp
111− died <- receiveTimeout 10000000 [
112− matchIf (\(ProcessMonitorNotification r _ _) -> r == mRef) (const $ return True)
113− ]
114− died `shouldBe` equalTo (Just True)
115−
116−testAgentPrioritisation :: TestResult [String] -> Process ()
117−testAgentPrioritisation result = do
118−
119− -- TODO: this isn't really testing how we /prioritise/ one source
120− -- over another at all, but I've not yet figured out the right way
121− -- to do so, since we're at the whim of the scheduler with regards
122− -- the timeliness of nsend versus mxNotify anyway.
123−
124− let name = "prioritising-agent"
125− (sp, rp) <- newChan
126− s <- mxAgent (MxAgentId name) ["first"] [
127− mxSink (\(s :: String) -> do
128− mxUpdateLocal (s:)
129− st <- mxGetLocal
130− case length st of
131− n | n == 5 -> do liftMX $ sendChan sp (sort st)
132− mxDeactivate "finished"
133− _ -> mxReceive -- go to the mailbox
134− )
135− ]
136−
137− nsend name "second"
138− mxNotify "third"
139− mxNotify "fourth"
140− nsend name "fifth"
141−
142− stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
143− awaitExit s
144−
145−testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
146−testAgentMailboxHandling result = do
147− (sp, rp) <- newChan
148− agent <- mxAgent (MxAgentId "mailbox-agent") () [
149− mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
150− ]
151−
152− nsend "mailbox-agent" ()
153−
154− stash result =<< receiveChanTimeout 1000000 rp
155− kill agent "finished"
156−
157−testAgentEventHandling :: TestResult Bool -> Process ()
158−testAgentEventHandling result = do
159− us <- getSelfPid
160− -- because this test is a bit racy, let's ensure it can't run indefinitely
161− timer <- spawnLocal $ do
162− pause (1000000 * 60 * 5)
163− -- okay we've waited 5 mins, let's kill the test off if it's stuck...
164− stash result False
165− kill us "Test Timed Out"
166−
167− let initState = [] :: [MxEvent]
168− (rc, rs) <- newChan
169− agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
170− (mxSink $ \ev -> do
171− st <- mxGetLocal
172− let act =
173− case ev of
174− (MxSpawned _) -> mxSetLocal (ev:st)
175− (MxProcessDied _ _) -> mxSetLocal (ev:st)
176− _ -> return ()
177− act >> (liftMX $ sendChan rc ()) >> mxReady),
178− (mxSink $ \(ev, sp :: SendPort Bool) -> do
179− st <- mxGetLocal
180− let found =
181− case ev of
182− MxSpawned p ->
183− isJust $ find (\ev' ->
184− case ev' of
185− (MxSpawned p') -> p' == p
186− _ -> False) st
187− MxProcessDied p r ->
188− isJust $ find (\ev' ->
189− case ev' of
190− (MxProcessDied p' r') -> p' == p && r == r'
191− _ -> False) st
192− _ -> False
193− liftMX $ sendChan sp found
194− mxReady)
195− ]
196−
197− -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
198− faff 2000000 rs
199−
200− (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
201−
202− pid <- spawnLocal $ expect >>= sendChan sp
203−
204− -- By waiting for a monitor notification, we have a
205− -- higher probably that the agent has seen the spawn and died events
206− monitor pid
207−
208− send pid ()
209− rct <- receiveChanTimeout 10000000 rp
210− unless (isJust rct) $ die "No response on channel"
211−
212− pmn <- receiveTimeout 2000000 [ match (\ProcessMonitorNotification{} -> return ()) ]
213− unless (isJust pmn) $ die "No monitor notification arrived"
214−
215− -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
216− faff 2000000 rs
217−
218− (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
219− mxNotify (MxSpawned pid, replyTo)
220− mxNotify (MxProcessDied pid DiedNormal, replyTo)
221−
222− seenAlive <- receiveChanTimeout 10000000 reply
223− seenDead <- receiveChanTimeout 10000000 reply
224−
225− stash result $ foldr (&&) True $ catMaybes [seenAlive, seenDead]
226− kill timer "test-complete"
227− kill agentPid "test-complete"
228− where
229− faff delay port = do
230− res <- receiveChanTimeout delay port
231− unless (isNothing res) $ pause delay
232−
233−testMxRegEvents :: TestResult () -> Process ()
234−testMxRegEvents result = do
235− {- This test only deals with the local case, to ensure that we are being
236− notified in the expected order - the remote cases related to the
237− behaviour of the node controller are contained in the CH test suite. -}
238− ensure (stash result ()) $ do
239− let label = "testMxRegEvents"
240− let agentLabel = "mxRegEvents-agent"
241− let delay = 1000000
242− (regChan, regSink) <- newChan
243− (unRegChan, unRegSink) <- newChan
244− agent <- mxAgent (MxAgentId agentLabel) () [
245− mxSink $ \ev -> do
246− case ev of
247− MxRegistered pid label'
248− | label' == label -> liftMX $ sendChan regChan (label', pid)
249− MxUnRegistered pid label'
250− | label' == label -> liftMX $ sendChan unRegChan (label', pid)
251− _ -> return ()
252− mxReady
253− ]
254−
255− p1 <- spawnLocal expect
256− p2 <- spawnLocal expect
257−
258− register label p1
259− reg1 <- receiveChanTimeout delay regSink
260− reg1 `shouldBe` equalTo (Just (label, p1))
261−
262− unregister label
263− unreg1 <- receiveChanTimeout delay unRegSink
264− unreg1 `shouldBe` equalTo (Just (label, p1))
265−
266− register label p2
267− reg2 <- receiveChanTimeout delay regSink
268− reg2 `shouldBe` equalTo (Just (label, p2))
269−
270− reregister label p1
271− unreg2 <- receiveChanTimeout delay unRegSink
272− unreg2 `shouldBe` equalTo (Just (label, p2))
273−
274− reg3 <- receiveChanTimeout delay regSink
275− reg3 `shouldBe` equalTo (Just (label, p1))
276−
277− mapM_ (flip kill $ "test-complete") [agent, p1, p2]
278−
279−testMxRegMon :: LocalNode -> TestResult () -> Process ()
280−testMxRegMon remoteNode result = do
281− ensure (stash result ()) $ do
282− -- ensure that when a registered process dies, we get a notification that
283− -- it has been unregistered as well as seeing the name get removed
284− let label1 = "aaaaa"
285− let label2 = "bbbbb"
286− let isValid l = l == label1 || l == label2
287− let agentLabel = "mxRegMon-agent"
288− let delay = 1000000
289− (regChan, regSink) <- newChan
290− (unRegChan, unRegSink) <- newChan
291− agent <- mxAgent (MxAgentId agentLabel) () [
292− mxSink $ \ev -> do
293− case ev of
294− MxRegistered pid label
295− | isValid label -> liftMX $ sendChan regChan (label, pid)
296− MxUnRegistered pid label
297− | isValid label -> liftMX $ sendChan unRegChan (label, pid)
298− _ -> return ()
299− mxReady
300− ]
301−
302− (sp, rp) <- newChan
303− liftIO $ forkProcess remoteNode $ do
304− getSelfPid >>= sendChan sp
305− expect :: Process ()
306−
307− p1 <- receiveChan rp
308−
309− register label1 p1
310− reg1 <- receiveChanTimeout delay regSink
311− reg1 `shouldBe` equalTo (Just (label1, p1))
312−
313− register label2 p1
314− reg2 <- receiveChanTimeout delay regSink
315− reg2 `shouldBe` equalTo (Just (label2, p1))
316−
317− n1 <- whereis label1
318− n1 `shouldBe` equalTo (Just p1)
319−
320− n2 <- whereis label2
321− n2 `shouldBe` equalTo (Just p1)
322−
323− kill p1 "goodbye"
324−
325− unreg1 <- receiveChanTimeout delay unRegSink
326− unreg2 <- receiveChanTimeout delay unRegSink
327−
328− let evts = [unreg1, unreg2]
329− -- we can't rely on the order of the values in the node controller's
330− -- map (it's either racy to do so, or no such guarantee exists for Data.Map),
331− -- so we simply verify that we received the un-registration events we expect
332− evts `shouldContain` (Just (label1, p1))
333− evts `shouldContain` (Just (label2, p1))
334−
335− kill agent "test-complete"
336−
337−ensure :: Process () -> Process () -> Process ()
338−ensure = flip finally
339−
340−testNSend :: (String -> () -> Process ())
341− -> Maybe LocalNode
342− -> Process ()
343−testNSend op n = do
344− us <- getSelfPid
345− let delay = 5000000
346− let label = "testMxSend" ++ (show us)
347− let isValid = isValidLabel n label
348−
349− testMxSend n label $ \p1 sink -> do
350− register label p1
351− reg1 <- receiveChanTimeout delay sink
352− case reg1 of
353− Just (MxRegistered pd lb)
354− | pd == p1 && isValid lb -> return ()
355− _ -> die $ "Reg-Failed: " ++ show reg1
356−
357− op label ()
358−
359− sent <- receiveChanTimeout delay sink
360− case sent of
361− Just (MxSentToName lb by _)
362− | by == us && isValid lb -> return True
363− _ -> die $ "Send-Failed: " ++ show sent
364−
365− where
366− isValidLabel nd l1 l2
367− | l2 == l2 = True
368− | isJust nd = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust nd)
369− | otherwise = False
370−
371−testSend :: (ProcessId -> () -> Process ())
372− -> Maybe LocalNode
373− -> Process ()
374−testSend op n = do
375− us <- getSelfPid
376− let label = "testMxSend" ++ (show us)
377− testMxSend n label $ \p1 sink -> do
378− -- initiate a send
379− op p1 ()
380−
381− -- verify the management event
382− sent <- receiveChanTimeout 5000000 sink
383− case sent of
384− Just (MxSent pidTo pidFrom _)
385− | pidTo == p1 && pidFrom == us -> return True
386− _ -> return False
387−
388−testChan :: (SendPort () -> () -> Process ())
389− -> Maybe LocalNode
390− -> Process ()
391−testChan op n = testMxSend n "" $ \p1 sink -> do
392−
393− us <- getSelfPid
394− send p1 us
395−
396− cleared <- receiveChanTimeout 2000000 sink
397− case cleared of
398− Just (MxSent pidTo pidFrom _)
399− | pidTo == p1 && pidFrom == us -> return ()
400− _ -> die "Received uncleared Mx Event"
401−
402− chan <- receiveTimeout 5000000 [ match return ]
403− let ch' = fromMaybe (error "No reply chan received") chan
404−
405− -- initiate a send
406− op ch' ()
407−
408− -- verify the management event
409− sent <- receiveChanTimeout 5000000 sink
410− case sent of
411− Just (MxSentToPort sId spId _)
412− | sId == us && spId == sendPortId ch' -> return True
413− _ -> return False
414−
415−type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
416−
417−testMxSend :: Maybe LocalNode -> String -> SendTest -> Process ()
418−testMxSend mNode label test = do
419− us <- getSelfPid
420− (sp, rp) <- newChan
421− (chan, sink) <- newChan
422− agent <- mxAgent (MxAgentId $ agentLabel us) () [
423− mxSink $ \ev -> do
424− case ev of
425− m@(MxSentToPort _ cid _)
426− | cid /= sendPortId chan
427− && cid /= sendPortId sp -> liftMX $ sendChan chan m
428− m@(MxSent _ fromPid _)
429− | fromPid == us -> liftMX $ sendChan chan m
430− m@(MxSentToName _ fromPid _)
431− | fromPid == us -> liftMX $ sendChan chan m
432− m@(MxRegistered _ name)
433− | name == label -> liftMX $ sendChan chan m
434− _ -> return ()
435− mxReady
436− ]
437−
438− case mNode of
439− Nothing -> void $ spawnLocal (proc sp)
440− Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
441−
442− p1 <- receiveChanTimeout 2000000 rp
443− unless (isJust p1) $ die "Timed out waiting for ProcessId"
444− res <- try $ test (fromJust p1) sink
445−
446− kill agent "bye"
447− kill (fromJust p1) "bye"
448−
449− case res of
450− Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
451− Right tr -> tr `shouldBe` equalTo True
452−
453−
454− where
455− agentLabel s = "mx-unsafe-check-agent-" ++ show s
456− proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
457−
458− go :: Maybe (ReceivePort ()) -> Process ()
459− go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
460− go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
461−
462− replyChannel p' = do
463− (s, r) <- newChan
464− send p' s
465− return r
466−
467−tests :: TestTransport -> IO [Test]
468−tests TestTransport{..} = do
469− node1 <- newLocalNode testTransport initRemoteTable
470− node2 <- newLocalNode testTransport initRemoteTable
471− return [
472− testGroup "MxAgents" [
473− testCase "EventHandling"
474− (delayedAssertion
475− "expected True, but events where not as expected"
476− node1 True testAgentEventHandling)
477− , testCase "InterAgentBroadcast"
478− (delayedAssertion
479− "expected (), but no broadcast was received"
480− node1 (Just ()) testAgentBroadcast)
481− , testCase "AgentMailboxHandling"
482− (delayedAssertion
483− "expected (Just ()), but no regular (mailbox) input was handled"
484− node1 (Just ()) testAgentMailboxHandling)
485− , testCase "AgentDualInputHandling"
486− (delayedAssertion
487− "expected sum = 15, but the result was Nothing"
488− node1 (Just 15 :: Maybe Int) testAgentDualInput)
489− , testCase "AgentInputPrioritisation"
490− (delayedAssertion
491− "expected [first, second, third, fourth, fifth], but result diverged"
492− node1 (sort ["first", "second",
493− "third", "fourth",
494− "fifth"]) testAgentPrioritisation)
495− ]
496− , testGroup "MxEvents" [
497− testCase "NameRegistrationEvents"
498− (delayedAssertion
499− "expected registration events to map to the correct ProcessId"
500− node1 () testMxRegEvents)
501− , testCase "PostDeathNameUnRegistrationEvents"
502− (delayedAssertion
503− "expected process deaths to result in unregistration events"
504− node1 () (testMxRegMon node2))
505− , testGroup "SendEvents" $ buildTestCases node1 node2
506− ]
507− ]
508− where
509− buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
510− ("NSend", [
511− ("nsend", testNSend nsend)
512− , ("Unsafe.nsend", testNSend Unsafe.nsend)
513− , ("nsendRemote", testNSend (nsendRemote nid))
514− , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
515− ])
516− , ("Send", [
517− ("send", testSend send)
518− , ("Unsafe.send", testSend Unsafe.send)
519− , ("usend", testSend usend)
520− , ("Unsafe.usend", testSend Unsafe.usend)
521− , ("sendChan", testChan sendChan)
522− , ("Unsafe.sendChan", testChan Unsafe.sendChan)
523− ])
524− , ("Forward", [
525− ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526− , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
527− ])
528− ]
529−
530− build :: LocalNode
531− -> LocalNode
532− -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
533− -> [Test]
534− build n ln specs =
535− [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536− testCase (intercalate "-" [caseName, caseSuffix])
537− (runProcess n (caseImpl caseNode))
538− | (caseName, caseImpl) <- groupCases
539− ]
540− | (groupName, groupCases) <- specs
541− , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]
542− ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hsdeleted−154
@@ -1,154 +0,0 @@
1−{-# LANGUAGE TemplateHaskell #-}
2−{-# OPTIONS_GHC -Wall #-}
3−
4−-- | XXX test doesn't work, because failure exceptions don't get propagated. The
5−-- test always claims to succeed, even if it failed.
6−
7−module Control.Distributed.Process.Tests.Receive (tests) where
8−
9−import Network.Transport.Test (TestTransport(..))
10−
11−import Network.Transport (Transport)
12−import Control.Distributed.Process
13−import Control.Distributed.Process.Node
14−
15−import Control.Monad
16−
17−import Test.HUnit (Assertion, (@?=))
18−import Test.Framework (Test)
19−import Test.Framework.Providers.HUnit (testCase)
20−
21−-- Tests:
22−
23−-- 1. 2 matchChans, receive on each one
24−-- 2. matchChan/matchIf, receive on each one
25−-- 3. matchIf/matchChan, receive on each one
26−-- 4. matchIf/matchChan/matchIf, receive on each one
27−
28−recTest1 :: ReceivePort ()
29− -> SendPort String
30− -> ReceivePort String -> ReceivePort String
31− -> Process ()
32−recTest1 wait sync r1 r2 = do
33− forever $ do
34− receiveChan wait
35− r <- receiveWait
36− [ matchChan r1 $ \s -> return ("received1 " ++ s)
37− , matchChan r2 $ \s -> return ("received2 " ++ s)
38− ]
39− sendChan sync r
40−
41−recTest2 :: ReceivePort ()
42− -> SendPort String
43− -> ReceivePort String -> ReceivePort String
44− -> Process ()
45−recTest2 wait sync r1 _ = do
46− forever $ do
47− receiveChan wait
48− r <- receiveWait
49− [ matchChan r1 $ \s -> return ("received1 " ++ s)
50− , matchIf (== "foo") $ \s -> return ("received2 " ++ s)
51− ]
52− sendChan sync r
53−
54−recTest3 :: ReceivePort ()
55− -> SendPort String
56− -> ReceivePort String -> ReceivePort String
57− -> Process ()
58−recTest3 wait sync r1 _ = do
59− forever $ do
60− receiveChan wait
61− r <- receiveWait
62− [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
63− , matchChan r1 $ \s -> return ("received2 " ++ s)
64− ]
65− sendChan sync r
66−
67−recTest4 :: ReceivePort ()
68− -> SendPort String
69− -> ReceivePort String -> ReceivePort String
70− -> Process ()
71−recTest4 wait sync r1 _ = do
72− forever $ do
73− receiveChan wait
74− r <- receiveWait
75− [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
76− , matchChan r1 $ \s -> return ("received2 " ++ s)
77− , matchIf (== "bar") $ \s -> return ("received3 " ++ s)
78− ]
79− sendChan sync r
80−
81−master :: Process ()
82−master = do
83− (waits,waitr) <- newChan
84− (syncs,syncr) <- newChan
85− let go expected = do
86− sendChan waits ()
87− r <- receiveChan syncr
88− liftIO $ print (r, expected, r == expected)
89− liftIO $ r @?= expected
90−
91− liftIO $ putStrLn "---- Test 1 ----"
92− (s1,r1) <- newChan
93− (s2,r2) <- newChan
94− p <- spawnLocal (recTest1 waitr syncs r1 r2)
95−
96− sendChan s1 "a" >> go "received1 a"
97− sendChan s2 "b" >> go "received2 b"
98− sendChan s1 "a" >> sendChan s2 "b" >> go "received1 a"
99− go "received2 b"
100−
101− kill p "BANG"
102−
103− liftIO $ putStrLn "\n---- Test 2 ----"
104− (s1,r1) <- newChan
105− (s2,r2) <- newChan
106− p <- spawnLocal (recTest2 waitr syncs r1 r2)
107−
108− sendChan s1 "a" >> go "received1 a"
109− send p "foo" >> go "received2 foo"
110− sendChan s1 "a" >> send p "foo" >> go "received1 a"
111− sendChan s1 "a" >> send p "bar" >> go "received1 a"
112− go "received2 foo"
113−
114− kill p "BANG"
115−
116− liftIO $ putStrLn "\n---- Test 3 ----"
117− (s1,r1) <- newChan
118− (s2,r2) <- newChan
119− p <- spawnLocal (recTest3 waitr syncs r1 r2)
120−
121− sendChan s1 "a" >> go "received2 a"
122− send p "foo" >> go "received1 foo"
123− sendChan s1 "a" >> send p "foo" >> go "received1 foo"
124− sendChan s1 "a" >> send p "bar" >> go "received2 a"
125− go "received2 a"
126−
127− kill p "BANG"
128−
129− liftIO $ putStrLn "\n---- Test 4 ----"
130− (s1,r1) <- newChan
131− (s2,r2) <- newChan
132− p <- spawnLocal (recTest4 waitr syncs r1 r2)
133−
134− sendChan s1 "a" >> go "received2 a"
135− send p "foo" >> go "received1 foo"
136− send p "bar" >> go "received3 bar"
137− sendChan s1 "a" >> send p "foo" >> go "received1 foo"
138− send p "bar" >> go "received2 a"
139− send p "foo" >> go "received1 foo" >> go "received3 bar"
140−
141− kill p "BANG"
142−
143− terminate
144−
145−testReceive :: Transport -> RemoteTable -> Assertion
146−testReceive transport rtable = do
147− node <- newLocalNode transport rtable
148− runProcess node $ master
149−
150−tests :: TestTransport -> IO [Test]
151−tests TestTransport{..} = do
152− let rtable = initRemoteTable
153− return
154− [ testCase "testReceive" (testReceive testTransport rtable) ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hsdeleted−129
@@ -1,129 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Control.Distributed.Process.Tests.Stats (tests) where
4−
5−import Control.Distributed.Process.Tests.Internal.Utils
6−import Network.Transport.Test (TestTransport(..))
7−
8−import Control.Concurrent.MVar
9− ( MVar
10− , newEmptyMVar
11− , putMVar
12− , takeMVar
13− )
14−import Control.Distributed.Process
15−import Control.Distributed.Process.Node
16−import Data.Binary ()
17−import Data.Typeable ()
18−
19−#if ! MIN_VERSION_base(4,6,0)
20−import Prelude hiding (catch)
21−#endif
22−
23−import Test.Framework
24− ( Test
25− , testGroup
26− )
27−import Test.HUnit (Assertion)
28−import Test.Framework.Providers.HUnit (testCase)
29−
30−testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
31−testLocalDeadProcessInfo result = do
32− pid <- spawnLocal $ do (_ :: String) <- expect; return ()
33− mref <- monitor pid
34− send pid "finish"
35− _ <- receiveWait [
36− matchIf (\(ProcessMonitorNotification ref' pid' r) ->
37− ref' == mref && pid' == pid && r == DiedNormal)
38− (\p -> return p)
39− ]
40− getProcessInfo pid >>= stash result
41−
42−testLocalLiveProcessInfo :: TestResult Bool -> Process ()
43−testLocalLiveProcessInfo result = do
44− self <- getSelfPid
45− node <- getSelfNode
46− register "foobar" self
47−
48− mon <- liftIO $ newEmptyMVar
49− -- TODO: we can't get the mailbox's length
50− -- mapM (send self) ["hello", "there", "mr", "process"]
51− pid <- spawnLocal $ do
52− link self
53− mRef <- monitor self
54− stash mon mRef
55− res <- expect
56− case res of
57− "die" -> return ()
58− _ -> die $ "unexpected message received: " ++ res
59−
60− monRef <- liftIO $ takeMVar mon
61−
62− mpInfo <- getProcessInfo self
63− case mpInfo of
64− Nothing -> stash result False
65− Just p -> verifyPInfo p pid monRef node
66− where verifyPInfo :: ProcessInfo
67− -> ProcessId
68− -> MonitorRef
69− -> NodeId
70− -> Process ()
71− verifyPInfo pInfo pid mref node =
72− stash result $ infoNode pInfo == node &&
73− infoLinks pInfo == [pid] &&
74− infoMonitors pInfo == [(pid, mref)] &&
75−-- infoMessageQueueLength pInfo == Just 4 &&
76− infoRegisteredNames pInfo == ["foobar"]
77−
78−testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
79−testRemoteLiveProcessInfo TestTransport{..} node1 = do
80− serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
81− liftIO $ launchRemote serverAddr
82− serverPid <- liftIO $ takeMVar serverAddr
83− withActiveRemote node1 $ \result -> do
84− self <- getSelfPid
85− link serverPid
86− -- our send op shouldn't overtake link or monitor requests AFAICT
87− -- so a little table tennis should get us synchronised properly
88− send serverPid (self, "ping")
89− pong <- expect
90− pInfo <- getProcessInfo serverPid
91− stash result $ pong == "pong" && pInfo /= Nothing
92− where
93− launchRemote :: MVar ProcessId -> IO ()
94− launchRemote locMV = do
95− node2 <- liftIO $ newLocalNode testTransport initRemoteTable
96− _ <- liftIO $ forkProcess node2 $ do
97− self <- getSelfPid
98− liftIO $ putMVar locMV self
99− _ <- receiveWait [
100− match (\(pid, "ping") -> send pid "pong")
101− ]
102− res <- expect
103− case res of
104− "stop" -> return ()
105− _ -> die $ "unexpected message received: " ++ res
106− return ()
107−
108− withActiveRemote :: LocalNode
109− -> ((TestResult Bool -> Process ()) -> Assertion)
110− withActiveRemote n = do
111− a <- delayedAssertion "getProcessInfo remotePid failed" n True
112− return a
113−
114−tests :: TestTransport -> IO [Test]
115−tests testtrans@TestTransport{..} = do
116− node1 <- newLocalNode testTransport initRemoteTable
117− return [
118− testGroup "Process Info" [
119− testCase "testLocalDeadProcessInfo"
120− (delayedAssertion
121− "expected dead process-info to be ProcessInfoNone"
122− node1 (Nothing) testLocalDeadProcessInfo)
123− , testCase "testLocalLiveProcessInfo"
124− (delayedAssertion
125− "expected process-info to be correctly populated"
126− node1 True testLocalLiveProcessInfo)
127− , testCase "testRemoveLiveProcessInfo"
128− (testRemoteLiveProcessInfo testtrans node1)
129− ] ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hsdeleted−431
@@ -1,431 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Control.Distributed.Process.Tests.Tracing (tests) where
4−
5−import Control.Distributed.Process.Tests.Internal.Utils
6−import Network.Transport.Test (TestTransport(..))
7−
8−import Control.Applicative ((<*))
9−import Control.Concurrent (threadDelay)
10−import Control.Concurrent.MVar
11− ( MVar
12− , newEmptyMVar
13− , newMVar
14− , putMVar
15− , takeMVar
16− )
17−import Control.Distributed.Process
18−import Control.Distributed.Process.Node
19−import Control.Distributed.Process.Debug
20−import Control.Distributed.Process.Management
21− ( MxEvent(..)
22− )
23−import qualified Control.Exception as IO (bracket)
24−import Data.List (isPrefixOf, isSuffixOf)
25−
26−#if ! MIN_VERSION_base(4,6,0)
27−import Prelude hiding (catch, log)
28−#else
29−import Prelude hiding ((<*))
30−#endif
31−
32−import Test.Framework
33− ( Test
34− , testGroup
35− )
36−import Test.Framework.Providers.HUnit (testCase)
37−import System.Environment (getEnvironment)
38−-- These are available in System.Environment only since base 4.7
39−import System.SetEnv (setEnv, unsetEnv)
40−
41−
42−testSpawnTracing :: TestResult Bool -> Process ()
43−testSpawnTracing result = do
44− setTraceFlags defaultTraceFlags {
45− traceSpawned = (Just TraceAll)
46− , traceDied = (Just TraceAll)
47− }
48−
49− evSpawned <- liftIO $ newEmptyMVar
50− evDied <- liftIO $ newEmptyMVar
51− tracer <- startTracer $ \ev -> do
52− case ev of
53− (MxSpawned p) -> liftIO $ putMVar evSpawned p
54− (MxProcessDied p r) -> liftIO $ putMVar evDied (p, r)
55− _ -> return ()
56−
57− (sp, rp) <- newChan
58− pid <- spawnLocal $ sendChan sp ()
59− () <- receiveChan rp
60−
61− tracedAlive <- liftIO $ takeMVar evSpawned
62− (tracedDead, tracedReason) <- liftIO $ takeMVar evDied
63−
64− mref <- monitor tracer
65− stopTracer -- this is asynchronous, so we need to wait...
66− receiveWait [
67− matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
68− ((\_ -> return ()))
69− ]
70− setTraceFlags defaultTraceFlags
71− stash result (tracedAlive == pid &&
72− tracedDead == pid &&
73− tracedReason == DiedNormal)
74−
75−testTraceRecvExplicitPid :: TestResult Bool -> Process ()
76−testTraceRecvExplicitPid result = do
77− res <- liftIO $ newEmptyMVar
78− pid <- spawnLocal $ do
79− self <- getSelfPid
80− expect >>= (flip sendChan) self
81− withFlags defaultTraceFlags {
82− traceRecv = traceOnly [pid]
83− } $ do
84− withTracer
85− (\ev ->
86− case ev of
87− (MxReceived pid' _) -> stash res (pid == pid')
88− _ -> return ()) $ do
89− (sp, rp) <- newChan
90− send pid sp
91− p <- receiveChan rp
92− res' <- liftIO $ takeMVar res
93− stash result (res' && (p == pid))
94− return ()
95−
96−testTraceRecvNamedPid :: TestResult Bool -> Process ()
97−testTraceRecvNamedPid result = do
98− res <- liftIO $ newEmptyMVar
99− pid <- spawnLocal $ do
100− self <- getSelfPid
101− register "foobar" self
102− expect >>= (flip sendChan) self
103− withFlags defaultTraceFlags {
104− traceRecv = traceOnly ["foobar"]
105− } $ do
106− withTracer
107− (\ev ->
108− case ev of
109− (MxReceived pid' _) -> stash res (pid == pid')
110− _ -> return ()) $ do
111− (sp, rp) <- newChan
112− send pid sp
113− p <- receiveChan rp
114− res' <- liftIO $ takeMVar res
115− stash result (res' && (p == pid))
116− return ()
117−
118−testTraceSending :: TestResult Bool -> Process ()
119−testTraceSending result = do
120− pid <- spawnLocal $ (expect :: Process String) >> return ()
121− self <- getSelfPid
122− res <- liftIO $ newEmptyMVar
123− withFlags defaultTraceFlags { traceSend = traceOn } $ do
124− withTracer
125− (\ev ->
126− case ev of
127− (MxSent to from msg) -> do
128− mS <- unwrapMessage msg :: Process (Maybe String)
129− case mS of
130− (Just s) -> do stash res (to == pid && from == self && s == "hello there")
131− stash res (to == pid && from == self)
132− _ -> die "failed state invariant, message type unmatched..."
133− _ ->
134− return ()) $ do
135− send pid "hello there"
136− res' <- liftIO $ takeMVar res
137− stash result res'
138−
139−testTraceRegistration :: TestResult Bool -> Process ()
140−testTraceRegistration result = do
141− (sp, rp) <- newChan
142− pid <- spawnLocal $ do
143− self <- getSelfPid
144− () <- expect
145− register "foobar" self
146− sendChan sp ()
147− () <- expect
148− return ()
149− res <- liftIO $ newEmptyMVar
150− withFlags defaultTraceFlags { traceRegistered = traceOn } $ do
151− withTracer
152− (\ev ->
153− case ev of
154− MxRegistered p s ->
155− stash res (p == pid && s == "foobar")
156− _ ->
157− return ()) $ do
158− _ <- monitor pid
159− send pid ()
160− () <- receiveChan rp
161− send pid ()
162− receiveWait [
163− match (\(ProcessMonitorNotification _ _ _) -> return ())
164− ]
165− res' <- liftIO $ takeMVar res
166− stash result res'
167−
168−testTraceUnRegistration :: TestResult Bool -> Process ()
169−testTraceUnRegistration result = do
170− pid <- spawnLocal $ do
171− () <- expect
172− unregister "foobar"
173− () <- expect
174− return ()
175− register "foobar" pid
176− res <- liftIO $ newEmptyMVar
177− withFlags defaultTraceFlags { traceUnregistered = traceOn } $ do
178− withTracer
179− (\ev ->
180− case ev of
181− MxUnRegistered p n -> do
182− stash res (p == pid && n == "foobar")
183− send pid ()
184− _ ->
185− return ()) $ do
186− mref <- monitor pid
187− send pid ()
188− receiveWait [
189− matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
190− (\_ -> return ())
191− ]
192− res' <- liftIO $ takeMVar res
193− stash result res'
194−
195−testTraceLayering :: TestResult () -> Process ()
196−testTraceLayering result = do
197− pid <- spawnLocal $ do
198− getSelfPid >>= register "foobar"
199− () <- expect
200− traceMessage ("traceMsg", 123 :: Int)
201− return ()
202− withFlags defaultTraceFlags {
203− traceDied = traceOnly [pid]
204− , traceRecv = traceOnly ["foobar"]
205− } $ doTest pid result
206− return ()
207− where
208− doTest :: ProcessId -> MVar () -> Process ()
209− doTest pid result' = do
210− -- TODO: this is pretty gross, even for a test case
211− died <- liftIO $ newEmptyMVar
212− withTracer
213− (\ev ->
214− case ev of
215− MxProcessDied _ _ -> liftIO $ putMVar died ()
216− _ -> return ())
217− ( do {
218− recv <- liftIO $ newEmptyMVar
219− ; withTracer
220− (\ev' ->
221− case ev' of
222− MxReceived _ _ -> liftIO $ putMVar recv ()
223− _ -> return ())
224− ( do {
225− user <- liftIO $ newEmptyMVar
226− ; withTracer
227− (\ev'' ->
228− case ev'' of
229− MxUser _ -> liftIO $ putMVar user ()
230− _ -> return ())
231− (send pid () >> (liftIO $ takeMVar user))
232− ; liftIO $ takeMVar recv
233− })
234− ; liftIO $ takeMVar died
235− })
236− liftIO $ putMVar result' ()
237−
238−testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
239−testRemoteTraceRelay TestTransport{..} result =
240− let flags = defaultTraceFlags { traceSpawned = traceOn }
241− in do
242− node2 <- liftIO $ newLocalNode testTransport initRemoteTable
243− mvNid <- liftIO $ newEmptyMVar
244−
245− -- As well as needing node2's NodeId, we want to
246− -- redirect all its logs back here, to avoid generating
247− -- garbage on stderr for the duration of the test run.
248− -- Here we set up that relay, and then wait for a signal
249− -- that the tracer (on node1) has seen the expected
250− -- MxSpawned message, at which point we're finished
251−
252− pid <- splinchLogger node2 mvNid
253− nid <- liftIO $ takeMVar mvNid
254− mref <- monitor pid
255− observedPid <- liftIO $ newEmptyMVar
256− spawnedPid <- liftIO $ newEmptyMVar
257− setTraceFlagsRemote flags nid
258−
259− withFlags defaultTraceFlags { traceSpawned = traceOn } $ do
260− withTracer
261− (\ev ->
262− case ev of
263− MxSpawned p -> stash observedPid p >> send pid ()
264− _ -> return ()) $ do
265− relayPid <- startTraceRelay nid
266− liftIO $ threadDelay 1000000
267− p <- liftIO $ forkProcess node2 $ do
268− expectTimeout 1000000 :: Process (Maybe ())
269− return ()
270− stash spawnedPid p
271−
272− -- Now we wait for (the outer) pid to exit. This won't happen until
273− -- our tracer has seen the trace event for `p' and sent `p' the
274− -- message it's waiting for prior to exiting
275− receiveWait [
276− matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
277− (\_ -> return ())
278− ]
279−
280− relayRef <- monitor relayPid
281− kill relayPid "stop"
282− receiveWait [
283− matchIf (\(ProcessMonitorNotification rref' _ _) -> rref' == relayRef)
284− (\_ -> return ())
285− ]
286− observed <- liftIO $ takeMVar observedPid
287− expected <- liftIO $ takeMVar spawnedPid
288− stash result (observed == expected)
289− -- and just to be polite...
290− liftIO $ closeLocalNode node2
291−
292− where
293− splinchLogger n2 mv = do
294− mLog <- whereis "logger"
295− case mLog of
296− Nothing -> die "no logger registered"
297− Just log' -> do liftIO $ forkProcess n2 $ do
298− logRelay <- spawnLocal $ relay log'
299− reregister "logger" logRelay
300− getSelfNode >>= stash mv >> (expect :: Process ())
301−
302−
303−-- | Sets the value of an environment variable while executing the given IO
304−-- computation and restores the preceeding value upon completion.
305−withEnv :: String -> String -> IO a -> IO a
306−withEnv var val =
307− IO.bracket (fmap (lookup var) getEnvironment <* setEnv var val)
308− (maybe (unsetEnv var) (setEnv var))
309− . const
310−
311−-- | Tests that one and only one interesting trace message is produced when a
312−-- given action is performed. A message is considered interesting when the given
313−-- function return @True@.
314−testSystemLoggerMsg :: TestTransport
315− -> Process a
316− -> (a -> String -> Bool)
317− -> IO ()
318−testSystemLoggerMsg t action interestingMessage =
319− withEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" "yes" $
320− withEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" "pdnusrl" $ do
321− n <- newLocalNode (testTransport t) initRemoteTable
322−
323− runProcess n $ do
324− self <- getSelfPid
325− reregister "trace.logger" self
326− a <- action
327− let interestingMessage' (_ :: String, msg) = interestingMessage a msg
328− -- Wait for the trace message.
329− receiveWait [ matchIf interestingMessage' $ const $ return () ]
330− -- Only one interesting message should arrive.
331− expectedTimeout <- receiveTimeout
332− 100000
333− [ matchIf interestingMessage' $ const $ return () ]
334− case expectedTimeout of
335− Nothing -> return ()
336− Just _ -> die "Unexpected message arrived..."
337−
338−
339−-- | Tests that one and only one trace message is produced when a message is
340−-- received.
341−testSystemLoggerMxReceive :: TestTransport -> IO ()
342−testSystemLoggerMxReceive t = testSystemLoggerMsg t
343− (getSelfPid >>= flip send ())
344− (\_ msg -> "MxReceived" `isPrefixOf` msg
345− -- discard traces of internal messages
346− && not (":: RegisterReply" `isSuffixOf` msg)
347− )
348−
349−-- | Tests that one and only one trace message is produced when a message is
350−-- sent.
351−testSystemLoggerMxSent :: TestTransport -> IO ()
352−testSystemLoggerMxSent t = testSystemLoggerMsg t
353− (getSelfPid >>= flip send ())
354− (const $ isPrefixOf "MxSent")
355−
356−-- | Tests that one and only one trace message is produced when a process dies.
357−testSystemLoggerMxProcessDied :: TestTransport -> IO ()
358−testSystemLoggerMxProcessDied t = testSystemLoggerMsg t
359− (spawnLocal $ return ())
360− (\pid -> isPrefixOf $ "MxProcessDied " ++ show pid)
361−
362−-- | Tests that one and only one trace message appears when a process spawns.
363−testSystemLoggerMxSpawned :: TestTransport -> IO ()
364−testSystemLoggerMxSpawned t = testSystemLoggerMsg t
365− (spawnLocal $ return ())
366− (\pid -> isPrefixOf $ "MxSpawned " ++ show pid)
367−
368−-- | Tests that one and only one trace message appears when a process is
369−-- registered.
370−testSystemLoggerMxRegistered :: TestTransport -> IO ()
371−testSystemLoggerMxRegistered t = testSystemLoggerMsg t
372− (getSelfPid >>= register "a" >> getSelfPid)
373− (\self -> isPrefixOf $ "MxRegistered " ++ show self ++ " " ++ show "a")
374−
375−-- | Tests that one and only one trace message appears when a process is
376−-- unregistered.
377−testSystemLoggerMxUnRegistered :: TestTransport -> IO ()
378−testSystemLoggerMxUnRegistered t = testSystemLoggerMsg t
379− (getSelfPid >>= register "a" >> unregister "a" >> getSelfPid)
380− (\self -> isPrefixOf $ "MxUnRegistered " ++ show self ++ " " ++ show "a")
381−
382−tests :: TestTransport -> IO [Test]
383−tests testtrans@TestTransport{..} = do
384− node1 <- newLocalNode testTransport initRemoteTable
385− -- if we execute the test cases in parallel, the
386− -- various tracers will race with one another and
387− -- we'll get garbage results (or worse, deadlocks)
388− lock <- liftIO $ newMVar ()
389− return [
390− testGroup "Tracing" [
391− testCase "Spawn Tracing"
392− (synchronisedAssertion
393− "expected dead process-info to be ProcessInfoNone"
394− node1 True testSpawnTracing lock)
395− , testCase "Recv Tracing (Explicit Pid)"
396− (synchronisedAssertion
397− "expected a recv trace for the supplied pid"
398− node1 True testTraceRecvExplicitPid lock)
399− , testCase "Recv Tracing (Named Pid)"
400− (synchronisedAssertion
401− "expected a recv trace for the process registered as 'foobar'"
402− node1 True testTraceRecvNamedPid lock)
403− , testCase "Trace Send(er)"
404− (synchronisedAssertion
405− "expected a 'send' trace with the requisite fields set"
406− node1 True testTraceSending lock)
407− , testCase "Trace Registration"
408− (synchronisedAssertion
409− "expected a 'registered' trace"
410− node1 True testTraceRegistration lock)
411− , testCase "Trace Unregistration"
412− (synchronisedAssertion
413− "expected an 'unregistered' trace"
414− node1 True testTraceUnRegistration lock)
415− , testCase "Trace Layering"
416− (synchronisedAssertion
417− "expected blah"
418− node1 () testTraceLayering lock)
419− , testCase "Remote Trace Relay"
420− (synchronisedAssertion
421− "expected blah"
422− node1 True (testRemoteTraceRelay testtrans) lock)
423− , testGroup "SystemLoggerTracer"
424− [ testCase "MxReceive" $ testSystemLoggerMxReceive testtrans
425− , testCase "MxSent" $ testSystemLoggerMxSent testtrans
426− , testCase "MxProcessDied" $ testSystemLoggerMxProcessDied testtrans
427− , testCase "MxSpawned" $ testSystemLoggerMxSpawned testtrans
428− , testCase "MxRegistered" $ testSystemLoggerMxRegistered testtrans
429− , testCase "MxUnRegistered" $ testSystemLoggerMxUnRegistered testtrans
430− ]
431− ] ]
distributed-process-tests/src/Network/Transport/Test.hsdeleted−12
@@ -1,12 +0,0 @@
1−module Network.Transport.Test where
2−
3−import qualified Network.Transport as NT
4−import Data.Typeable (Typeable)
5−
6−-- | Extra operations required of transports for the purposes of testing.
7−data TestTransport = TestTransport
8− { -- | The transport to use for testing.
9− testTransport :: NT.Transport
10− -- | IO action to perform to simulate losing a connection.
11− , testBreakConnection :: NT.EndPointAddress -> NT.EndPointAddress -> IO ()
12− } deriving (Typeable)
distributed-process-tests/tests/runInMemory.hsdeleted−30
@@ -1,30 +0,0 @@
1−-- Run tests using the TCP transport.
2−
3−module Main where
4−
5−import TEST_SUITE_MODULE (tests)
6−
7−import Network.Transport.Test (TestTransport(..))
8−import Network.Transport.InMemory
9−import Test.Framework (defaultMainWithArgs)
10−
11−import System.Environment (getArgs)
12−
13−main :: IO ()
14−main = do
15− (transport, internals) <- createTransportExposeInternals
16− ts <- tests TestTransport
17− { testTransport = transport
18− , testBreakConnection = \addr1 addr2 -> breakConnection internals addr1 addr2 "user error"
19− }
20− args <- getArgs
21− -- Tests are time sensitive. Running the tests concurrently can slow them
22− -- down enough that threads using threadDelay would wake up later than
23− -- expected, thus changing the order in which messages were expected.
24− -- Therefore we run the tests sequentially by passing "-j 1" to
25− -- test-framework. This does not solve the issue but makes it less likely.
26− --
27− -- The problem was first detected with
28− -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
29− -- in particular.
30− defaultMainWithArgs ts ("-j" : "1" : args)
distributed-process-tests/tests/runTCP.hsdeleted−47
@@ -1,47 +0,0 @@
1−-- Run tests using the TCP transport.
2−--
3−module Main where
4−
5−import TEST_SUITE_MODULE (tests)
6−
7−import Network.Transport.Test (TestTransport(..))
8−import Network.Socket (sClose)
9−import Network.Transport.TCP
10− ( createTransportExposeInternals
11− , TransportInternals(socketBetween)
12− , defaultTCPParameters
13− , defaultTCPAddr
14− , TCPParameters(..)
15− )
16−import Test.Framework (defaultMainWithArgs)
17−
18−import Control.Concurrent (threadDelay)
19−import Control.Exception (IOException, try)
20−import System.Environment (getArgs)
21−import System.IO
22−
23−main :: IO ()
24−main = do
25− hSetBuffering stdout LineBuffering
26− hSetBuffering stderr LineBuffering
27− Right (transport, internals) <-
28− createTransportExposeInternals (defaultTCPAddr "127.0.0.1" "8080")
29− defaultTCPParameters { transportConnectTimeout = Just 3000000 }
30− ts <- tests TestTransport
31− { testTransport = transport
32− , testBreakConnection = \addr1 addr2 -> do
33− esock <- try $ socketBetween internals addr1 addr2
34− either (\e -> const (return ()) (e :: IOException)) sClose esock
35− threadDelay 10000
36− }
37− args <- getArgs
38− -- Tests are time sensitive. Running the tests concurrently can slow them
39− -- down enough that threads using threadDelay would wake up later than
40− -- expected, thus changing the order in which messages were expected.
41− -- Therefore we run the tests sequentially by passing "-j 1" to
42− -- test-framework. This does not solve the issue but makes it less likely.
43− --
44− -- The problem was first detected with
45− -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
46− -- in particular.
47− defaultMainWithArgs ts ("-j" : "1" : args)
distributed-process.cabaldeleted−168
@@ -1,168 +0,0 @@
1−Name: distributed-process
2−Version: 0.7.4
3−Cabal-Version: >=1.8
4−Build-Type: Simple
5−License: BSD3
6−License-File: LICENSE
7−Copyright: Well-Typed LLP, Tweag I/O Limited
8−Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9−Maintainer: Tim Watson <watson.timothy@gmail.com>
10−Stability: experimental
11−Homepage: http://haskell-distributed.github.com/
12−Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
13−Synopsis: Cloud Haskell: Erlang-style concurrency in Haskell
14−Description: This is an implementation of Cloud Haskell, as described in
15− /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,
16− and Simon Peyton Jones
17− (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
18− although some of the details are different. The precise message
19− passing semantics are based on /A unified semantics for future Erlang/
20− by Hans Svensson, Lars-&#xc5;ke Fredlund and Clara Benac Earle.
21−
22− You will probably also want to install a Cloud Haskell backend such
23− as distributed-process-simplelocalnet.
24−Tested-With: GHC==7.10.3 GHC==8.0.2 GHC==8.2.2 GHC==8.4.4
25−Category: Control
26−extra-source-files: ChangeLog
27−
28−Source-Repository head
29− Type: git
30− Location: https://github.com/haskell-distributed/distributed-process
31− SubDir: distributed-process
32−
33−flag th
34− description: Build with Template Haskell support
35− default: True
36−
37−flag old-locale
38− description: If false then depend on time >= 1.5.
39− .
40− If true then depend on time < 1.5 together with old-locale.
41− default: False
42−
43−Library
44− Build-Depends: base >= 4.6 && < 5,
45− binary >= 0.6.3 && < 0.10,
46− hashable >= 1.2.0.5 && < 1.3,
47− network-transport >= 0.4.1.0 && < 0.6,
48− stm >= 2.4 && < 2.6,
49− transformers >= 0.2 && < 0.6,
50− mtl >= 2.0 && < 2.4,
51− data-accessor >= 0.2 && < 0.3,
52− bytestring >= 0.9 && < 0.11,
53− random >= 1.0 && < 1.2,
54− distributed-static >= 0.2 && < 0.4,
55− rank1dynamic >= 0.1 && < 0.5,
56− syb >= 0.3 && < 0.8,
57− exceptions >= 0.5,
58− containers >= 0.5 && < 0.7,
59− deepseq >= 1.3.0.1 && < 1.6
60− Exposed-modules: Control.Distributed.Process,
61− Control.Distributed.Process.Closure,
62− Control.Distributed.Process.Debug,
63− Control.Distributed.Process.Internal.BiMultiMap,
64− Control.Distributed.Process.Internal.Closure.BuiltIn,
65− Control.Distributed.Process.Internal.Closure.Explicit,
66− Control.Distributed.Process.Internal.CQueue,
67− Control.Distributed.Process.Internal.Messaging,
68− Control.Distributed.Process.Internal.Primitives,
69− Control.Distributed.Process.Internal.Spawn,
70− Control.Distributed.Process.Internal.StrictContainerAccessors,
71− Control.Distributed.Process.Internal.StrictList,
72− Control.Distributed.Process.Internal.StrictMVar,
73− Control.Distributed.Process.Internal.Types,
74− Control.Distributed.Process.Internal.WeakTQueue,
75− Control.Distributed.Process.Management,
76− Control.Distributed.Process.Node,
77− Control.Distributed.Process.Serializable,
78− Control.Distributed.Process.UnsafePrimitives
79− Control.Distributed.Process.Management.Internal.Agent,
80− Control.Distributed.Process.Management.Internal.Bus,
81− Control.Distributed.Process.Management.Internal.Types,
82− Control.Distributed.Process.Management.Internal.Trace.Primitives,
83− Control.Distributed.Process.Management.Internal.Trace.Remote,
84− Control.Distributed.Process.Management.Internal.Trace.Types,
85− Control.Distributed.Process.Management.Internal.Trace.Tracer
86− ghc-options: -Wall
87− HS-Source-Dirs: src
88− other-extensions: BangPatterns
89− CPP
90− DeriveDataTypeable
91− DeriveFunctor
92− DeriveGeneric
93− ExistentialQuantification
94− FlexibleInstances
95− GADTs
96− GeneralizedNewtypeDeriving
97− KindSignatures
98− MagicHash
99− PatternGuards
100− RankNTypes
101− RecordWildCards
102− ScopedTypeVariables
103− StandaloneDeriving
104− TypeFamilies
105− TypeSynonymInstances
106− UnboxedTuples
107− UndecidableInstances
108− if flag(old-locale)
109− Build-Depends: time < 1.5, old-locale >= 1.0 && <1.1
110− else
111− Build-Depends: time >= 1.5
112− if flag(th)
113− other-extensions: TemplateHaskell
114− Build-Depends: template-haskell >= 2.6
115− Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
116− CPP-Options: -DTemplateHaskellSupport
117−
118−-- Tests are in distributed-process-test package, for convenience.
119−
120−benchmark distributed-process-throughput
121− Type: exitcode-stdio-1.0
122− Build-Depends: base >= 4.6 && < 5,
123− distributed-process,
124− network-transport-tcp >= 0.3 && < 0.7,
125− bytestring >= 0.9 && < 0.11,
126− binary >= 0.6.3 && < 0.10
127− Main-Is: benchmarks/Throughput.hs
128− ghc-options: -Wall
129−
130−benchmark distributed-process-latency
131− Type: exitcode-stdio-1.0
132− Build-Depends: base >= 4.6 && < 5,
133− distributed-process,
134− network-transport-tcp >= 0.3 && < 0.7,
135− bytestring >= 0.9 && < 0.11,
136− binary >= 0.6.3 && < 0.10
137− Main-Is: benchmarks/Latency.hs
138− ghc-options: -Wall
139−
140−benchmark distributed-process-channels
141− Type: exitcode-stdio-1.0
142− Build-Depends: base >= 4.6 && < 5,
143− distributed-process,
144− network-transport-tcp >= 0.3 && < 0.7,
145− bytestring >= 0.9 && < 0.11,
146− binary >= 0.6.3 && < 0.10
147− Main-Is: benchmarks/Channels.hs
148− ghc-options: -Wall
149−
150−benchmark distributed-process-spawns
151− Type: exitcode-stdio-1.0
152− Build-Depends: base >= 4.6 && < 5,
153− distributed-process,
154− network-transport-tcp >= 0.3 && < 0.7,
155− bytestring >= 0.9 && < 0.11,
156− binary >= 0.6.3 && < 0.10
157− Main-Is: benchmarks/Spawns.hs
158− ghc-options: -Wall
159−
160−benchmark distributed-process-ring
161− Type: exitcode-stdio-1.0
162− Build-Depends: base >= 4.6 && < 5,
163− distributed-process,
164− network-transport-tcp >= 0.3 && < 0.7,
165− bytestring >= 0.9 && < 0.11,
166− binary >= 0.6.3 && < 0.10
167− Main-Is: benchmarks/ProcessRing.hs
168− ghc-options: -Wall -threaded -O2 -rtsopts
distributed-process/ChangeLognew file+67
@@ -0,0 +1,67 @@
1+<<date>> Edsko de Vries <edsko@well-typed.com> 0.3.2
2+
3+* Improved treatment of network failure, using new failure semantics of
4+Network.Transport.
5+* Make NodeId Typeable
6+* Extend Template Haskell support with "remotableDec" so that you can refer to
7+$(mkClosure 'f) within the body of "f".
8+
9+2012-08-16 Edsko de Vries <edsko@well-typed.com> 0.3.1
10+
11+* Fix memory leaks
12+* Make Template Haskell support optional
13+* Relax dependency constraints
14+
15+2012-08-07 Edsko de Vries <edsko@well-typed.com> 0.3.0
16+
17+* Extract 'static' into a separate package (C.D.Static)
18+* Use new package rank1dynamic to proper runtime checks for polymorphic values
19+
20+2012-08-02 Edsko de Vries <edsko@well-typed.com> 0.2.3.0
21+
22+* Expose the constructors of Closure
23+* Add instance (Typeable a => Serializable (Static a)) and make sure we only
24+use the internal representation of Static where really necessary
25+* Improved docs
26+
27+2012-07-31 Edsko de Vries <edsko@well-typed.com> 0.2.2.0
28+
29+* Add exception handling primitives
30+* Fix runProcess: if the process threw an exception, a 'waiting indefinitely on
31+MVar' exception would be thrown.
32+
33+2012-07-21 Edsko de Vries <edsko@well-typed.com> 0.2.1.4
34+
35+* Bugfix in the node controller
36+(one way this bug materialized: when using the SimpleLocalnet backend,
37+slave nodes could not be reused)
38+* Improved documentation in Control.Distributed.Process.Closure
39+
40+2012-07-20 Edsko de Vries <edsko@well-typed.com> 0.2.1.3
41+
42+* Improve docs
43+* Local versions of spawn
44+
45+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.2
46+
47+* Base 4.6 compatibility
48+* Relax constraints on bytestring and containers
49+
50+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.1
51+
52+* Relax upper bound on 'time' dependency
53+
54+2012-07-11 Edsko de Vries <edsko@well-typed.com> 0.2.1
55+
56+* Complete redesign of the underlying implementation of static values and
57+closures.
58+
59+* Add support for 'spawnChannel'
60+
61+2012-07-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
62+
63+* Bugfix: Continue processing messages when a connection breaks.
64+
65+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
66+
67+* Initial release.
distributed-process/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Well-Typed LLP, 2011-2012
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
distributed-process/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
benchmarks/Channels.hs → distributed-process/benchmarks/Channels.hsrenamed+6−8
@@ -4,7 +4,7 @@ import Control.Monad
4import Control.Applicative
4import Control.Applicative
5import Control.Distributed.Process
5import Control.Distributed.Process
6import Control.Distributed.Process.Node
6import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
7+import Network.Transport.TCP (createTransport, defaultTCPParameters)
8import Data.Binary (encode, decode)
8import Data.Binary (encode, decode)
9import qualified Data.ByteString.Lazy as BSL
9import qualified Data.ByteString.Lazy as BSL
10
10
@@ -23,11 +23,11 @@ pingClient n them = do
23 receiveChan rc
23 receiveChan rc
24 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
24 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
25
25
26−initialProcess :: String -> Process ()
26+initialProcess :: String -> Process ()
27initialProcess "SERVER" = do
27initialProcess "SERVER" = do
28 us <- getSelfPid
28 us <- getSelfPid
29 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
29 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
30− pingServer
30+ pingServer
31initialProcess "CLIENT" = do
31initialProcess "CLIENT" = do
32 n <- liftIO $ getLine
32 n <- liftIO $ getLine
33 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
33 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
@@ -36,8 +36,6 @@ initialProcess "CLIENT" = do
36main :: IO ()
36main :: IO ()
37main = do
37main = do
38 [role, host, port] <- getArgs
38 [role, host, port] <- getArgs
39− trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
39+ Right transport <- createTransport host port defaultTCPParameters
40− case trans of
41− Right transport -> do node <- newLocalNode transport initRemoteTable
40+ node <- newLocalNode transport initRemoteTable
42− runProcess node $ initialProcess role
41+ runProcess node $ initialProcess role
43− Left other -> error $ show other
benchmarks/Latency.hs → distributed-process/benchmarks/Latency.hsrenamed+6−9
@@ -3,7 +3,7 @@ import Control.Monad
3import Control.Applicative
3import Control.Applicative
4import Control.Distributed.Process
4import Control.Distributed.Process
5import Control.Distributed.Process.Node
5import Control.Distributed.Process.Node
6−import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
6+import Network.Transport.TCP (createTransport, defaultTCPParameters)
7import Data.Binary (encode, decode)
7import Data.Binary (encode, decode)
8import qualified Data.ByteString.Lazy as BSL
8import qualified Data.ByteString.Lazy as BSL
9
9
@@ -18,11 +18,11 @@ pingClient n them = do
18 replicateM_ n $ send them us >> (expect :: Process ())
18 replicateM_ n $ send them us >> (expect :: Process ())
19 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
19 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
20
20
21−initialProcess :: String -> Process ()
21+initialProcess :: String -> Process ()
22initialProcess "SERVER" = do
22initialProcess "SERVER" = do
23 us <- getSelfPid
23 us <- getSelfPid
24 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
24 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
25− pingServer
25+ pingServer
26initialProcess "CLIENT" = do
26initialProcess "CLIENT" = do
27 n <- liftIO $ getLine
27 n <- liftIO $ getLine
28 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
28 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
@@ -31,9 +31,6 @@ initialProcess "CLIENT" = do
31main :: IO ()
31main :: IO ()
32main = do
32main = do
33 [role, host, port] <- getArgs
33 [role, host, port] <- getArgs
34− trans <- createTransport
35− (defaultTCPAddr host port) defaultTCPParameters
34+ Right transport <- createTransport host port defaultTCPParameters
36− case trans of
37− Right transport -> do node <- newLocalNode transport initRemoteTable
35+ node <- newLocalNode transport initRemoteTable
38− runProcess node $ initialProcess role
36+ runProcess node $ initialProcess role
39− Left other -> error $ show other
benchmarks/Spawns.hs → distributed-process/benchmarks/Spawns.hsrenamed+6−10
@@ -1,5 +1,3 @@
1−{-# LANGUAGE BangPatterns #-}
2−
3-- | Like Throughput, but send every ping from a different process
1-- | Like Throughput, but send every ping from a different process
4-- (i.e., require a lightweight connection per ping)
2-- (i.e., require a lightweight connection per ping)
5import System.Environment
3import System.Environment
@@ -7,14 +5,14 @@ import Control.Monad
7import Control.Applicative
5import Control.Applicative
8import Control.Distributed.Process
6import Control.Distributed.Process
9import Control.Distributed.Process.Node
7import Control.Distributed.Process.Node
10−import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
8+import Network.Transport.TCP (createTransport, defaultTCPParameters)
11import Data.Binary (encode, decode)
9import Data.Binary (encode, decode)
12import qualified Data.ByteString.Lazy as BSL
10import qualified Data.ByteString.Lazy as BSL
13
11
14counter :: Process ()
12counter :: Process ()
15counter = go 0
13counter = go 0
16 where
14 where
17− go :: Int -> Process ()
15+ go :: Int -> Process ()
18 go !n = do
16 go !n = do
19 b <- expect
17 b <- expect
20 case b of
18 case b of
@@ -29,7 +27,7 @@ count n them = do
29 n' <- expect
27 n' <- expect
30 liftIO $ print (n == n')
28 liftIO $ print (n == n')
31
29
32−initialProcess :: String -> Process ()
30+initialProcess :: String -> Process ()
33initialProcess "SERVER" = do
31initialProcess "SERVER" = do
34 us <- getSelfPid
32 us <- getSelfPid
35 liftIO $ BSL.writeFile "counter.pid" (encode us)
33 liftIO $ BSL.writeFile "counter.pid" (encode us)
@@ -42,8 +40,6 @@ initialProcess "CLIENT" = do
42main :: IO ()
40main :: IO ()
43main = do
41main = do
44 [role, host, port] <- getArgs
42 [role, host, port] <- getArgs
45− trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
43+ Right transport <- createTransport host port defaultTCPParameters
46− case trans of
47− Right transport -> do node <- newLocalNode transport initRemoteTable
44+ node <- newLocalNode transport initRemoteTable
48− runProcess node $ initialProcess role
45+ runProcess node $ initialProcess role
49− Left other -> error $ show other
benchmarks/Throughput.hs → distributed-process/benchmarks/Throughput.hsrenamed+9−13
@@ -1,12 +1,10 @@
1−{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
3−
4import System.Environment
2import System.Environment
5import Control.Monad
3import Control.Monad
6import Control.Applicative
4import Control.Applicative
7import Control.Distributed.Process
5import Control.Distributed.Process
8import Control.Distributed.Process.Node
6import Control.Distributed.Process.Node
9−import Network.Transport.TCP (createTransport, defaultTCPParameters, defaultTCPAddr)
7+import Network.Transport.TCP (createTransport, defaultTCPParameters)
10import Data.Binary
8import Data.Binary
11import qualified Data.ByteString.Lazy as BSL
9import qualified Data.ByteString.Lazy as BSL
12import Data.Typeable
10import Data.Typeable
@@ -16,8 +14,8 @@ data SizedList a = SizedList { size :: Int , elems :: [a] }
16
14
17instance Binary a => Binary (SizedList a) where
15instance Binary a => Binary (SizedList a) where
18 put (SizedList sz xs) = put sz >> mapM_ put xs
16 put (SizedList sz xs) = put sz >> mapM_ put xs
19− get = do
17+ get = do
20− sz <- get
18+ sz <- get
21 xs <- getMany sz
19 xs <- getMany sz
22 return (SizedList sz xs)
20 return (SizedList sz xs)
23
21
@@ -39,9 +37,9 @@ nats = \n -> SizedList n (aux n)
39counter :: Process ()
37counter :: Process ()
40counter = go 0
38counter = go 0
41 where
39 where
42− go :: Int -> Process ()
40+ go :: Int -> Process ()
43 go !n =
41 go !n =
44− receiveWait
42+ receiveWait
45 [ match $ \xs -> go (n + size (xs :: SizedList Int))
43 [ match $ \xs -> go (n + size (xs :: SizedList Int))
46 , match $ \them -> send them n >> go 0
44 , match $ \them -> send them n >> go 0
47 ]
45 ]
@@ -54,7 +52,7 @@ count (packets, sz) them = do
54 n' <- expect
52 n' <- expect
55 liftIO $ print (packets * sz, n' == packets * sz)
53 liftIO $ print (packets * sz, n' == packets * sz)
56
54
57−initialProcess :: String -> Process ()
55+initialProcess :: String -> Process ()
58initialProcess "SERVER" = do
56initialProcess "SERVER" = do
59 us <- getSelfPid
57 us <- getSelfPid
60 liftIO $ BSL.writeFile "counter.pid" (encode us)
58 liftIO $ BSL.writeFile "counter.pid" (encode us)
@@ -67,8 +65,6 @@ initialProcess "CLIENT" = do
67main :: IO ()
65main :: IO ()
68main = do
66main = do
69 [role, host, port] <- getArgs
67 [role, host, port] <- getArgs
70− trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
68+ Right transport <- createTransport host port defaultTCPParameters
71− case trans of
72− Right transport -> do node <- newLocalNode transport initRemoteTable
69+ node <- newLocalNode transport initRemoteTable
73− runProcess node $ initialProcess role
70+ runProcess node $ initialProcess role
74− Left other -> error $ show other
benchmarks/erlang/latency.erl → distributed-process/benchmarks/erlang/latency.erlrenamed

The content is the same.

benchmarks/erlang/throughput.erl → distributed-process/benchmarks/erlang/throughput.erlrenamed

The content is the same.

benchmarks/remote/Latency.hs → distributed-process/benchmarks/remote/Latency.hsrenamed+3−3
@@ -18,15 +18,15 @@ pingClient n them = do
18 replicateM_ n $ send them us >> (expect :: ProcessM ())
18 replicateM_ n $ send them us >> (expect :: ProcessM ())
19 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
19 liftIO . putStrLn $ "Did " ++ show n ++ " pings"
20
20
21−initialProcess :: String -> ProcessM ()
21+initialProcess :: String -> ProcessM ()
22initialProcess "SERVER" = do
22initialProcess "SERVER" = do
23 us <- getSelfPid
23 us <- getSelfPid
24 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
24 liftIO $ BSL.writeFile "pingServer.pid" (encode us)
25− pingServer
25+ pingServer
26initialProcess "CLIENT" = do
26initialProcess "CLIENT" = do
27 n <- liftIO $ getLine
27 n <- liftIO $ getLine
28 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
28 them <- liftIO $ decode <$> BSL.readFile "pingServer.pid"
29 pingClient (read n) them
29 pingClient (read n) them
30
30
31main :: IO ()
31main :: IO ()
32−main = remoteInit (Just "config") [] initialProcess
32+main = remoteInit (Just "config") [] initialProcess
benchmarks/remote/Throughput.hs → distributed-process/benchmarks/remote/Throughput.hsrenamed+3−3
@@ -10,7 +10,7 @@ import Remote
10counter :: ProcessM ()
10counter :: ProcessM ()
11counter = go 0
11counter = go 0
12 where
12 where
13− go :: Int -> ProcessM ()
13+ go :: Int -> ProcessM ()
14 go !n = do
14 go !n = do
15 b <- expect
15 b <- expect
16 case b of
16 case b of
@@ -25,7 +25,7 @@ count n them = do
25 n' <- expect
25 n' <- expect
26 liftIO $ print (n == n')
26 liftIO $ print (n == n')
27
27
28−initialProcess :: String -> ProcessM ()
28+initialProcess :: String -> ProcessM ()
29initialProcess "SERVER" = do
29initialProcess "SERVER" = do
30 us <- getSelfPid
30 us <- getSelfPid
31 liftIO $ BSL.writeFile "counter.pid" (encode us)
31 liftIO $ BSL.writeFile "counter.pid" (encode us)
@@ -36,4 +36,4 @@ initialProcess "CLIENT" = do
36 count (read n) them
36 count (read n) them
37
37
38main :: IO ()
38main :: IO ()
39−main = remoteInit (Just "config") [] initialProcess
39+main = remoteInit (Just "config") [] initialProcess
distributed-process/distributed-process.cabalnew file+172
@@ -0,0 +1,172 @@
1+Name: distributed-process
2+Version: 0.3.2
3+Cabal-Version: >=1.8
4+Build-Type: Simple
5+License: BSD3
6+License-File: LICENSE
7+Copyright: Well-Typed LLP
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: edsko@well-typed.com, dcoutts@well-typed.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com
13+Synopsis: Cloud Haskell: Erlang-style concurrency in Haskell
14+Description: This is an implementation of Cloud Haskell, as described in
15+ /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,
16+ and Simon Peyton Jones
17+ (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
18+ although some of the details are different. The precise message
19+ passing semantics are based on /A unified semantics for future Erlang/
20+ by Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.
21+
22+ You will probably also want to install a Cloud Haskell backend such
23+ as distributed-process-simplelocalnet.
24+Tested-With: GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
25+Category: Control
26+
27+Source-Repository head
28+ Type: git
29+ Location: https://github.com/haskell-distributed/distributed-process
30+ SubDir: distributed-process
31+
32+flag th
33+ description: Build with Template Haskell support
34+ default: True
35+
36+flag benchmarks
37+ description: Build benchmarks
38+ default: False
39+
40+Library
41+ Build-Depends: base >= 4.4 && < 5,
42+ binary >= 0.5 && < 0.6,
43+ network-transport >= 0.3 && < 0.4,
44+ stm >= 2.3 && < 2.5,
45+ transformers >= 0.2 && < 0.4,
46+ mtl >= 2.0 && < 2.2,
47+ data-accessor >= 0.2 && < 0.3,
48+ bytestring >= 0.9 && < 0.11,
49+ containers >= 0.4 && < 0.6,
50+ old-locale >= 1.0 && < 1.1,
51+ time >= 1.2 && < 1.5,
52+ random >= 1.0 && < 1.1,
53+ ghc-prim >= 0.2 && < 0.4,
54+ distributed-static >= 0.2 && < 0.3,
55+ rank1dynamic >= 0.1 && < 0.2,
56+ syb >= 0.3 && < 0.4
57+ Exposed-modules: Control.Distributed.Process,
58+ Control.Distributed.Process.Serializable,
59+ Control.Distributed.Process.Closure,
60+ Control.Distributed.Process.Node,
61+ Control.Distributed.Process.Internal.Primitives,
62+ Control.Distributed.Process.Internal.CQueue,
63+ Control.Distributed.Process.Internal.Types,
64+ Control.Distributed.Process.Internal.Closure.BuiltIn,
65+ Control.Distributed.Process.Internal.Messaging,
66+ Control.Distributed.Process.Internal.StrictMVar,
67+ Control.Distributed.Process.Internal.WeakTQueue
68+ Extensions: RankNTypes,
69+ ScopedTypeVariables,
70+ FlexibleInstances,
71+ UndecidableInstances,
72+ ExistentialQuantification,
73+ GADTs,
74+ GeneralizedNewtypeDeriving,
75+ DeriveDataTypeable,
76+ CPP,
77+ BangPatterns
78+ ghc-options: -Wall
79+ HS-Source-Dirs: src
80+ if flag(th)
81+ Build-Depends: template-haskell >= 2.6 && < 2.9
82+ Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
83+ CPP-Options: -DTemplateHaskellSupport
84+
85+Test-Suite TestCH
86+ Type: exitcode-stdio-1.0
87+ Main-Is: TestCH.hs
88+ Build-Depends: base >= 4.4 && < 5,
89+ random >= 1.0 && < 1.1,
90+ ansi-terminal >= 0.5 && < 0.6,
91+ distributed-process >= 0.3 && < 0.4,
92+ network-transport >= 0.3 && < 0.4,
93+ network-transport-tcp >= 0.3 && < 0.4,
94+ binary >= 0.5 && < 0.6,
95+ network >= 2.3 && < 2.5
96+ Other-modules: TestAuxiliary
97+ Extensions: CPP,
98+ ScopedTypeVariables,
99+ DeriveDataTypeable,
100+ GeneralizedNewtypeDeriving
101+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
102+ HS-Source-Dirs: tests
103+
104+Test-Suite TestClosure
105+ Type: exitcode-stdio-1.0
106+ Main-Is: TestClosure.hs
107+ Build-Depends: base >= 4.4 && < 5,
108+ random >= 1.0 && < 1.1,
109+ ansi-terminal >= 0.5 && < 0.6,
110+ distributed-static >= 0.2 && < 0.3,
111+ distributed-process >= 0.3 && < 0.4,
112+ network-transport >= 0.3 && < 0.4,
113+ network-transport-tcp >= 0.3 && < 0.4,
114+ bytestring >= 0.9 && < 0.11,
115+ network >= 2.3 && < 2.5
116+ Other-modules: TestAuxiliary
117+ Extensions: CPP,
118+ ScopedTypeVariables
119+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
120+ HS-Source-Dirs: tests
121+
122+Executable distributed-process-throughput
123+ if flag(benchmarks)
124+ Build-Depends: base >= 4.4 && < 5,
125+ distributed-process >= 0.3 && < 0.4,
126+ network-transport-tcp >= 0.3 && < 0.4,
127+ bytestring >= 0.9 && < 0.11,
128+ binary >= 0.5 && < 0.6
129+ else
130+ buildable: False
131+ Main-Is: benchmarks/Throughput.hs
132+ ghc-options: -Wall
133+ Extensions: BangPatterns
134+
135+Executable distributed-process-latency
136+ if flag(benchmarks)
137+ Build-Depends: base >= 4.4 && < 5,
138+ distributed-process >= 0.3 && < 0.4,
139+ network-transport-tcp >= 0.3 && < 0.4,
140+ bytestring >= 0.9 && < 0.11,
141+ binary >= 0.5 && < 0.6
142+ else
143+ buildable: False
144+ Main-Is: benchmarks/Latency.hs
145+ ghc-options: -Wall
146+ Extensions: BangPatterns
147+
148+Executable distributed-process-channels
149+ if flag(benchmarks)
150+ Build-Depends: base >= 4.4 && < 5,
151+ distributed-process >= 0.3 && < 0.4,
152+ network-transport-tcp >= 0.3 && < 0.4,
153+ bytestring >= 0.9 && < 0.11,
154+ binary >= 0.5 && < 0.6
155+ else
156+ buildable: False
157+ Main-Is: benchmarks/Channels.hs
158+ ghc-options: -Wall
159+ Extensions: BangPatterns
160+
161+Executable distributed-process-spawns
162+ if flag(benchmarks)
163+ Build-Depends: base >= 4.4 && < 5,
164+ distributed-process >= 0.3 && < 0.4,
165+ network-transport-tcp >= 0.3 && < 0.4,
166+ bytestring >= 0.9 && < 0.11,
167+ binary >= 0.5 && < 0.6
168+ else
169+ buildable: False
170+ Main-Is: benchmarks/Spawns.hs
171+ ghc-options: -Wall
172+ Extensions: BangPatterns
src/Control/Distributed/Process.hs → distributed-process/src/Control/Distributed/Process.hsrenamed+173−205
@@ -1,33 +1,24 @@
1−{-# LANGUAGE CPP #-}
2−{- | [Cloud Haskell]
1+-- | [Cloud Haskell]
3−
2+--
4−This is an implementation of Cloud Haskell, as described in
3+-- This is an implementation of Cloud Haskell, as described in
5−/Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon
4+-- /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon
6−Peyton Jones (see
5+-- Peyton Jones
7−<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
6+-- (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
8−although some of the details are different. The precise message passing
7+-- although some of the details are different. The precise message passing
9−semantics are based on /A unified semantics for future Erlang/ by Hans
8+-- semantics are based on /A unified semantics for future Erlang/ by Hans
10−Svensson, Lars-Åke Fredlund and Clara Benac Earle.
9+-- Svensson, Lars-Åke Fredlund and Clara Benac Earle.
11−
12−For a detailed description of the package and other reference materials,
13−please see the distributed-process wiki page on github:
14−<https://github.com/haskell-distributed/distributed-process/wiki>.
15−
16−-}
17−module Control.Distributed.Process
10+module Control.Distributed.Process
18− ( -- * Basic types
11+ ( -- * Basic types
19 ProcessId
12 ProcessId
20− , NodeId(..)
13+ , NodeId
21 , Process
14 , Process
22 , SendPortId
15 , SendPortId
23 , processNodeId
16 , processNodeId
24 , sendPortProcessId
17 , sendPortProcessId
25 , liftIO -- Reexported for convenience
18 , liftIO -- Reexported for convenience
26 -- * Basic messaging
19 -- * Basic messaging
27− , send
20+ , send
28− , usend
29 , expect
21 , expect
30− , expectTimeout
31 -- * Channels
22 -- * Channels
32 , ReceivePort
23 , ReceivePort
33 , SendPort
24 , SendPort
@@ -35,16 +26,8 @@ module Control.Distributed.Process
35 , newChan
26 , newChan
36 , sendChan
27 , sendChan
37 , receiveChan
28 , receiveChan
38− , receiveChanTimeout
39 , mergePortsBiased
29 , mergePortsBiased
40 , mergePortsRR
30 , mergePortsRR
41− -- * Unsafe messaging variants
42− , unsafeSend
43− , unsafeUSend
44− , unsafeSendChan
45− , unsafeNSend
46− , unsafeNSendRemote
47− , unsafeWrapMessage
48 -- * Advanced messaging
31 -- * Advanced messaging
49 , Match
32 , Match
50 , receiveWait
33 , receiveWait
@@ -52,44 +35,16 @@ module Control.Distributed.Process
52 , match
35 , match
53 , matchIf
36 , matchIf
54 , matchUnknown
37 , matchUnknown
38+ , AbstractMessage(..)
55 , matchAny
39 , matchAny
56− , matchAnyIf
57− , matchChan
58− , matchSTM
59− , Message
60− , matchMessage
61− , matchMessageIf
62− , isEncoded
63− , wrapMessage
64− , unwrapMessage
65− , handleMessage
66− , handleMessageIf
67− , handleMessage_
68− , handleMessageIf_
69− , forward
70− , uforward
71− , delegate
72− , relay
73− , proxy
74 -- * Process management
40 -- * Process management
75 , spawn
41 , spawn
76 , call
42 , call
77 , terminate
43 , terminate
78− , die
79− , kill
80− , exit
81− , catchExit
82− , catchesExit
83 , ProcessTerminationException(..)
44 , ProcessTerminationException(..)
84− , ProcessRegistrationException(..)
85 , SpawnRef
45 , SpawnRef
86 , getSelfPid
46 , getSelfPid
87 , getSelfNode
47 , getSelfNode
88− , ProcessInfo(..)
89− , getProcessInfo
90− , NodeStats(..)
91− , getNodeStats
92− , getLocalNodeStats
93 -- * Monitoring and linking
48 -- * Monitoring and linking
94 , link
49 , link
95 , linkNode
50 , linkNode
@@ -101,12 +56,10 @@ module Control.Distributed.Process
101 , monitorNode
56 , monitorNode
102 , monitorPort
57 , monitorPort
103 , unmonitor
58 , unmonitor
104− , withMonitor
105− , withMonitor_
106− , MonitorRef -- opaque
107 , ProcessLinkException(..)
59 , ProcessLinkException(..)
108 , NodeLinkException(..)
60 , NodeLinkException(..)
109 , PortLinkException(..)
61 , PortLinkException(..)
62+ , MonitorRef -- opaque
110 , ProcessMonitorNotification(..)
63 , ProcessMonitorNotification(..)
111 , NodeMonitorNotification(..)
64 , NodeMonitorNotification(..)
112 , PortMonitorNotification(..)
65 , PortMonitorNotification(..)
@@ -122,61 +75,54 @@ module Control.Distributed.Process
122 , say
75 , say
123 -- * Registry
76 -- * Registry
124 , register
77 , register
125− , reregister
126 , unregister
78 , unregister
127 , whereis
79 , whereis
128 , nsend
80 , nsend
129− , registerRemoteAsync
81+ , registerRemote
130− , reregisterRemoteAsync
82+ , unregisterRemote
131− , unregisterRemoteAsync
132 , whereisRemoteAsync
83 , whereisRemoteAsync
133 , nsendRemote
84 , nsendRemote
134 , WhereIsReply(..)
85 , WhereIsReply(..)
135− , RegisterReply(..)
136 -- * Exception handling
86 -- * Exception handling
137 , catch
87 , catch
138− , Handler(..)
139− , catches
140− , try
141 , mask
88 , mask
142− , mask_
143 , onException
89 , onException
144 , bracket
90 , bracket
145 , bracket_
91 , bracket_
146 , finally
92 , finally
147 -- * Auxiliary API
93 -- * Auxiliary API
94+ , expectTimeout
148 , spawnAsync
95 , spawnAsync
149 , spawnSupervised
96 , spawnSupervised
150 , spawnLink
97 , spawnLink
151 , spawnMonitor
98 , spawnMonitor
152 , spawnChannel
99 , spawnChannel
153 , DidSpawn(..)
100 , DidSpawn(..)
154− -- * Local versions of 'spawn'
101+ -- * Local versions of 'spawn'
155 , spawnLocal
102 , spawnLocal
156 , spawnChannelLocal
103 , spawnChannelLocal
157− , callLocal
158 -- * Reconnecting
104 -- * Reconnecting
159 , reconnect
105 , reconnect
160 , reconnectPort
106 , reconnectPort
161 ) where
107 ) where
162
108
109+#if ! MIN_VERSION_base(4,6,0)
110+import Prelude hiding (catch)
111+#endif
112+
113+import Data.Typeable (Typeable)
163import Control.Monad.IO.Class (liftIO)
114import Control.Monad.IO.Class (liftIO)
164−import Control.Applicative
115+import Control.Applicative ((<$>))
165import Control.Monad.Reader (ask)
116import Control.Monad.Reader (ask)
166−import Control.Concurrent (killThread)
167−import Control.Concurrent.MVar
168− ( MVar
169− , newEmptyMVar
170− , takeMVar
171− , putMVar
172− )
173−import Control.Distributed.Static
117+import Control.Distributed.Static
174 ( Closure
118 ( Closure
175 , closure
119 , closure
176 , Static
120 , Static
177 , RemoteTable
121 , RemoteTable
122+ , closureCompose
123+ , staticClosure
178 )
124 )
179−import Control.Distributed.Process.Internal.Types
125+import Control.Distributed.Process.Internal.Types
180 ( NodeId(..)
126 ( NodeId(..)
181 , ProcessId(..)
127 , ProcessId(..)
182 , Process(..)
128 , Process(..)
@@ -187,7 +133,6 @@ import Control.Distributed.Process.Internal.Types
187 , ProcessLinkException(..)
133 , ProcessLinkException(..)
188 , NodeLinkException(..)
134 , NodeLinkException(..)
189 , PortLinkException(..)
135 , PortLinkException(..)
190− , ProcessRegistrationException(..)
191 , DiedReason(..)
136 , DiedReason(..)
192 , SpawnRef(..)
137 , SpawnRef(..)
193 , DidSpawn(..)
138 , DidSpawn(..)
@@ -195,16 +140,27 @@ import Control.Distributed.Process.Internal.Types
195 , ReceivePort(..)
140 , ReceivePort(..)
196 , SendPortId(..)
141 , SendPortId(..)
197 , WhereIsReply(..)
142 , WhereIsReply(..)
198− , RegisterReply(..)
199 , LocalProcess(processNode)
143 , LocalProcess(processNode)
200− , Message
144+ , nullProcessId
201− , localProcessWithId
145+ )
146+import Control.Distributed.Process.Serializable (Serializable, SerializableDict)
147+import Control.Distributed.Process.Internal.Closure.BuiltIn
148+ ( sdictUnit
149+ , sdictSendPort
150+ , sndStatic
151+ , idCP
152+ , seqCP
153+ , bindCP
154+ , splitCP
155+ , cpLink
156+ , cpUnlink
157+ , cpExpect
158+ , cpSend
159+ , cpNewChan
202 )
160 )
203−import Control.Distributed.Process.Serializable (Serializable)
204import Control.Distributed.Process.Internal.Primitives
161import Control.Distributed.Process.Internal.Primitives
205 ( -- Basic messaging
162 ( -- Basic messaging
206− send
163+ send
207− , usend
208 , expect
164 , expect
209 -- Channels
165 -- Channels
210 , newChan
166 , newChan
@@ -212,11 +168,6 @@ import Control.Distributed.Process.Internal.Primitives
212 , receiveChan
168 , receiveChan
213 , mergePortsBiased
169 , mergePortsBiased
214 , mergePortsRR
170 , mergePortsRR
215− , unsafeSend
216− , unsafeUSend
217− , unsafeSendChan
218− , unsafeNSend
219− , unsafeNSendRemote
220 -- Advanced messaging
171 -- Advanced messaging
221 , Match
172 , Match
222 , receiveWait
173 , receiveWait
@@ -224,40 +175,13 @@ import Control.Distributed.Process.Internal.Primitives
224 , match
175 , match
225 , matchIf
176 , matchIf
226 , matchUnknown
177 , matchUnknown
178+ , AbstractMessage(..)
227 , matchAny
179 , matchAny
228− , matchAnyIf
229− , matchChan
230− , matchSTM
231− , matchMessage
232− , matchMessageIf
233− , isEncoded
234− , wrapMessage
235− , unsafeWrapMessage
236− , unwrapMessage
237− , handleMessage
238− , handleMessageIf
239− , handleMessage_
240− , handleMessageIf_
241− , forward
242− , uforward
243− , delegate
244− , relay
245− , proxy
246 -- Process management
180 -- Process management
247 , terminate
181 , terminate
248 , ProcessTerminationException(..)
182 , ProcessTerminationException(..)
249− , die
250− , exit
251− , catchExit
252− , catchesExit
253− , kill
254 , getSelfPid
183 , getSelfPid
255 , getSelfNode
184 , getSelfNode
256− , ProcessInfo(..)
257− , getProcessInfo
258− , NodeStats(..)
259− , getNodeStats
260− , getLocalNodeStats
261 -- Monitoring and linking
185 -- Monitoring and linking
262 , link
186 , link
263 , linkNode
187 , linkNode
@@ -269,19 +193,15 @@ import Control.Distributed.Process.Internal.Primitives
269 , monitorNode
193 , monitorNode
270 , monitorPort
194 , monitorPort
271 , unmonitor
195 , unmonitor
272− , withMonitor
273− , withMonitor_
274 -- Logging
196 -- Logging
275 , say
197 , say
276 -- Registry
198 -- Registry
277 , register
199 , register
278− , reregister
279 , unregister
200 , unregister
280 , whereis
201 , whereis
281 , nsend
202 , nsend
282− , registerRemoteAsync
203+ , registerRemote
283− , reregisterRemoteAsync
204+ , unregisterRemote
284− , unregisterRemoteAsync
285 , whereisRemoteAsync
205 , whereisRemoteAsync
286 , nsendRemote
206 , nsendRemote
287 -- Closures
207 -- Closures
@@ -289,51 +209,22 @@ import Control.Distributed.Process.Internal.Primitives
289 , unClosure
209 , unClosure
290 -- Exception handling
210 -- Exception handling
291 , catch
211 , catch
292− , Handler(..)
293− , catches
294− , try
295 , mask
212 , mask
296− , mask_
297 , onException
213 , onException
298 , bracket
214 , bracket
299 , bracket_
215 , bracket_
300 , finally
216 , finally
301 -- Auxiliary API
217 -- Auxiliary API
302 , expectTimeout
218 , expectTimeout
303− , receiveChanTimeout
304 , spawnAsync
219 , spawnAsync
305 -- Reconnecting
220 -- Reconnecting
306 , reconnect
221 , reconnect
307 , reconnectPort
222 , reconnectPort
308 )
223 )
309import Control.Distributed.Process.Node (forkProcess)
224import Control.Distributed.Process.Node (forkProcess)
310−import Control.Distributed.Process.Internal.Types
311− ( processThread
312− , withValidLocalState
313− )
314−import Control.Distributed.Process.Internal.Spawn
315− ( -- Spawning Processes/Channels
316− spawn
317− , spawnLink
318− , spawnMonitor
319− , spawnChannel
320− , spawnSupervised
321− , call
322− )
323−import qualified Control.Monad.Catch as Catch
324−
325−#if MIN_VERSION_base(4,6,0)
326−import Prelude
327−#else
328−import Prelude hiding (catch)
329−#endif
330−import qualified Control.Exception as Exception (onException)
331−import Data.Accessor ((^.))
332−import Data.Foldable (forM_)
333−
334
225
335-- INTERNAL NOTES
226-- INTERNAL NOTES
336−--
227+--
337-- 1. 'send' never fails. If you want to know that the remote process received
228-- 1. 'send' never fails. If you want to know that the remote process received
338-- your message, you will need to send an explicit acknowledgement. If you
229-- your message, you will need to send an explicit acknowledgement. If you
339-- want to know when the remote process failed, you will need to monitor
230-- want to know when the remote process failed, you will need to monitor
@@ -342,11 +233,7 @@ import Data.Foldable (forM_)
342-- 2. 'send' may block (when the system TCP buffers are full, while we are
233-- 2. 'send' may block (when the system TCP buffers are full, while we are
343-- trying to establish a connection to the remote endpoint, etc.) but its
234-- trying to establish a connection to the remote endpoint, etc.) but its
344-- return does not imply that the remote process received the message (much
235-- return does not imply that the remote process received the message (much
345−-- less processed it). When 'send' targets a local process, it is dispatched
236+-- less processed it)
346−-- via the node controller however, in which cases it will /not/ block. This
347−-- isn't part of the semantics, so it should be ok. The ordering is maintained
348−-- because the ctrlChannel still has FIFO semantics with regards interactions
349−-- between two disparate forkIO threads.
350--
237--
351-- 3. Message delivery is reliable and ordered. That means that if process A
238-- 3. Message delivery is reliable and ordered. That means that if process A
352-- sends messages m1, m2, m3 to process B, B will either arrive all three
239-- sends messages m1, m2, m3 to process B, B will either arrive all three
@@ -375,17 +262,17 @@ import Data.Foldable (forM_)
375-- http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/remote.pdf
262-- http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/remote.pdf
376--
263--
377-- The precise semantics for message passing is based on
264-- The precise semantics for message passing is based on
378−--
265+--
379-- [2] "A Unified Semantics for Future Erlang", Hans Svensson, Lars-Ake Fredlund
266-- [2] "A Unified Semantics for Future Erlang", Hans Svensson, Lars-Ake Fredlund
380-- and Clara Benac Earle (not freely available online, unfortunately)
267-- and Clara Benac Earle (not freely available online, unfortunately)
381--
268--
382-- Some pointers to related documentation about Erlang, for comparison and
269-- Some pointers to related documentation about Erlang, for comparison and
383−-- inspiration:
270+-- inspiration:
384--
271--
385-- [3] "Programming Distributed Erlang Applications: Pitfalls and Recipes",
272-- [3] "Programming Distributed Erlang Applications: Pitfalls and Recipes",
386−-- Hans Svensson and Lars-Ake Fredlund
273+-- Hans Svensson and Lars-Ake Fredlund
387-- http://man.lupaworld.com/content/develop/p37-svensson.pdf
274-- http://man.lupaworld.com/content/develop/p37-svensson.pdf
388−-- [4] The Erlang manual, sections "Message Sending" and "Send"
275+-- [4] The Erlang manual, sections "Message Sending" and "Send"
389-- http://www.erlang.org/doc/reference_manual/processes.html#id82409
276-- http://www.erlang.org/doc/reference_manual/processes.html#id82409
390-- http://www.erlang.org/doc/reference_manual/expressions.html#send
277-- http://www.erlang.org/doc/reference_manual/expressions.html#send
391-- [5] Questions "Is the order of message reception guaranteed?" and
278-- [5] Questions "Is the order of message reception guaranteed?" and
@@ -395,6 +282,117 @@ import Data.Foldable (forM_)
395-- [6] "Delivery of Messages", post on erlang-questions
282-- [6] "Delivery of Messages", post on erlang-questions
396-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
283-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
397
284
285+--------------------------------------------------------------------------------
286+-- Primitives are defined in a separate module; here we only define derived --
287+-- constructs --
288+--------------------------------------------------------------------------------
289+
290+-- | Spawn a process
291+--
292+-- For more information about 'Closure', see
293+-- "Control.Distributed.Process.Closure".
294+--
295+-- See also 'call'.
296+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
297+spawn nid proc = do
298+ us <- getSelfPid
299+ mRef <- monitorNode nid
300+ sRef <- spawnAsync nid $ cpLink us
301+ `seqCP` cpExpect sdictUnit
302+ `seqCP` cpUnlink us
303+ `seqCP` proc
304+ mPid <- receiveWait
305+ [ matchIf (\(DidSpawn ref _) -> ref == sRef)
306+ (\(DidSpawn _ pid) -> return $ Right pid)
307+ , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)
308+ (\(NodeMonitorNotification _ _ err) -> return $ Left err)
309+ ]
310+ unmonitor mRef
311+ case mPid of
312+ Left _err -> return (nullProcessId nid)
313+ Right pid -> send pid () >> return pid
314+
315+-- | Spawn a process and link to it
316+--
317+-- Note that this is just the sequential composition of 'spawn' and 'link'.
318+-- (The "Unified" semantics that underlies Cloud Haskell does not even support
319+-- a synchronous link operation)
320+spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId
321+spawnLink nid proc = do
322+ pid <- spawn nid proc
323+ link pid
324+ return pid
325+
326+-- | Like 'spawnLink', but monitor the spawned process
327+spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
328+spawnMonitor nid proc = do
329+ pid <- spawn nid proc
330+ ref <- monitor pid
331+ return (pid, ref)
332+
333+-- | Run a process remotely and wait for it to reply
334+--
335+-- We monitor the remote process: if it dies before it can send a reply, we die
336+-- too.
337+--
338+-- For more information about 'Static', 'SerializableDict', and 'Closure', see
339+-- "Control.Distributed.Process.Closure".
340+--
341+-- See also 'spawn'.
342+call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
343+call dict nid proc = do
344+ us <- getSelfPid
345+ (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
346+ -- We are guaranteed to receive the reply before the monitor notification
347+ -- (if a reply is sent at all)
348+ -- NOTE: This might not be true if we switch to unreliable delivery.
349+ mResult <- receiveWait
350+ [ match (return . Right)
351+ , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
352+ (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
353+ ]
354+ case mResult of
355+ Right a -> do
356+ -- Wait for the monitor message so that we the mailbox doesn't grow
357+ receiveWait
358+ [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
359+ (\(ProcessMonitorNotification {}) -> return ())
360+ ]
361+ -- Clean up connection to pid
362+ reconnect pid
363+ return a
364+ Left err ->
365+ fail $ "call: remote process died: " ++ show err
366+
367+-- | Spawn a child process, have the child link to the parent and the parent
368+-- monitor the child
369+spawnSupervised :: NodeId
370+ -> Closure (Process ())
371+ -> Process (ProcessId, MonitorRef)
372+spawnSupervised nid proc = do
373+ us <- getSelfPid
374+ them <- spawn nid (cpLink us `seqCP` proc)
375+ ref <- monitor them
376+ return (them, ref)
377+
378+-- | Spawn a new process, supplying it with a new 'ReceivePort' and return
379+-- the corresponding 'SendPort'.
380+spawnChannel :: forall a. Typeable a => Static (SerializableDict a)
381+ -> NodeId
382+ -> Closure (ReceivePort a -> Process ())
383+ -> Process (SendPort a)
384+spawnChannel dict nid proc = do
385+ us <- getSelfPid
386+ spawn nid (go us)
387+ expect
388+ where
389+ go :: ProcessId -> Closure (Process ())
390+ go pid = cpNewChan dict
391+ `bindCP`
392+ (cpSend (sdictSendPort dict) pid `splitCP` proc)
393+ `bindCP`
394+ (idCP `closureCompose` staticClosure sndStatic)
395+
398--------------------------------------------------------------------------------
396--------------------------------------------------------------------------------
399-- Local versions of spawn --
397-- Local versions of spawn --
400--------------------------------------------------------------------------------
398--------------------------------------------------------------------------------
@@ -402,46 +400,16 @@ import Data.Foldable (forM_)
402-- | Spawn a process on the local node
400-- | Spawn a process on the local node
403spawnLocal :: Process () -> Process ProcessId
401spawnLocal :: Process () -> Process ProcessId
404spawnLocal proc = do
402spawnLocal proc = do
405− node <- processNode <$> ask
403+ node <- processNode <$> ask
406 liftIO $ forkProcess node proc
404 liftIO $ forkProcess node proc
407
405
408-- | Create a new typed channel, spawn a process on the local node, passing it
406-- | Create a new typed channel, spawn a process on the local node, passing it
409-- the receive port, and return the send port
407-- the receive port, and return the send port
410spawnChannelLocal :: Serializable a
408spawnChannelLocal :: Serializable a
411− => (ReceivePort a -> Process ())
409+ => (ReceivePort a -> Process ())
412 -> Process (SendPort a)
410 -> Process (SendPort a)
413−spawnChannelLocal proc = do
411+spawnChannelLocal proc = do
414− node <- processNode <$> ask
412+ node <- processNode <$> ask
415− liftIO $ do
413+ (sport, rport) <- newChan
416− mvar <- newEmptyMVar
417− _ <- forkProcess node $ do
414+ _ <- liftIO $ forkProcess node (proc rport)
418− -- It is important that we allocate the new channel in the new process,
415+ return sport
419− -- because otherwise it will be associated with the wrong process ID
420− (sport, rport) <- newChan
421− liftIO $ putMVar mvar sport
422− proc rport
423− takeMVar mvar
424−
425−-- | Local version of 'call'. Running a process in this way isolates it from
426−-- messages sent to the caller process, and also allows silently dropping late
427−-- or duplicate messages sent to the isolated process after it exits.
428−-- Silently dropping messages may not always be the best approach.
429−callLocal :: Process a -> Process a
430−callLocal proc = Catch.mask $ \release -> do
431− mv <- liftIO newEmptyMVar :: Process (MVar (Either Catch.SomeException a))
432− child <- spawnLocal $ Catch.try (release proc) >>= liftIO . putMVar mv
433− lproc <- ask
434− liftIO $ do
435− rs <- Exception.onException (takeMVar mv) $ Catch.uninterruptibleMask_ $
436− -- Exceptions need to be prevented from interrupting the clean up or
437− -- the original exception which caused entering the handler could be
438− -- forgotten. For instance, this could have a problematic effect
439− -- when the original exception was meant to kill the thread and the
440− -- second exception doesn't (like the exception thrown by
441− -- 'System.Timeout.timeout').
442− do mchildThreadId <- withValidLocalState (processNode lproc) $
443− \vst -> return $ fmap processThread $
444− vst ^. localProcessWithId (processLocalId child)
445− forM_ mchildThreadId killThread
446− takeMVar mv
447− either Catch.throwM return rs
src/Control/Distributed/Process/Closure.hs → distributed-process/src/Control/Distributed/Process/Closure.hsrenamed+39−63
@@ -15,15 +15,15 @@
15-- > f = ...
15-- > f = ...
16--
16--
17-- you can use a Template Haskell splice to create a static version of 'f':
17-- you can use a Template Haskell splice to create a static version of 'f':
18−--
18+--
19-- > $(mkStatic 'f) :: forall a1 .. an. (Typeable a1, .., Typeable an) => Static T
19-- > $(mkStatic 'f) :: forall a1 .. an. (Typeable a1, .., Typeable an) => Static T
20−--
20+--
21-- Every module that you write that contains calls to 'mkStatic' needs to
21-- Every module that you write that contains calls to 'mkStatic' needs to
22-- have a call to 'remotable':
22-- have a call to 'remotable':
23--
23--
24-- > remotable [ 'f, 'g, ... ]
24-- > remotable [ 'f, 'g, ... ]
25--
25--
26−-- where you must pass every function (or other value) that you pass as an
26+-- where you must pass every function (or other value) that you pass as an
27-- argument to 'mkStatic'. The call to 'remotable' will create a definition
27-- argument to 'mkStatic'. The call to 'remotable' will create a definition
28--
28--
29-- > __remoteTable :: RemoteTable -> RemoteTable
29-- > __remoteTable :: RemoteTable -> RemoteTable
@@ -32,12 +32,12 @@
32-- Cloud Haskell. You should have (at most) one call to 'remotable' per module,
32-- Cloud Haskell. You should have (at most) one call to 'remotable' per module,
33-- and compose all created functions when initializing Cloud Haskell:
33-- and compose all created functions when initializing Cloud Haskell:
34--
34--
35−-- > let rtable :: RemoteTable
35+-- > let rtable :: RemoteTable
36-- > rtable = M1.__remoteTable
36-- > rtable = M1.__remoteTable
37-- > . M2.__remoteTable
37-- > . M2.__remoteTable
38-- > . ...
38-- > . ...
39-- > . Mn.__remoteTable
39-- > . Mn.__remoteTable
40−-- > $ initRemoteTable
40+-- > $ initRemoteTable
41--
41--
42-- NOTE: If you get a type error from ghc along these lines
42-- NOTE: If you get a type error from ghc along these lines
43--
43--
@@ -52,19 +52,19 @@
52--
52--
53-- > call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
53-- > call :: Serializable a => Static (SerializableDict a) -> NodeId -> Closure (Process a) -> Process a
54--
54--
55−-- Given some serializable type 'T' you can define
55+-- Given some serializable type 'T' you can define
56--
56--
57-- > sdictT :: SerializableDict T
57-- > sdictT :: SerializableDict T
58-- > sdictT = SerializableDict
58-- > sdictT = SerializableDict
59--
59--
60-- and then have
60-- and then have
61−--
61+--
62-- > $(mkStatic 'sdictT) :: Static (SerializableDict T)
62-- > $(mkStatic 'sdictT) :: Static (SerializableDict T)
63--
63--
64-- However, since these dictionaries are so frequently required Cloud Haskell
64-- However, since these dictionaries are so frequently required Cloud Haskell
65-- provides special support for them. When you call 'remotable' on a
65-- provides special support for them. When you call 'remotable' on a
66-- /monomorphic/ function @f :: T1 -> T2@
66-- /monomorphic/ function @f :: T1 -> T2@
67−--
67+--
68-- > remotable ['f]
68-- > remotable ['f]
69--
69--
70-- then a serialization dictionary is automatically created for you, which you
70-- then a serialization dictionary is automatically created for you, which you
@@ -80,26 +80,26 @@
80--
80--
81-- Suppose you have a process
81-- Suppose you have a process
82--
82--
83−-- > isPrime :: Integer -> Process Bool
83+-- > isPrime :: Integer -> Process Bool
84--
84--
85−-- Then
85+-- Then
86--
86--
87-- > $(mkClosure 'isPrime) :: Integer -> Closure (Process Bool)
87-- > $(mkClosure 'isPrime) :: Integer -> Closure (Process Bool)
88--
88--
89−-- which you can then 'call', for example, to have a remote node check if
89+-- which you can then 'call', for example, to have a remote node check if
90-- a number is prime.
90-- a number is prime.
91--
91--
92-- In general, if you have a /monomorphic/ function
92-- In general, if you have a /monomorphic/ function
93--
93--
94-- > f :: T1 -> T2
94-- > f :: T1 -> T2
95−--
95+--
96-- then
96-- then
97--
97--
98-- > $(mkClosure 'f) :: T1 -> Closure T2
98-- > $(mkClosure 'f) :: T1 -> Closure T2
99--
99--
100-- provided that 'T1' is serializable (*) (remember to pass 'f' to 'remotable').
100-- provided that 'T1' is serializable (*) (remember to pass 'f' to 'remotable').
101--
101--
102−-- (You can also create closures manually--see the documentation of
102+-- (You can also create closures manually--see the documentation of
103-- "Control.Distributed.Static" for examples.)
103-- "Control.Distributed.Static" for examples.)
104--
104--
105-- [Example]
105-- [Example]
@@ -114,34 +114,34 @@
114-- > import Control.Distributed.Process.Closure
114-- > import Control.Distributed.Process.Closure
115-- > import Control.Distributed.Process.Backend.SimpleLocalnet
115-- > import Control.Distributed.Process.Backend.SimpleLocalnet
116-- > import Control.Distributed.Process.Node (initRemoteTable)
116-- > import Control.Distributed.Process.Node (initRemoteTable)
117−-- >
117+-- >
118-- > isPrime :: Integer -> Process Bool
118-- > isPrime :: Integer -> Process Bool
119-- > isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
119-- > isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
120-- > where
120-- > where
121-- > sieve :: [Integer] -> [Integer]
121-- > sieve :: [Integer] -> [Integer]
122-- > sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
122-- > sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
123−-- >
123+-- >
124-- > remotable ['isPrime]
124-- > remotable ['isPrime]
125−-- >
125+-- >
126-- > master :: [NodeId] -> Process ()
126-- > master :: [NodeId] -> Process ()
127-- > master [] = liftIO $ putStrLn "no slaves"
127-- > master [] = liftIO $ putStrLn "no slaves"
128-- > master (slave:_) = do
128-- > master (slave:_) = do
129-- > isPrime79 <- call $(functionTDict 'isPrime) slave ($(mkClosure 'isPrime) (79 :: Integer))
129-- > isPrime79 <- call $(functionTDict 'isPrime) slave ($(mkClosure 'isPrime) (79 :: Integer))
130−-- > liftIO $ print isPrime79
130+-- > liftIO $ print isPrime79
131−-- >
131+-- >
132-- > main :: IO ()
132-- > main :: IO ()
133-- > main = do
133-- > main = do
134-- > args <- getArgs
134-- > args <- getArgs
135-- > case args of
135-- > case args of
136-- > ["master", host, port] -> do
136-- > ["master", host, port] -> do
137−-- > backend <- initializeBackend host port rtable
137+-- > backend <- initializeBackend host port rtable
138−-- > startMaster backend master
138+-- > startMaster backend master
139-- > ["slave", host, port] -> do
139-- > ["slave", host, port] -> do
140−-- > backend <- initializeBackend host port rtable
140+-- > backend <- initializeBackend host port rtable
141-- > startSlave backend
141-- > startSlave backend
142-- > where
142-- > where
143-- > rtable :: RemoteTable
143-- > rtable :: RemoteTable
144−-- > rtable = __remoteTable initRemoteTable
144+-- > rtable = __remoteTable initRemoteTable
145--
145--
146-- [Notes]
146-- [Notes]
147--
147--
@@ -150,53 +150,42 @@
150-- a priori if 'T1' is serializable or not due to a bug in the Template
150-- a priori if 'T1' is serializable or not due to a bug in the Template
151-- Haskell libraries (<http://hackage.haskell.org/trac/ghc/ticket/7066>)
151-- Haskell libraries (<http://hackage.haskell.org/trac/ghc/ticket/7066>)
152--
152--
153−-- (**) Even though 'call' is passed an explicit serialization
153+-- (**) Even though 'call' is passed an explicit serialization
154−-- dictionary, we still need the 'Serializable' constraint because
154+-- dictionary, we still need the 'Serializable' constraint because
155-- 'Static' is not the /true/ static. If it was, we could 'unstatic'
155-- 'Static' is not the /true/ static. If it was, we could 'unstatic'
156-- the dictionary and pattern match on it to bring the 'Typeable'
156-- the dictionary and pattern match on it to bring the 'Typeable'
157-- instance into scope, but unless proper 'static' support is added to
157-- instance into scope, but unless proper 'static' support is added to
158−-- ghc we need both the type class argument and the explicit dictionary.
158+-- ghc we need both the type class argument and the explicit dictionary.
159−module Control.Distributed.Process.Closure
159+module Control.Distributed.Process.Closure
160 ( -- * Serialization dictionaries (and their static versions)
160 ( -- * Serialization dictionaries (and their static versions)
161 SerializableDict(..)
161 SerializableDict(..)
162 , staticDecode
162 , staticDecode
163 , sdictUnit
163 , sdictUnit
164 , sdictProcessId
164 , sdictProcessId
165 , sdictSendPort
165 , sdictSendPort
166− , sdictStatic
167− , sdictClosure
168− -- * The CP type and associated combinators
166+ -- * The CP type and associated combinators
169 , CP
167 , CP
170 , idCP
168 , idCP
171 , splitCP
169 , splitCP
172 , returnCP
170 , returnCP
173− , bindCP
171+ , bindCP
174− , seqCP
172+ , seqCP
175− -- * CP versions of Cloud Haskell primitives
173+ -- * CP versions of Cloud Haskell primitives
176 , cpLink
174 , cpLink
177 , cpUnlink
175 , cpUnlink
178− , cpRelay
179 , cpSend
176 , cpSend
180 , cpExpect
177 , cpExpect
181 , cpNewChan
178 , cpNewChan
182− -- * Working with static values and closures (without Template Haskell)
183− , RemoteRegister
184− , MkTDict(..)
185− , mkStaticVal
186− , mkClosureValSingle
187− , mkClosureVal
188− , call'
189−#ifdef TemplateHaskellSupport
179+#ifdef TemplateHaskellSupport
190− -- * Template Haskell support for creating static values and closures
180+ -- * Template Haskell support for creating static values and closures
191 , remotable
181 , remotable
192 , remotableDecl
182 , remotableDecl
193 , mkStatic
183 , mkStatic
194 , mkClosure
184 , mkClosure
195− , mkStaticClosure
196 , functionSDict
185 , functionSDict
197 , functionTDict
186 , functionTDict
198#endif
187#endif
199− ) where
188+ ) where
200
189
201import Control.Distributed.Process.Serializable (SerializableDict(..))
190import Control.Distributed.Process.Serializable (SerializableDict(..))
202import Control.Distributed.Process.Internal.Closure.BuiltIn
191import Control.Distributed.Process.Internal.Closure.BuiltIn
@@ -205,40 +194,27 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
205 , sdictUnit
194 , sdictUnit
206 , sdictProcessId
195 , sdictProcessId
207 , sdictSendPort
196 , sdictSendPort
208− , sdictStatic
209− , sdictClosure
210− -- The CP type and associated combinators
197+ -- The CP type and associated combinators
211 , CP
198 , CP
212 , idCP
199 , idCP
213 , splitCP
200 , splitCP
214 , returnCP
201 , returnCP
215− , bindCP
202+ , bindCP
216− , seqCP
203+ , seqCP
217− -- CP versions of Cloud Haskell primitives
204+ -- CP versions of Cloud Haskell primitives
218 , cpLink
205 , cpLink
219 , cpUnlink
206 , cpUnlink
220− , cpRelay
221 , cpSend
207 , cpSend
222 , cpExpect
208 , cpExpect
223 , cpNewChan
209 , cpNewChan
224 )
210 )
225−import Control.Distributed.Process.Internal.Closure.Explicit
226− (
227− RemoteRegister
228− , MkTDict(..)
229− , mkStaticVal
230− , mkClosureValSingle
231− , mkClosureVal
232− , call'
233− )
234−#ifdef TemplateHaskellSupport
211+#ifdef TemplateHaskellSupport
235−import Control.Distributed.Process.Internal.Closure.TH
212+import Control.Distributed.Process.Internal.Closure.TH
236 ( remotable
213 ( remotable
237 , remotableDecl
214 , remotableDecl
238 , mkStatic
215 , mkStatic
239 , functionSDict
216 , functionSDict
240 , functionTDict
217 , functionTDict
241 , mkClosure
218 , mkClosure
242− , mkStaticClosure
243 )
219 )
244#endif
220#endif
distributed-process/src/Control/Distributed/Process/Internal/CQueue.hsnew file+105
@@ -0,0 +1,105 @@
1+-- | Concurrent queue for single reader, single writer
2+module Control.Distributed.Process.Internal.CQueue
3+ ( CQueue
4+ , BlockSpec(..)
5+ , newCQueue
6+ , enqueue
7+ , dequeue
8+ ) where
9+
10+import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar)
11+import Control.Concurrent.STM
12+ ( atomically
13+ , TChan
14+ , newTChan
15+ , writeTChan
16+ , readTChan
17+ , tryReadTChan
18+ )
19+import Control.Applicative ((<$>), (<*>))
20+import Control.Exception (mask, onException)
21+import System.Timeout (timeout)
22+
23+-- We use a TCHan rather than a Chan so that we have a non-blocking read
24+data CQueue a = CQueue (MVar [a]) -- Arrived
25+ (TChan a) -- Incoming
26+
27+newCQueue :: IO (CQueue a)
28+newCQueue = CQueue <$> newMVar [] <*> atomically newTChan
29+
30+enqueue :: CQueue a -> a -> IO ()
31+enqueue (CQueue _arrived incoming) a = atomically $ writeTChan incoming a
32+
33+data BlockSpec =
34+ NonBlocking
35+ | Blocking
36+ | Timeout Int
37+
38+-- | Dequeue an element
39+--
40+-- The timeout (if any) is applied only to waiting for incoming messages, not
41+-- to checking messages that have already arrived
42+dequeue :: forall a b.
43+ CQueue a -- ^ Queue
44+ -> BlockSpec -- ^ Blocking behaviour
45+ -> [a -> Maybe b] -- ^ List of matches
46+ -> IO (Maybe b) -- ^ 'Nothing' only on timeout
47+dequeue (CQueue arrived incoming) blockSpec matches = go
48+ where
49+ go :: IO (Maybe b)
50+ go = mask $ \restore -> do
51+ arr <- takeMVar arrived
52+ -- We first check the arrived messages. If we get interrupted during this
53+ -- search, we just put the MVar back (we haven't read from the Chan yet)
54+ (arr', mb) <- onException (restore (checkArrived [] arr))
55+ (putMVar arrived arr)
56+ case (mb, blockSpec) of
57+ (Just b, _) -> do
58+ putMVar arrived arr'
59+ return (Just b)
60+ (Nothing, NonBlocking) ->
61+ checkNonBlocking arr'
62+ (Nothing, Blocking) ->
63+ Just <$> checkBlocking arr'
64+ (Nothing, Timeout n) ->
65+ timeout n $ checkBlocking arr'
66+
67+ -- We reverse the accumulator on return only if we find a match
68+ checkArrived :: [a] -> [a] -> IO ([a], Maybe b)
69+ checkArrived acc [] = return (acc, Nothing)
70+ checkArrived acc (x:xs) =
71+ case check x of
72+ Just y -> return (reverse acc ++ xs, Just y)
73+ Nothing -> checkArrived (x:acc) xs
74+
75+ -- If we call checkBlocking there may or may not be a timeout
76+ checkBlocking :: [a] -> IO b
77+ checkBlocking acc = do
78+ -- readTChan is a blocking call, and hence is interruptable. If it is
79+ -- interrupted, we put the value of the accumulator in 'arrived'
80+ -- (as opposed to the original value), so that no messages get lost
81+ -- (hence the low-level structure using mask rather than modifyMVar)
82+ x <- onException (atomically $ readTChan incoming)
83+ (putMVar arrived $ reverse acc)
84+ case check x of
85+ Nothing -> checkBlocking (x:acc)
86+ Just y -> putMVar arrived (reverse acc) >> return y
87+
88+ -- checkNonBlocking is only called if there is no timeout
89+ checkNonBlocking :: [a] -> IO (Maybe b)
90+ checkNonBlocking acc = do
91+ -- tryReadTChan is *not* interruptible
92+ mx <- atomically $ tryReadTChan incoming
93+ case mx of
94+ Nothing -> putMVar arrived (reverse acc) >> return Nothing
95+ Just x -> case check x of
96+ Nothing -> checkNonBlocking (x:acc)
97+ Just y -> putMVar arrived (reverse acc) >> return (Just y)
98+
99+ check :: a -> Maybe b
100+ check = checkMatches matches
101+
102+ checkMatches :: [a -> Maybe b] -> a -> Maybe b
103+ checkMatches [] _ = Nothing
104+ checkMatches (m:ms) a = case m a of Nothing -> checkMatches ms a
105+ Just b -> Just b
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs → distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hsrenamed+39−121
@@ -1,42 +1,33 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−{-# LANGUAGE RankNTypes #-}
3−module Control.Distributed.Process.Internal.Closure.BuiltIn
1+module Control.Distributed.Process.Internal.Closure.BuiltIn
4− ( -- * Remote table
2+ ( -- * Remote table
5 remoteTable
3 remoteTable
6 -- * Static dictionaries and associated operations
4 -- * Static dictionaries and associated operations
7 , staticDecode
5 , staticDecode
8 , sdictUnit
6 , sdictUnit
9 , sdictProcessId
7 , sdictProcessId
10 , sdictSendPort
8 , sdictSendPort
11− , sdictStatic
12− , sdictClosure
13 -- * Some static values
9 -- * Some static values
14 , sndStatic
10 , sndStatic
15− -- * The CP type and associated combinators
11+ -- * The CP type and associated combinators
16 , CP
12 , CP
17 , idCP
13 , idCP
18 , splitCP
14 , splitCP
19 , returnCP
15 , returnCP
20− , bindCP
16+ , bindCP
21− , seqCP
17+ , seqCP
22− -- * CP versions of Cloud Haskell primitives
18+ -- * CP versions of Cloud Haskell primitives
23− , decodeProcessIdStatic
24 , cpLink
19 , cpLink
25 , cpUnlink
20 , cpUnlink
26− , cpRelay
27 , cpSend
21 , cpSend
28 , cpExpect
22 , cpExpect
29 , cpNewChan
23 , cpNewChan
30− -- * Support for some CH operations
31− , cpDelayed
32− , cpEnableTraceRemote
33 ) where
24 ) where
34
25
35−import Data.ByteString.Lazy (ByteString)
26+import Data.ByteString.Lazy (ByteString)
36import Data.Binary (decode, encode)
27import Data.Binary (decode, encode)
37import Data.Rank1Typeable (Typeable, ANY, ANY1, ANY2, ANY3, ANY4)
28import Data.Rank1Typeable (Typeable, ANY, ANY1, ANY2, ANY3, ANY4)
38import Data.Rank1Dynamic (toDynamic)
29import Data.Rank1Dynamic (toDynamic)
39−import Control.Distributed.Static
30+import Control.Distributed.Static
40 ( RemoteTable
31 ( RemoteTable
41 , registerStatic
32 , registerStatic
42 , Static
33 , Static
@@ -49,30 +40,22 @@ import Control.Distributed.Static
49 , staticCompose
40 , staticCompose
50 , staticClosure
41 , staticClosure
51 )
42 )
52−import Control.Distributed.Process.Serializable
43+import Control.Distributed.Process.Serializable
53 ( SerializableDict(..)
44 ( SerializableDict(..)
54 , Serializable
45 , Serializable
55− , TypeableDict(..)
56 )
46 )
57−import Control.Distributed.Process.Internal.Types
47+import Control.Distributed.Process.Internal.Types
58 ( Process
48 ( Process
59 , ProcessId
49 , ProcessId
60 , SendPort
50 , SendPort
61 , ReceivePort
51 , ReceivePort
62− , ProcessMonitorNotification(ProcessMonitorNotification)
63 )
52 )
64−import Control.Distributed.Process.Internal.Primitives
53+import Control.Distributed.Process.Internal.Primitives
65 ( link
54 ( link
66 , unlink
55 , unlink
67− , relay
68 , send
56 , send
69 , expect
57 , expect
70 , newChan
58 , newChan
71− , monitor
72− , unmonitor
73− , match
74− , matchIf
75− , receiveWait
76 )
59 )
77
60
78--------------------------------------------------------------------------------
61--------------------------------------------------------------------------------
@@ -80,26 +63,22 @@ import Control.Distributed.Process.Internal.Primitives
80--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
81
64
82remoteTable :: RemoteTable -> RemoteTable
65remoteTable :: RemoteTable -> RemoteTable
83−remoteTable =
66+remoteTable =
84 registerStatic "$decodeDict" (toDynamic (decodeDict :: SerializableDict ANY -> ByteString -> ANY))
67 registerStatic "$decodeDict" (toDynamic (decodeDict :: SerializableDict ANY -> ByteString -> ANY))
85 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
68 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
86− . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
69+ . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
87 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
70 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
88− . registerStatic "$sdictStatic" (toDynamic (sdictStatic_ :: TypeableDict ANY -> SerializableDict (Static ANY)))
89− . registerStatic "$sdictClosure" (toDynamic (sdictClosure_ :: TypeableDict ANY -> SerializableDict (Closure ANY)))
90 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
71 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
91 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
72 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
92 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
73 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
93 . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> ProcessId))
74 . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> ProcessId))
94 . registerStatic "$link" (toDynamic link)
75 . registerStatic "$link" (toDynamic link)
95 . registerStatic "$unlink" (toDynamic unlink)
76 . registerStatic "$unlink" (toDynamic unlink)
96− . registerStatic "$relay" (toDynamic relay)
97 . registerStatic "$sendDict" (toDynamic (sendDict :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))
77 . registerStatic "$sendDict" (toDynamic (sendDict :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))
98 . registerStatic "$expectDict" (toDynamic (expectDict :: SerializableDict ANY -> Process ANY))
78 . registerStatic "$expectDict" (toDynamic (expectDict :: SerializableDict ANY -> Process ANY))
99 . registerStatic "$newChanDict" (toDynamic (newChanDict :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))
79 . registerStatic "$newChanDict" (toDynamic (newChanDict :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))
100 . registerStatic "$cpSplit" (toDynamic (cpSplit :: (ANY1 -> Process ANY3) -> (ANY2 -> Process ANY4) -> (ANY1, ANY2) -> Process (ANY3, ANY4)))
80 . registerStatic "$cpSplit" (toDynamic (cpSplit :: (ANY1 -> Process ANY3) -> (ANY2 -> Process ANY4) -> (ANY1, ANY2) -> Process (ANY3, ANY4)))
101 . registerStatic "$snd" (toDynamic (snd :: (ANY1, ANY2) -> ANY2))
81 . registerStatic "$snd" (toDynamic (snd :: (ANY1, ANY2) -> ANY2))
102− . registerStatic "$delay" (toDynamic delay)
103 where
82 where
104 decodeDict :: forall a. SerializableDict a -> ByteString -> a
83 decodeDict :: forall a. SerializableDict a -> ByteString -> a
105 decodeDict SerializableDict = decode
84 decodeDict SerializableDict = decode
@@ -107,12 +86,6 @@ remoteTable =
107 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
86 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
108 sdictSendPort_ SerializableDict = SerializableDict
87 sdictSendPort_ SerializableDict = SerializableDict
109
88
110− sdictStatic_ :: forall a. TypeableDict a -> SerializableDict (Static a)
111− sdictStatic_ TypeableDict = SerializableDict
112−
113− sdictClosure_ :: forall a. TypeableDict a -> SerializableDict (Closure a)
114− sdictClosure_ TypeableDict = SerializableDict
115−
116 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
89 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
117 sendDict SerializableDict = send
90 sendDict SerializableDict = send
118
91
@@ -137,31 +110,23 @@ remoteTable =
137-- See module documentation of "Control.Distributed.Process.Closure" for an
110-- See module documentation of "Control.Distributed.Process.Closure" for an
138-- example.
111-- example.
139staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)
112staticDecode :: Typeable a => Static (SerializableDict a) -> Static (ByteString -> a)
140−staticDecode dict = decodeDictStatic `staticApply` dict
113+staticDecode dict = decodeDictStatic `staticApply` dict
141 where
114 where
142 decodeDictStatic :: Typeable a => Static (SerializableDict a -> ByteString -> a)
115 decodeDictStatic :: Typeable a => Static (SerializableDict a -> ByteString -> a)
143 decodeDictStatic = staticLabel "$decodeDict"
116 decodeDictStatic = staticLabel "$decodeDict"
144
117
145−-- | Serialization dictionary for '()'
118+-- | Serialization dictionary for '()'
146sdictUnit :: Static (SerializableDict ())
119sdictUnit :: Static (SerializableDict ())
147−sdictUnit = staticLabel "$sdictUnit"
120+sdictUnit = staticLabel "$sdictUnit"
148
121
149−-- | Serialization dictionary for 'ProcessId'
122+-- | Serialization dictionary for 'ProcessId'
150sdictProcessId :: Static (SerializableDict ProcessId)
123sdictProcessId :: Static (SerializableDict ProcessId)
151−sdictProcessId = staticLabel "$sdictProcessId"
124+sdictProcessId = staticLabel "$sdictProcessId"
152
125
153-- | Serialization dictionary for 'SendPort'
126-- | Serialization dictionary for 'SendPort'
154−sdictSendPort :: Typeable a
127+sdictSendPort :: Typeable a
155 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
128 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
156−sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
129+sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
157−
158−-- | Serialization dictionary for 'Static'.
159−sdictStatic :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Static a))
160−sdictStatic = staticApply (staticLabel "$sdictStatic")
161−
162−-- | Serialization dictionary for 'Closure'.
163−sdictClosure :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Closure a))
164−sdictClosure = staticApply (staticLabel "$sdictClosure")
165
130
166--------------------------------------------------------------------------------
131--------------------------------------------------------------------------------
167-- Static values --
132-- Static values --
@@ -180,20 +145,20 @@ type CP a b = Closure (a -> Process b)
180returnProcessStatic :: Typeable a => Static (a -> Process a)
145returnProcessStatic :: Typeable a => Static (a -> Process a)
181returnProcessStatic = staticLabel "$returnProcess"
146returnProcessStatic = staticLabel "$returnProcess"
182
147
183−-- | 'CP' version of 'Control.Category.id'
148+-- | 'CP' version of 'Control.Category.id'
184idCP :: Typeable a => CP a a
149idCP :: Typeable a => CP a a
185idCP = staticClosure returnProcessStatic
150idCP = staticClosure returnProcessStatic
186
151
187-- | 'CP' version of ('Control.Arrow.***')
152-- | 'CP' version of ('Control.Arrow.***')
188−splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d)
153+splitCP :: (Typeable a, Typeable b, Typeable c, Typeable d)
189 => CP a c -> CP b d -> CP (a, b) (c, d)
154 => CP a c -> CP b d -> CP (a, b) (c, d)
190splitCP p q = cpSplitStatic `closureApplyStatic` p `closureApply` q
155splitCP p q = cpSplitStatic `closureApplyStatic` p `closureApply` q
191 where
156 where
192 cpSplitStatic :: Static ((a -> Process c) -> (b -> Process d) -> (a, b) -> Process (c, d))
157 cpSplitStatic :: Static ((a -> Process c) -> (b -> Process d) -> (a, b) -> Process (c, d))
193− cpSplitStatic = staticLabel "$cpSplit"
158+ cpSplitStatic = staticLabel "$cpSplit"
194
159
195-- | 'CP' version of 'Control.Monad.return'
160-- | 'CP' version of 'Control.Monad.return'
196−returnCP :: forall a. Serializable a
161+returnCP :: forall a. Serializable a
197 => Static (SerializableDict a) -> a -> Closure (Process a)
162 => Static (SerializableDict a) -> a -> Closure (Process a)
198returnCP dict x = closure decoder (encode x)
163returnCP dict x = closure decoder (encode x)
199 where
164 where
@@ -205,18 +170,18 @@ returnCP dict x = closure decoder (encode x)
205-- | 'CP' version of ('Control.Monad.>>')
170-- | 'CP' version of ('Control.Monad.>>')
206seqCP :: (Typeable a, Typeable b)
171seqCP :: (Typeable a, Typeable b)
207 => Closure (Process a) -> Closure (Process b) -> Closure (Process b)
172 => Closure (Process a) -> Closure (Process b) -> Closure (Process b)
208−seqCP p q = seqProcessStatic `closureApplyStatic` p `closureApply` q
173+seqCP p q = seqProcessStatic `closureApplyStatic` p `closureApply` q
209 where
174 where
210 seqProcessStatic :: (Typeable a, Typeable b)
175 seqProcessStatic :: (Typeable a, Typeable b)
211 => Static (Process a -> Process b -> Process b)
176 => Static (Process a -> Process b -> Process b)
212 seqProcessStatic = staticLabel "$seqProcess"
177 seqProcessStatic = staticLabel "$seqProcess"
213
178
214−-- | (Not quite the) 'CP' version of ('Control.Monad.>>=')
179+-- | (Not quite the) 'CP' version of ('Control.Monad.>>=')
215bindCP :: forall a b. (Typeable a, Typeable b)
180bindCP :: forall a b. (Typeable a, Typeable b)
216 => Closure (Process a) -> CP a b -> Closure (Process b)
181 => Closure (Process a) -> CP a b -> Closure (Process b)
217−bindCP x f = bindProcessStatic `closureApplyStatic` x `closureApply` f
182+bindCP x f = bindProcessStatic `closureApplyStatic` x `closureApply` f
218 where
183 where
219− bindProcessStatic :: (Typeable a, Typeable b)
184+ bindProcessStatic :: (Typeable a, Typeable b)
220 => Static (Process a -> (a -> Process b) -> Process b)
185 => Static (Process a -> (a -> Process b) -> Process b)
221 bindProcessStatic = staticLabel "$bindProcess"
186 bindProcessStatic = staticLabel "$bindProcess"
222
187
@@ -229,7 +194,7 @@ decodeProcessIdStatic = staticLabel "$decodeProcessId"
229
194
230-- | 'CP' version of 'link'
195-- | 'CP' version of 'link'
231cpLink :: ProcessId -> Closure (Process ())
196cpLink :: ProcessId -> Closure (Process ())
232−cpLink = closure (linkStatic `staticCompose` decodeProcessIdStatic) . encode
197+cpLink = closure (linkStatic `staticCompose` decodeProcessIdStatic) . encode
233 where
198 where
234 linkStatic :: Static (ProcessId -> Process ())
199 linkStatic :: Static (ProcessId -> Process ())
235 linkStatic = staticLabel "$link"
200 linkStatic = staticLabel "$link"
@@ -242,18 +207,18 @@ cpUnlink = closure (unlinkStatic `staticCompose` decodeProcessIdStatic) . encode
242 unlinkStatic = staticLabel "$unlink"
207 unlinkStatic = staticLabel "$unlink"
243
208
244-- | 'CP' version of 'send'
209-- | 'CP' version of 'send'
245−cpSend :: forall a. Typeable a
210+cpSend :: forall a. Typeable a
246− => Static (SerializableDict a) -> ProcessId -> CP a ()
211+ => Static (SerializableDict a) -> ProcessId -> CP a ()
247cpSend dict pid = closure decoder (encode pid)
212cpSend dict pid = closure decoder (encode pid)
248 where
213 where
249 decoder :: Static (ByteString -> a -> Process ())
214 decoder :: Static (ByteString -> a -> Process ())
250 decoder = (sendDictStatic `staticApply` dict)
215 decoder = (sendDictStatic `staticApply` dict)
251− `staticCompose`
216+ `staticCompose`
252− decodeProcessIdStatic
217+ decodeProcessIdStatic
253
218
254− sendDictStatic :: Typeable a
219+ sendDictStatic :: Typeable a
255 => Static (SerializableDict a -> ProcessId -> a -> Process ())
220 => Static (SerializableDict a -> ProcessId -> a -> Process ())
256− sendDictStatic = staticLabel "$sendDict"
221+ sendDictStatic = staticLabel "$sendDict"
257
222
258-- | 'CP' version of 'expect'
223-- | 'CP' version of 'expect'
259cpExpect :: Typeable a => Static (SerializableDict a) -> Closure (Process a)
224cpExpect :: Typeable a => Static (SerializableDict a) -> Closure (Process a)
@@ -263,58 +228,11 @@ cpExpect dict = staticClosure (expectDictStatic `staticApply` dict)
263 expectDictStatic = staticLabel "$expectDict"
228 expectDictStatic = staticLabel "$expectDict"
264
229
265-- | 'CP' version of 'newChan'
230-- | 'CP' version of 'newChan'
266−cpNewChan :: Typeable a
231+cpNewChan :: Typeable a
267− => Static (SerializableDict a)
232+ => Static (SerializableDict a)
268 -> Closure (Process (SendPort a, ReceivePort a))
233 -> Closure (Process (SendPort a, ReceivePort a))
269cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)
234cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)
270 where
235 where
271− newChanDictStatic :: Typeable a
236+ newChanDictStatic :: Typeable a
272 => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))
237 => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))
273− newChanDictStatic = staticLabel "$newChanDict"
238+ newChanDictStatic = staticLabel "$newChanDict"
274−
275−-- | 'CP' version of 'relay'
276−cpRelay :: ProcessId -> Closure (Process ())
277−cpRelay = closure (relayStatic `staticCompose` decodeProcessIdStatic) . encode
278− where
279− relayStatic :: Static (ProcessId -> Process ())
280− relayStatic = staticLabel "$relay"
281−
282−-- TODO: move cpEnableTraceRemote into Trace/Primitives.hs
283−
284−cpEnableTraceRemote :: ProcessId -> Closure (Process ())
285−cpEnableTraceRemote =
286− closure (enableTraceStatic `staticCompose` decodeProcessIdStatic) . encode
287− where
288− enableTraceStatic :: Static (ProcessId -> Process ())
289− enableTraceStatic = staticLabel "$enableTraceRemote"
290−
291−--------------------------------------------------------------------------------
292−-- Support for spawn --
293−--------------------------------------------------------------------------------
294−
295−-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
296−-- from 'them' (origin is not verified) before proceeding as @p@. In order to
297−-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
298−-- monitor message instead, it proceeds as @p@ too.
299−delay :: ProcessId -> Process () -> Process ()
300−delay them p = do
301− ref <- monitor them
302− let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
303− receiveWait [
304− match $ \() -> unmonitor ref
305− , matchIf sameRef $ \_ -> return ()
306− ]
307− p
308−
309−-- | 'CP' version of 'delay'
310−cpDelayed :: ProcessId -> Closure (Process ()) -> Closure (Process ())
311−cpDelayed = closureApply . cpDelay'
312− where
313− cpDelay' :: ProcessId -> Closure (Process () -> Process ())
314− cpDelay' pid = closure decoder (encode pid)
315−
316− decoder :: Static (ByteString -> Process () -> Process ())
317− decoder = delayStatic `staticCompose` decodeProcessIdStatic
318−
319− delayStatic :: Static (ProcessId -> Process () -> Process ())
320− delayStatic = staticLabel "$delay"
src/Control/Distributed/Process/Internal/Closure/TH.hs → distributed-process/src/Control/Distributed/Process/Internal/Closure/TH.hsrenamed+63−100
@@ -1,6 +1,6 @@
1-- | Template Haskell support
1-- | Template Haskell support
2−{-# LANGUAGE TemplateHaskell, CPP #-}
2+{-# LANGUAGE TemplateHaskell #-}
3−module Control.Distributed.Process.Internal.Closure.TH
3+module Control.Distributed.Process.Internal.Closure.TH
4 ( -- * User-level API
4 ( -- * User-level API
5 remotable
5 remotable
6 , remotableDecl
6 , remotableDecl
@@ -8,17 +8,14 @@ module Control.Distributed.Process.Internal.Closure.TH
8 , functionSDict
8 , functionSDict
9 , functionTDict
9 , functionTDict
10 , mkClosure
10 , mkClosure
11− , mkStaticClosure
12 ) where
11 ) where
13
12
14import Prelude hiding (succ, any)
13import Prelude hiding (succ, any)
15import Control.Applicative ((<$>))
14import Control.Applicative ((<$>))
16−import Language.Haskell.TH
15+import Language.Haskell.TH
17 ( -- Q monad and operations
16 ( -- Q monad and operations
18 Q
17 Q
19 , reify
18 , reify
20− , Loc(loc_module)
21− , location
22 -- Names
19 -- Names
23 , Name
20 , Name
24 , mkName
21 , mkName
@@ -29,14 +26,7 @@ import Language.Haskell.TH
29 , Type(AppT, ForallT, VarT, ArrowT)
26 , Type(AppT, ForallT, VarT, ArrowT)
30 , Info(VarI)
27 , Info(VarI)
31 , TyVarBndr(PlainTV, KindedTV)
28 , TyVarBndr(PlainTV, KindedTV)
32− , Pred
29+ , Pred(ClassP)
33−#if MIN_VERSION_template_haskell(2,10,0)
34− , conT
35− , appT
36−#else
37− , classP
38−#endif
39− , varT
40 -- Lifted constructors
30 -- Lifted constructors
41 -- .. Literals
31 -- .. Literals
42 , stringL
32 , stringL
@@ -50,7 +40,7 @@ import Language.Haskell.TH
50 , funD
40 , funD
51 , sigD
41 , sigD
52 )
42 )
53−import Data.Maybe (catMaybes)
43+import Data.Maybe (catMaybes)
54import Data.Binary (encode)
44import Data.Binary (encode)
55import Data.Generics (everywhereM, mkM, gmapM)
45import Data.Generics (everywhereM, mkM, gmapM)
56import Data.Rank1Dynamic (toDynamic)
46import Data.Rank1Dynamic (toDynamic)
@@ -59,17 +49,16 @@ import Data.Rank1Typeable
59 , Succ
49 , Succ
60 , TypVar
50 , TypVar
61 )
51 )
62−import Control.Distributed.Static
52+import Control.Distributed.Static
63 ( RemoteTable
53 ( RemoteTable
64 , registerStatic
54 , registerStatic
65 , Static
55 , Static
66 , staticLabel
56 , staticLabel
67 , closure
57 , closure
68 , staticCompose
58 , staticCompose
69− , staticClosure
70 )
59 )
71import Control.Distributed.Process.Internal.Types (Process)
60import Control.Distributed.Process.Internal.Types (Process)
72−import Control.Distributed.Process.Serializable
61+import Control.Distributed.Process.Serializable
73 ( SerializableDict(SerializableDict)
62 ( SerializableDict(SerializableDict)
74 )
63 )
75import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)
64import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)
@@ -80,12 +69,12 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)
80
69
81-- | Create the closure, decoder, and metadata definitions for the given list
70-- | Create the closure, decoder, and metadata definitions for the given list
82-- of functions
71-- of functions
83−remotable :: [Name] -> Q [Dec]
72+remotable :: [Name] -> Q [Dec]
84remotable ns = do
73remotable ns = do
85− types <- mapM getType ns
74+ types <- mapM getType ns
86− (closures, inserts) <- unzip <$> mapM generateDefs types
75+ (closures, inserts) <- unzip <$> mapM generateDefs types
87 rtable <- createMetaData (mkName "__remoteTable") (concat inserts)
76 rtable <- createMetaData (mkName "__remoteTable") (concat inserts)
88− return $ concat closures ++ rtable
77+ return $ concat closures ++ rtable
89
78
90-- | Like 'remotable', but parameterized by the declaration of a function
79-- | Like 'remotable', but parameterized by the declaration of a function
91-- instead of the function name. So where for 'remotable' you'd do
80-- instead of the function name. So where for 'remotable' you'd do
@@ -115,9 +104,9 @@ remotableDecl :: [Q [Dec]] -> Q [Dec]
115remotableDecl qDecs = do
104remotableDecl qDecs = do
116 decs <- concat <$> sequence qDecs
105 decs <- concat <$> sequence qDecs
117 let types = catMaybes (map typeOf decs)
106 let types = catMaybes (map typeOf decs)
118− (closures, inserts) <- unzip <$> mapM generateDefs types
107+ (closures, inserts) <- unzip <$> mapM generateDefs types
119 rtable <- createMetaData (mkName "__remoteTableDecl") (concat inserts)
108 rtable <- createMetaData (mkName "__remoteTableDecl") (concat inserts)
120− return $ decs ++ concat closures ++ rtable
109+ return $ decs ++ concat closures ++ rtable
121 where
110 where
122 typeOf :: Dec -> Maybe (Name, Type)
111 typeOf :: Dec -> Maybe (Name, Type)
123 typeOf (SigD name typ) = Just (name, typ)
112 typeOf (SigD name typ) = Just (name, typ)
@@ -125,15 +114,15 @@ remotableDecl qDecs = do
125
114
126-- | Construct a static value.
115-- | Construct a static value.
127--
116--
128−-- If @f : forall a1 .. an. T@
117+-- If @f : forall a1 .. an. T@
129−-- then @$(mkStatic 'f) :: forall a1 .. an. Static T@.
118+-- then @$(mkStatic 'f) :: forall a1 .. an. Static T@.
130−-- Be sure to pass 'f' to 'remotable'.
119+-- Be sure to pass 'f' to 'remotable'.
131mkStatic :: Name -> Q Exp
120mkStatic :: Name -> Q Exp
132mkStatic = varE . staticName
121mkStatic = varE . staticName
133
122
134−-- | If @f : T1 -> T2@ is a monomorphic function
123+-- | If @f : T1 -> T2@ is a monomorphic function
135-- then @$(functionSDict 'f) :: Static (SerializableDict T1)@.
124-- then @$(functionSDict 'f) :: Static (SerializableDict T1)@.
136−--
125+--
137-- Be sure to pass 'f' to 'remotable'.
126-- Be sure to pass 'f' to 'remotable'.
138functionSDict :: Name -> Q Exp
127functionSDict :: Name -> Q Exp
139functionSDict = varE . sdictName
128functionSDict = varE . sdictName
@@ -145,31 +134,24 @@ functionSDict = varE . sdictName
145functionTDict :: Name -> Q Exp
134functionTDict :: Name -> Q Exp
146functionTDict = varE . tdictName
135functionTDict = varE . tdictName
147
136
148−-- | If @f : T1 -> T2@ then @$(mkClosure 'f) :: T1 -> Closure T2@.
137+-- | If @f : T1 -> T2@ then @$(mkClosure 'f) :: T1 -> Closure T2@.
149--
138--
150−-- TODO: The current version of mkClosure is too polymorphic
139+-- TODO: The current version of mkClosure is too polymorphic
151-- (@forall a. Binary a => a -> Closure T2).
140-- (@forall a. Binary a => a -> Closure T2).
152mkClosure :: Name -> Q Exp
141mkClosure :: Name -> Q Exp
153−mkClosure n =
142+mkClosure n =
154− [| closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n))
143+ [| closure ($(mkStatic n) `staticCompose` staticDecode $(functionSDict n))
155 . encode
144 . encode
156 |]
145 |]
157
146
158−-- | Make a 'Closure' from a static function. This is useful for
159−-- making a closure for a top-level @Process ()@ function, because
160−-- using 'mkClosure' would require adding a dummy @()@ argument.
161−--
162−mkStaticClosure :: Name -> Q Exp
163−mkStaticClosure n = [| staticClosure $( mkStatic n ) |]
164−
165--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
166-- Internal (Template Haskell) --
148-- Internal (Template Haskell) --
167--------------------------------------------------------------------------------
149--------------------------------------------------------------------------------
168
150
169-- | Generate the code to add the metadata to the CH runtime
151-- | Generate the code to add the metadata to the CH runtime
170createMetaData :: Name -> [Q Exp] -> Q [Dec]
152createMetaData :: Name -> [Q Exp] -> Q [Dec]
171−createMetaData name is =
153+createMetaData name is =
172− sequence [ sigD name [t| RemoteTable -> RemoteTable |]
154+ sequence [ sigD name [t| RemoteTable -> RemoteTable |]
173 , sfnD name (compose is)
155 , sfnD name (compose is)
174 ]
156 ]
175
157
@@ -179,52 +161,52 @@ generateDefs (origName, fullType) = do
179 let (typVars, typ') = case fullType of ForallT vars [] mono -> (vars, mono)
161 let (typVars, typ') = case fullType of ForallT vars [] mono -> (vars, mono)
180 _ -> ([], fullType)
162 _ -> ([], fullType)
181
163
182− -- The main "static" entry
164+ -- The main "static" entry
183− (static, register) <- makeStatic typVars typ'
165+ (static, register) <- makeStatic typVars typ'
184−
166+
185− -- If n :: T1 -> T2, static serializable dictionary for T1
167+ -- If n :: T1 -> T2, static serializable dictionary for T1
186 -- TODO: we should check if arg is an instance of Serializable, but we cannot
168 -- TODO: we should check if arg is an instance of Serializable, but we cannot
187 -- http://hackage.haskell.org/trac/ghc/ticket/7066
169 -- http://hackage.haskell.org/trac/ghc/ticket/7066
188 (sdict, registerSDict) <- case (typVars, typ') of
170 (sdict, registerSDict) <- case (typVars, typ') of
189− ([], ArrowT `AppT` arg `AppT` _res) ->
171+ ([], ArrowT `AppT` arg `AppT` _res) ->
190 makeDict (sdictName origName) arg
172 makeDict (sdictName origName) arg
191− _ ->
173+ _ ->
192 return ([], [])
174 return ([], [])
193−
175+
194 -- If n :: T1 -> Process T2, static serializable dictionary for T2
176 -- If n :: T1 -> Process T2, static serializable dictionary for T2
195 -- TODO: check if T2 is serializable (same as above)
177 -- TODO: check if T2 is serializable (same as above)
196 (tdict, registerTDict) <- case (typVars, typ') of
178 (tdict, registerTDict) <- case (typVars, typ') of
197− ([], ArrowT `AppT` _arg `AppT` (proc' `AppT` res)) | proc' == proc ->
179+ ([], ArrowT `AppT` _arg `AppT` (proc' `AppT` res)) | proc' == proc ->
198− makeDict (tdictName origName) res
180+ makeDict (tdictName origName) res
199 _ ->
181 _ ->
200 return ([], [])
182 return ([], [])
201−
183+
202 return ( concat [static, sdict, tdict]
184 return ( concat [static, sdict, tdict]
203 , concat [register, registerSDict, registerTDict]
185 , concat [register, registerSDict, registerTDict]
204 )
186 )
205 where
187 where
206 makeStatic :: [TyVarBndr] -> Type -> Q ([Dec], [Q Exp])
188 makeStatic :: [TyVarBndr] -> Type -> Q ([Dec], [Q Exp])
207− makeStatic typVars typ = do
189+ makeStatic typVars typ = do
208 static <- generateStatic origName typVars typ
190 static <- generateStatic origName typVars typ
209− let dyn = case typVars of
191+ let dyn = case typVars of
210 [] -> [| toDynamic $(varE origName) |]
192 [] -> [| toDynamic $(varE origName) |]
211 _ -> [| toDynamic ($(varE origName) :: $(monomorphize typVars typ)) |]
193 _ -> [| toDynamic ($(varE origName) :: $(monomorphize typVars typ)) |]
212 return ( static
194 return ( static
213− , [ [| registerStatic $(showFQN origName) $dyn |] ]
195+ , [ [| registerStatic $(stringE (show origName)) $dyn |] ]
214 )
196 )
215
197
216− makeDict :: Name -> Type -> Q ([Dec], [Q Exp])
198+ makeDict :: Name -> Type -> Q ([Dec], [Q Exp])
217 makeDict dictName typ = do
199 makeDict dictName typ = do
218− sdict <- generateDict dictName typ
200+ sdict <- generateDict dictName typ
219 let dyn = [| toDynamic (SerializableDict :: SerializableDict $(return typ)) |]
201 let dyn = [| toDynamic (SerializableDict :: SerializableDict $(return typ)) |]
220 return ( sdict
202 return ( sdict
221− , [ [| registerStatic $(showFQN dictName) $dyn |] ]
203+ , [ [| registerStatic $(stringE (show dictName)) $dyn |] ]
222 )
204 )
223
205
224-- | Turn a polymorphic type into a monomorphic type using ANY and co
206-- | Turn a polymorphic type into a monomorphic type using ANY and co
225monomorphize :: [TyVarBndr] -> Type -> Q Type
207monomorphize :: [TyVarBndr] -> Type -> Q Type
226−monomorphize tvs =
208+monomorphize tvs =
227− let subst = zip (map tyVarBndrName tvs) anys
209+ let subst = zip (map tyVarBndrName tvs) anys
228 in everywhereM (mkM (applySubst subst))
210 in everywhereM (mkM (applySubst subst))
229 where
211 where
230 anys :: [Q Type]
212 anys :: [Q Type]
@@ -235,44 +217,39 @@ monomorphize tvs =
235
217
236 zero :: Q Type
218 zero :: Q Type
237 zero = [t| Zero |]
219 zero = [t| Zero |]
238−
220+
239 succ :: Q Type -> Q Type
221 succ :: Q Type -> Q Type
240 succ t = [t| Succ $t |]
222 succ t = [t| Succ $t |]
241−
223+
242 applySubst :: [(Name, Q Type)] -> Type -> Q Type
224 applySubst :: [(Name, Q Type)] -> Type -> Q Type
243− applySubst s (VarT n) =
225+ applySubst s (VarT n) =
244− case lookup n s of
226+ case lookup n s of
245 Nothing -> return (VarT n)
227 Nothing -> return (VarT n)
246 Just t -> t
228 Just t -> t
247 applySubst s t = gmapM (mkM (applySubst s)) t
229 applySubst s t = gmapM (mkM (applySubst s)) t
248
230
249−-- | Generate a static value
231+-- | Generate a static value
250generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
232generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
251generateStatic n xs typ = do
233generateStatic n xs typ = do
252 staticTyp <- [t| Static |]
234 staticTyp <- [t| Static |]
253 sequence
235 sequence
254− [ sigD (staticName n) $ do
236+ [ sigD (staticName n) $
237+ return (ForallT xs
255− txs <- sequence $ map typeable xs
238+ (map typeable xs)
256− return (ForallT xs
257− txs
258− (staticTyp `AppT` typ))
239+ (staticTyp `AppT` typ)
240+ )
259− , sfnD (staticName n) [| staticLabel $(showFQN n) |]
241+ , sfnD (staticName n) [| staticLabel $(stringE (show n)) |]
260 ]
242 ]
261 where
243 where
262− typeable :: TyVarBndr -> Q Pred
244+ typeable :: TyVarBndr -> Pred
263− typeable tv =
264−#if MIN_VERSION_template_haskell(2,10,0)
265− conT (mkName "Typeable") `appT` varT (tyVarBndrName tv)
266−#else
267− classP (mkName "Typeable") [varT (tyVarBndrName tv)]
245+ typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)]
268−#endif
269
246
270−-- | Generate a serialization dictionary with name 'n' for type 'typ'
247+-- | Generate a serialization dictionary with name 'n' for type 'typ'
271generateDict :: Name -> Type -> Q [Dec]
248generateDict :: Name -> Type -> Q [Dec]
272generateDict n typ = do
249generateDict n typ = do
273 sequence
250 sequence
274 [ sigD n $ [t| Static (SerializableDict $(return typ)) |]
251 [ sigD n $ [t| Static (SerializableDict $(return typ)) |]
275− , sfnD n [| staticLabel $(showFQN n) |]
252+ , sfnD n [| staticLabel $(stringE (show n)) |]
276 ]
253 ]
277
254
278staticName :: Name -> Name
255staticName :: Name -> Name
@@ -291,7 +268,7 @@ tdictName n = mkName $ nameBase n ++ "__tdict"
291-- | Compose a set of expressions
268-- | Compose a set of expressions
292compose :: [Q Exp] -> Q Exp
269compose :: [Q Exp] -> Q Exp
293compose [] = [| id |]
270compose [] = [| id |]
294−compose [e] = e
271+compose [e] = e
295compose (e:es) = [| $e . $(compose es) |]
272compose (e:es) = [| $e . $(compose es) |]
296
273
297-- | Literal string as an expression
274-- | Literal string as an expression
@@ -300,31 +277,17 @@ stringE = litE . stringL
300
277
301-- | Look up the "original name" (module:name) and type of a top-level function
278-- | Look up the "original name" (module:name) and type of a top-level function
302getType :: Name -> Q (Name, Type)
279getType :: Name -> Q (Name, Type)
303−getType name = do
280+getType name = do
304 info <- reify name
281 info <- reify name
305− case info of
282+ case info of
306−#if MIN_VERSION_template_haskell(2,11,0)
307− VarI origName typ _ -> return (origName, typ)
308−#else
309 VarI origName typ _ _ -> return (origName, typ)
283 VarI origName typ _ _ -> return (origName, typ)
310−#endif
311 _ -> fail $ show name ++ " not found"
284 _ -> fail $ show name ++ " not found"
312
285
313-- | Variation on 'funD' which takes a single expression to define the function
286-- | Variation on 'funD' which takes a single expression to define the function
314sfnD :: Name -> Q Exp -> Q Dec
287sfnD :: Name -> Q Exp -> Q Dec
315−sfnD n e = funD n [clause [] (normalB e) []]
288+sfnD n e = funD n [clause [] (normalB e) []]
316−
289+
317−-- | The name of a type variable binding occurrence
290+-- | The name of a type variable binding occurrence
318tyVarBndrName :: TyVarBndr -> Name
291tyVarBndrName :: TyVarBndr -> Name
319tyVarBndrName (PlainTV n) = n
292tyVarBndrName (PlainTV n) = n
320tyVarBndrName (KindedTV n _) = n
293tyVarBndrName (KindedTV n _) = n
321−
322−-- | Fully qualified name; that is, the name and the _current_ module
323−--
324−-- We ignore the module part of the Name argument (which may or may not exist)
325−-- because we construct various names (`staticName`, `sdictName`, `tdictName`)
326−-- and those names certainly won't have Module components.
327−showFQN :: Name -> Q Exp
328−showFQN n = do
329− loc <- location
330− stringE (loc_module loc ++ "." ++ nameBase n)
distributed-process/src/Control/Distributed/Process/Internal/Messaging.hsnew file+169
@@ -0,0 +1,169 @@
1+module Control.Distributed.Process.Internal.Messaging
2+ ( sendPayload
3+ , sendBinary
4+ , sendMessage
5+ , disconnect
6+ , closeImplicitReconnections
7+ , impliesDeathOf
8+ ) where
9+
10+import Data.Accessor ((^.), (^=))
11+import Data.Binary (Binary, encode)
12+import qualified Data.Map as Map (partitionWithKey, elems)
13+import qualified Data.ByteString.Lazy as BSL (toChunks)
14+import qualified Data.ByteString as BSS (ByteString)
15+import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
16+import Control.Concurrent.Chan (writeChan)
17+import Control.Monad (unless)
18+import qualified Network.Transport as NT
19+ ( Connection
20+ , send
21+ , defaultConnectHints
22+ , connect
23+ , Reliability(ReliableOrdered)
24+ , close
25+ )
26+import Control.Distributed.Process.Internal.Types
27+ ( LocalNode(localState, localEndPoint, localCtrlChan)
28+ , Identifier
29+ , localConnections
30+ , localConnectionBetween
31+ , nodeAddress
32+ , nodeOf
33+ , messageToPayload
34+ , createMessage
35+ , NCMsg(..)
36+ , ProcessSignal(Died)
37+ , DiedReason(DiedDisconnect)
38+ , ImplicitReconnect(WithImplicitReconnect)
39+ , NodeId
40+ , ProcessId(processNodeId)
41+ , SendPortId(sendPortProcessId)
42+ , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
43+ )
44+import Control.Distributed.Process.Serializable (Serializable)
45+
46+--------------------------------------------------------------------------------
47+-- Message sending --
48+--------------------------------------------------------------------------------
49+
50+sendPayload :: LocalNode
51+ -> Identifier
52+ -> Identifier
53+ -> ImplicitReconnect
54+ -> [BSS.ByteString]
55+ -> IO ()
56+sendPayload node from to implicitReconnect payload = do
57+ mConn <- connBetween node from to implicitReconnect
58+ didSend <- case mConn of
59+ Just conn -> do
60+ didSend <- NT.send conn payload
61+ case didSend of
62+ Left _err -> return False
63+ Right () -> return True
64+ Nothing -> return False
65+ unless didSend $ do
66+ writeChan (localCtrlChan node) NCMsg
67+ { ctrlMsgSender = to
68+ , ctrlMsgSignal = Died to DiedDisconnect
69+ }
70+
71+sendBinary :: Binary a
72+ => LocalNode
73+ -> Identifier
74+ -> Identifier
75+ -> ImplicitReconnect
76+ -> a
77+ -> IO ()
78+sendBinary node from to implicitReconnect
79+ = sendPayload node from to implicitReconnect . BSL.toChunks . encode
80+
81+sendMessage :: Serializable a
82+ => LocalNode
83+ -> Identifier
84+ -> Identifier
85+ -> ImplicitReconnect
86+ -> a
87+ -> IO ()
88+sendMessage node from to implicitReconnect =
89+ sendPayload node from to implicitReconnect . messageToPayload . createMessage
90+
91+setupConnBetween :: LocalNode
92+ -> Identifier
93+ -> Identifier
94+ -> ImplicitReconnect
95+ -> IO (Maybe NT.Connection)
96+setupConnBetween node from to implicitReconnect = do
97+ mConn <- NT.connect endPoint
98+ (nodeAddress . nodeOf $ to)
99+ NT.ReliableOrdered
100+ NT.defaultConnectHints
101+ case mConn of
102+ Right conn -> do
103+ didSend <- NT.send conn (BSL.toChunks . encode $ to)
104+ case didSend of
105+ Left _ ->
106+ return Nothing
107+ Right () -> do
108+ modifyMVar_ nodeState $ return .
109+ (localConnectionBetween from to ^= Just (conn, implicitReconnect))
110+ return $ Just conn
111+ Left _ ->
112+ return Nothing
113+ where
114+ endPoint = localEndPoint node
115+ nodeState = localState node
116+
117+connBetween :: LocalNode
118+ -> Identifier
119+ -> Identifier
120+ -> ImplicitReconnect
121+ -> IO (Maybe NT.Connection)
122+connBetween node from to implicitReconnect = do
123+ mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)
124+ case mConn of
125+ Just (conn, _) ->
126+ return $ Just conn
127+ Nothing ->
128+ setupConnBetween node from to implicitReconnect
129+ where
130+ nodeState = localState node
131+
132+disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
133+disconnect node from to =
134+ modifyMVar_ (localState node) $ \st ->
135+ case st ^. localConnectionBetween from to of
136+ Nothing ->
137+ return st
138+ Just (conn, _) -> do
139+ NT.close conn
140+ return (localConnectionBetween from to ^= Nothing $ st)
141+
142+closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
143+closeImplicitReconnections node to =
144+ modifyMVar_ (localState node) $ \st -> do
145+ let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
146+ shouldClose _ _ = False
147+ let (affected, unaffected) = Map.partitionWithKey shouldClose (st ^. localConnections)
148+ mapM_ (NT.close . fst) (Map.elems affected)
149+ return (localConnections ^= unaffected $ st)
150+
151+-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
152+-- implies the death of @b@ (for instance, a process on that node)
153+impliesDeathOf :: Identifier
154+ -> Identifier
155+ -> Bool
156+NodeIdentifier nid `impliesDeathOf` NodeIdentifier nid' =
157+ nid' == nid
158+NodeIdentifier nid `impliesDeathOf` ProcessIdentifier pid =
159+ processNodeId pid == nid
160+NodeIdentifier nid `impliesDeathOf` SendPortIdentifier cid =
161+ processNodeId (sendPortProcessId cid) == nid
162+ProcessIdentifier pid `impliesDeathOf` ProcessIdentifier pid' =
163+ pid' == pid
164+ProcessIdentifier pid `impliesDeathOf` SendPortIdentifier cid =
165+ sendPortProcessId cid == pid
166+SendPortIdentifier cid `impliesDeathOf` SendPortIdentifier cid' =
167+ cid' == cid
168+_ `impliesDeathOf` _ =
169+ False
distributed-process/src/Control/Distributed/Process/Internal/Primitives.hsnew file+661
@@ -0,0 +1,661 @@
1+-- | Cloud Haskell primitives
2+--
3+-- We define these in a separate module so that we don't have to rely on
4+-- the closure combinators
5+module Control.Distributed.Process.Internal.Primitives
6+ ( -- * Basic messaging
7+ send
8+ , expect
9+ -- * Channels
10+ , newChan
11+ , sendChan
12+ , receiveChan
13+ , mergePortsBiased
14+ , mergePortsRR
15+ -- * Advanced messaging
16+ , Match
17+ , receiveWait
18+ , receiveTimeout
19+ , match
20+ , matchIf
21+ , matchUnknown
22+ , AbstractMessage(..)
23+ , matchAny
24+ -- * Process management
25+ , terminate
26+ , ProcessTerminationException(..)
27+ , getSelfPid
28+ , getSelfNode
29+ -- * Monitoring and linking
30+ , link
31+ , unlink
32+ , monitor
33+ , unmonitor
34+ -- * Logging
35+ , say
36+ -- * Registry
37+ , register
38+ , unregister
39+ , whereis
40+ , nsend
41+ , registerRemote
42+ , unregisterRemote
43+ , whereisRemoteAsync
44+ , nsendRemote
45+ -- * Closures
46+ , unClosure
47+ , unStatic
48+ -- * Exception handling
49+ , catch
50+ , mask
51+ , onException
52+ , bracket
53+ , bracket_
54+ , finally
55+ -- * Auxiliary API
56+ , expectTimeout
57+ , spawnAsync
58+ , linkNode
59+ , linkPort
60+ , unlinkNode
61+ , unlinkPort
62+ , monitorNode
63+ , monitorPort
64+ -- * Reconnecting
65+ , reconnect
66+ , reconnectPort
67+ ) where
68+
69+#if ! MIN_VERSION_base(4,6,0)
70+import Prelude hiding (catch)
71+#endif
72+
73+import Data.Binary (decode)
74+import Data.Time.Clock (getCurrentTime)
75+import Data.Time.Format (formatTime)
76+import System.Locale (defaultTimeLocale)
77+import Control.Monad.Reader (ask)
78+import Control.Monad.IO.Class (MonadIO, liftIO)
79+import Control.Applicative ((<$>))
80+import Control.Exception (Exception, throwIO, SomeException)
81+import qualified Control.Exception as Ex (catch, mask)
82+import Control.Distributed.Process.Internal.StrictMVar
83+ ( StrictMVar
84+ , modifyMVar
85+ , modifyMVar_
86+ )
87+import Control.Concurrent.Chan (writeChan)
88+import Control.Concurrent.STM
89+ ( STM
90+ , atomically
91+ , orElse
92+ , newTVar
93+ , readTVar
94+ , writeTVar
95+ )
96+import Control.Distributed.Process.Internal.CQueue (dequeue, BlockSpec(..))
97+import Control.Distributed.Process.Serializable (Serializable, fingerprint)
98+import Data.Accessor ((^.), (^:), (^=))
99+import Control.Distributed.Static (Closure, Static)
100+import Data.Rank1Typeable (Typeable)
101+import qualified Control.Distributed.Static as Static (unstatic, unclosure)
102+import Control.Distributed.Process.Internal.Types
103+ ( NodeId(..)
104+ , ProcessId(..)
105+ , LocalNode(..)
106+ , LocalProcess(..)
107+ , Process(..)
108+ , Message(..)
109+ , MonitorRef(..)
110+ , SpawnRef(..)
111+ , NCMsg(..)
112+ , ProcessSignal(..)
113+ , monitorCounter
114+ , spawnCounter
115+ , SendPort(..)
116+ , ReceivePort(..)
117+ , channelCounter
118+ , typedChannelWithId
119+ , TypedChannel(..)
120+ , SendPortId(..)
121+ , Identifier(..)
122+ , DidUnmonitor(..)
123+ , DidUnlinkProcess(..)
124+ , DidUnlinkNode(..)
125+ , DidUnlinkPort(..)
126+ , WhereIsReply(..)
127+ , createMessage
128+ , runLocalProcess
129+ , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
130+ , LocalProcessState
131+ , LocalSendPortId
132+ , messageToPayload
133+ )
134+import Control.Distributed.Process.Internal.Messaging
135+ ( sendMessage
136+ , sendBinary
137+ , sendPayload
138+ , disconnect
139+ )
140+import Control.Distributed.Process.Internal.WeakTQueue
141+ ( newTQueueIO
142+ , readTQueue
143+ , mkWeakTQueue
144+ )
145+
146+--------------------------------------------------------------------------------
147+-- Basic messaging --
148+--------------------------------------------------------------------------------
149+
150+-- | Send a message
151+send :: Serializable a => ProcessId -> a -> Process ()
152+-- This requires a lookup on every send. If we want to avoid that we need to
153+-- modify serializable to allow for stateful (IO) deserialization
154+send them msg = do
155+ proc <- ask
156+ liftIO $ sendMessage (processNode proc)
157+ (ProcessIdentifier (processId proc))
158+ (ProcessIdentifier them)
159+ NoImplicitReconnect
160+ msg
161+
162+-- | Wait for a message of a specific type
163+expect :: forall a. Serializable a => Process a
164+expect = receiveWait [match return]
165+
166+--------------------------------------------------------------------------------
167+-- Channels --
168+--------------------------------------------------------------------------------
169+
170+-- | Create a new typed channel
171+newChan :: Serializable a => Process (SendPort a, ReceivePort a)
172+newChan = do
173+ proc <- ask
174+ liftIO . modifyMVar (processState proc) $ \st -> do
175+ let lcid = st ^. channelCounter
176+ let cid = SendPortId { sendPortProcessId = processId proc
177+ , sendPortLocalId = lcid
178+ }
179+ let sport = SendPort cid
180+ chan <- liftIO newTQueueIO
181+ chan' <- mkWeakTQueue chan $ finalizer (processState proc) lcid
182+ let rport = ReceivePortSingle chan
183+ let tch = TypedChannel chan'
184+ return ( (channelCounter ^: (+ 1))
185+ . (typedChannelWithId lcid ^= Just tch)
186+ $ st
187+ , (sport, rport)
188+ )
189+ where
190+ finalizer :: StrictMVar LocalProcessState -> LocalSendPortId -> IO ()
191+ finalizer processState lcid = modifyMVar_ processState $
192+ return . (typedChannelWithId lcid ^= Nothing)
193+
194+-- | Send a message on a typed channel
195+sendChan :: Serializable a => SendPort a -> a -> Process ()
196+sendChan (SendPort cid) msg = do
197+ proc <- ask
198+ liftIO $ sendBinary (processNode proc)
199+ (ProcessIdentifier (processId proc))
200+ (SendPortIdentifier cid)
201+ NoImplicitReconnect
202+ msg
203+
204+-- | Wait for a message on a typed channel
205+receiveChan :: Serializable a => ReceivePort a -> Process a
206+receiveChan = 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
218+
219+ rotate :: [a] -> [a]
220+ rotate [] = []
221+ rotate (x:xs) = xs ++ [x]
222+
223+-- | Merge a list of typed channels.
224+--
225+-- The result port is left-biased: if there are messages available on more
226+-- than one port, the first available message is returned.
227+mergePortsBiased :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
228+mergePortsBiased = return . ReceivePortBiased
229+
230+-- | Like 'mergePortsBiased', but with a round-robin scheduler (rather than
231+-- left-biased)
232+mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
233+mergePortsRR ps = liftIO . atomically $ ReceivePortRR <$> newTVar ps
234+
235+--------------------------------------------------------------------------------
236+-- Advanced messaging --
237+--------------------------------------------------------------------------------
238+
239+-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
240+newtype Match b = Match { unMatch :: Message -> Maybe (Process b) }
241+
242+-- | Test the matches in order against each message in the queue
243+receiveWait :: [Match b] -> Process b
244+receiveWait ms = do
245+ queue <- processQueue <$> ask
246+ Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
247+ proc
248+
249+-- | Like 'receiveWait' but with a timeout.
250+--
251+-- If the timeout is zero do a non-blocking check for matching messages. A
252+-- non-zero timeout is applied only when waiting for incoming messages (that is,
253+-- /after/ we have checked the messages that are already in the mailbox).
254+receiveTimeout :: Int -> [Match b] -> Process (Maybe b)
255+receiveTimeout t ms = do
256+ queue <- processQueue <$> ask
257+ let blockSpec = if t == 0 then NonBlocking else Timeout t
258+ mProc <- liftIO $ dequeue queue blockSpec (map unMatch ms)
259+ case mProc of
260+ Nothing -> return Nothing
261+ Just proc -> Just <$> proc
262+
263+-- | Match against any message of the right type
264+match :: forall a b. Serializable a => (a -> Process b) -> Match b
265+match = matchIf (const True)
266+
267+-- | Match against any message of the right type that satisfies a predicate
268+matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
269+matchIf c p = Match $ \msg ->
270+ let decoded :: a
271+ decoded = decode . messageEncoding $ msg in
272+ if messageFingerprint msg == fingerprint (undefined :: a) && c decoded
273+ then Just $ p decoded
274+ else Nothing
275+
276+data AbstractMessage = AbstractMessage {
277+ forward :: ProcessId -> Process ()
278+ }
279+
280+-- | Match against an arbitrary message
281+matchAny :: forall b. (AbstractMessage -> Process b) -> Match b
282+matchAny p = Match $ Just . p . abstract
283+ where
284+ abstract :: Message -> AbstractMessage
285+ abstract msg = AbstractMessage {
286+ forward = \them -> do
287+ proc <- ask
288+ liftIO $ sendPayload (processNode proc)
289+ (ProcessIdentifier (processId proc))
290+ (ProcessIdentifier them)
291+ NoImplicitReconnect
292+ (messageToPayload msg)
293+ }
294+
295+-- | Remove any message from the queue
296+matchUnknown :: Process b -> Match b
297+matchUnknown = Match . const . Just
298+
299+--------------------------------------------------------------------------------
300+-- Process management --
301+--------------------------------------------------------------------------------
302+
303+-- | Thrown by 'terminate'
304+data ProcessTerminationException = ProcessTerminationException
305+ deriving (Show, Typeable)
306+
307+instance Exception ProcessTerminationException
308+
309+-- | Terminate (throws a ProcessTerminationException)
310+terminate :: Process a
311+terminate = liftIO $ throwIO ProcessTerminationException
312+
313+-- | Our own process ID
314+getSelfPid :: Process ProcessId
315+getSelfPid = processId <$> ask
316+
317+-- | Get the node ID of our local node
318+getSelfNode :: Process NodeId
319+getSelfNode = localNodeId . processNode <$> ask
320+
321+--------------------------------------------------------------------------------
322+-- Monitoring and linking --
323+--------------------------------------------------------------------------------
324+
325+-- | Link to a remote process (asynchronous)
326+--
327+-- Note that 'link' provides unidirectional linking (see 'spawnSupervised').
328+-- Linking makes no distinction between normal and abnormal termination of
329+-- the remote process.
330+link :: ProcessId -> Process ()
331+link = sendCtrlMsg Nothing . Link . ProcessIdentifier
332+
333+-- | Monitor another process (asynchronous)
334+monitor :: ProcessId -> Process MonitorRef
335+monitor = monitor' . ProcessIdentifier
336+
337+-- | Remove a link
338+--
339+-- This is synchronous in the sense that once it returns you are guaranteed
340+-- that no exception will be raised if the remote process dies. However, it is
341+-- asynchronous in the sense that we do not wait for a response from the remote
342+-- node.
343+unlink :: ProcessId -> Process ()
344+unlink pid = do
345+ unlinkAsync pid
346+ receiveWait [ matchIf (\(DidUnlinkProcess pid') -> pid' == pid)
347+ (\_ -> return ())
348+ ]
349+
350+-- | Remove a node link
351+--
352+-- This has the same synchronous/asynchronous nature as 'unlink'.
353+unlinkNode :: NodeId -> Process ()
354+unlinkNode nid = do
355+ unlinkNodeAsync nid
356+ receiveWait [ matchIf (\(DidUnlinkNode nid') -> nid' == nid)
357+ (\_ -> return ())
358+ ]
359+
360+-- | Remove a channel (send port) link
361+--
362+-- This has the same synchronous/asynchronous nature as 'unlink'.
363+unlinkPort :: SendPort a -> Process ()
364+unlinkPort sport = do
365+ unlinkPortAsync sport
366+ receiveWait [ matchIf (\(DidUnlinkPort cid) -> cid == sendPortId sport)
367+ (\_ -> return ())
368+ ]
369+
370+-- | Remove a monitor
371+--
372+-- This has the same synchronous/asynchronous nature as 'unlink'.
373+unmonitor :: MonitorRef -> Process ()
374+unmonitor ref = do
375+ unmonitorAsync ref
376+ receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
377+ (\_ -> return ())
378+ ]
379+
380+--------------------------------------------------------------------------------
381+-- Exception handling --
382+--------------------------------------------------------------------------------
383+
384+-- | Lift 'Control.Exception.catch'
385+catch :: Exception e => Process a -> (e -> Process a) -> Process a
386+catch p h = do
387+ lproc <- ask
388+ liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
389+
390+-- | Lift 'Control.Exception.mask'
391+mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
392+mask p = do
393+ lproc <- ask
394+ liftIO $ Ex.mask $ \restore ->
395+ runLocalProcess lproc (p (liftRestore lproc restore))
396+ where
397+ liftRestore :: LocalProcess -> (forall a. IO a -> IO a) -> (forall a. Process a -> Process a)
398+ liftRestore lproc restoreIO = liftIO . restoreIO . runLocalProcess lproc
399+
400+-- | Lift 'Control.Exception.onException'
401+onException :: Process a -> Process b -> Process a
402+onException p what = p `catch` \e -> do _ <- what
403+ liftIO $ throwIO (e :: SomeException)
404+
405+-- | Lift 'Control.Exception.bracket'
406+bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
407+bracket before after thing =
408+ mask $ \restore -> do
409+ a <- before
410+ r <- restore (thing a) `onException` after a
411+ _ <- after a
412+ return r
413+
414+-- | Lift 'Control.Exception.bracket_'
415+bracket_ :: Process a -> Process b -> Process c -> Process c
416+bracket_ before after thing = bracket before (const after) (const thing)
417+
418+-- | Lift 'Control.Exception.finally'
419+finally :: Process a -> Process b -> Process a
420+finally a sequel = bracket_ (return ()) sequel a
421+
422+--------------------------------------------------------------------------------
423+-- Auxiliary API --
424+--------------------------------------------------------------------------------
425+
426+-- | Like 'expect' but with a timeout
427+expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)
428+expectTimeout timeout = receiveTimeout timeout [match return]
429+
430+-- | Asynchronous version of 'spawn'
431+--
432+-- ('spawn' is defined in terms of 'spawnAsync' and 'expect')
433+spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
434+spawnAsync nid proc = do
435+ spawnRef <- getSpawnRef
436+ sendCtrlMsg (Just nid) $ Spawn proc spawnRef
437+ return spawnRef
438+
439+-- | Monitor a node (asynchronous)
440+monitorNode :: NodeId -> Process MonitorRef
441+monitorNode =
442+ monitor' . NodeIdentifier
443+
444+-- | Monitor a typed channel (asynchronous)
445+monitorPort :: forall a. Serializable a => SendPort a -> Process MonitorRef
446+monitorPort (SendPort cid) =
447+ monitor' (SendPortIdentifier cid)
448+
449+-- | Remove a monitor (asynchronous)
450+unmonitorAsync :: MonitorRef -> Process ()
451+unmonitorAsync =
452+ sendCtrlMsg Nothing . Unmonitor
453+
454+-- | Link to a node (asynchronous)
455+linkNode :: NodeId -> Process ()
456+linkNode = link' . NodeIdentifier
457+
458+-- | Link to a channel (asynchronous)
459+linkPort :: SendPort a -> Process ()
460+linkPort (SendPort cid) =
461+ link' (SendPortIdentifier cid)
462+
463+-- | Remove a process link (asynchronous)
464+unlinkAsync :: ProcessId -> Process ()
465+unlinkAsync =
466+ sendCtrlMsg Nothing . Unlink . ProcessIdentifier
467+
468+-- | Remove a node link (asynchronous)
469+unlinkNodeAsync :: NodeId -> Process ()
470+unlinkNodeAsync =
471+ sendCtrlMsg Nothing . Unlink . NodeIdentifier
472+
473+-- | Remove a channel (send port) link (asynchronous)
474+unlinkPortAsync :: SendPort a -> Process ()
475+unlinkPortAsync (SendPort cid) =
476+ sendCtrlMsg Nothing . Unlink $ SendPortIdentifier cid
477+
478+--------------------------------------------------------------------------------
479+-- Logging --
480+--------------------------------------------------------------------------------
481+
482+-- | Log a string
483+--
484+-- @say message@ sends a message (time, pid of the current process, message)
485+-- to the process registered as 'logger'. By default, this process simply
486+-- sends the string to 'stderr'. Individual Cloud Haskell backends might
487+-- replace this with a different logger process, however.
488+say :: String -> Process ()
489+say string = do
490+ now <- liftIO getCurrentTime
491+ us <- getSelfPid
492+ nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
493+
494+--------------------------------------------------------------------------------
495+-- Registry --
496+--------------------------------------------------------------------------------
497+
498+-- | Register a process with the local registry (asynchronous).
499+--
500+-- The process to be registered does not have to be local itself.
501+register :: String -> ProcessId -> Process ()
502+register label pid =
503+ sendCtrlMsg Nothing (Register label (Just pid))
504+
505+-- | Register a process with a remote registry (asynchronous).
506+--
507+-- The process to be registered does not have to live on the same remote node.
508+registerRemote :: NodeId -> String -> ProcessId -> Process ()
509+registerRemote nid label pid =
510+ sendCtrlMsg (Just nid) (Register label (Just pid))
511+
512+-- | Remove a process from the local registry (asynchronous).
513+unregister :: String -> Process ()
514+unregister label =
515+ sendCtrlMsg Nothing (Register label Nothing)
516+
517+-- | Remove a process from a remote registry (asynchronous).
518+unregisterRemote :: NodeId -> String -> Process ()
519+unregisterRemote nid label =
520+ sendCtrlMsg (Just nid) (Register label Nothing)
521+
522+-- | Query the local process registry
523+whereis :: String -> Process (Maybe ProcessId)
524+whereis label = do
525+ sendCtrlMsg Nothing (WhereIs label)
526+ receiveWait [ matchIf (\(WhereIsReply label' _) -> label == label')
527+ (\(WhereIsReply _ mPid) -> return mPid)
528+ ]
529+
530+-- | Query a remote process registry (asynchronous)
531+--
532+-- Reply will come in the form of a 'WhereIsReply' message.
533+--
534+-- There is currently no synchronous version of 'whereisRemoteAsync': if
535+-- you implement one yourself, be sure to take into account that the remote
536+-- node might die or get disconnect before it can respond (i.e. you should
537+-- use 'monitorNode' and take appropriate action when you receive a
538+-- 'NodeMonitorNotification').
539+whereisRemoteAsync :: NodeId -> String -> Process ()
540+whereisRemoteAsync nid label =
541+ sendCtrlMsg (Just nid) (WhereIs label)
542+
543+-- | Named send to a process in the local registry (asynchronous)
544+nsend :: Serializable a => String -> a -> Process ()
545+nsend label msg =
546+ sendCtrlMsg Nothing (NamedSend label (createMessage msg))
547+
548+-- | Named send to a process in a remote registry (asynchronous)
549+nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
550+nsendRemote nid label msg =
551+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
552+
553+--------------------------------------------------------------------------------
554+-- Closures --
555+--------------------------------------------------------------------------------
556+
557+-- | Resolve a static value
558+unStatic :: Typeable a => Static a -> Process a
559+unStatic static = do
560+ rtable <- remoteTable . processNode <$> ask
561+ case Static.unstatic rtable static of
562+ Left err -> fail $ "Could not resolve static value: " ++ err
563+ Right x -> return x
564+
565+-- | Resolve a closure
566+unClosure :: Typeable a => Closure a -> Process a
567+unClosure closure = do
568+ rtable <- remoteTable . processNode <$> ask
569+ case Static.unclosure rtable closure of
570+ Left err -> fail $ "Could not resolve closure: " ++ err
571+ Right x -> return x
572+
573+--------------------------------------------------------------------------------
574+-- Reconnecting --
575+--------------------------------------------------------------------------------
576+
577+-- | Cloud Haskell provides the illusion of connection-less, reliable, ordered
578+-- message passing. However, when network connections get disrupted this
579+-- illusion cannot always be maintained. Once a network connection breaks (even
580+-- temporarily) no further communication on that connection will be possible.
581+-- For example, if process A sends a message to process B, and A is then
582+-- notified (by monitor notification) that it got disconnected from B, A will
583+-- not be able to send any further messages to B, /unless/ A explicitly
584+-- indicates that it is acceptable to attempt to reconnect to B using the
585+-- Cloud Haskell 'reconnect' primitive.
586+--
587+-- Importantly, when A calls 'reconnect' it acknowledges that some messages to
588+-- B might have been lost. For instance, if A sends messages m1 and m2 to B,
589+-- then receives a monitor notification that its connection to B has been lost,
590+-- calls 'reconnect' and then sends m3, it is possible that B will receive m1
591+-- and m3 but not m2.
592+--
593+-- Note that 'reconnect' does not mean /reconnect now/ but rather /it is okay
594+-- to attempt to reconnect on the next send/. In particular, if no further
595+-- communication attempts are made to B then A can use reconnect to clean up
596+-- its connection to B.
597+reconnect :: ProcessId -> Process ()
598+reconnect them = do
599+ us <- getSelfPid
600+ node <- processNode <$> ask
601+ liftIO $ disconnect node (ProcessIdentifier us) (ProcessIdentifier them)
602+
603+-- | Reconnect to a sendport. See 'reconnect' for more information.
604+reconnectPort :: SendPort a -> Process ()
605+reconnectPort them = do
606+ us <- getSelfPid
607+ node <- processNode <$> ask
608+ liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))
609+
610+--------------------------------------------------------------------------------
611+-- Auxiliary functions --
612+--------------------------------------------------------------------------------
613+
614+getMonitorRefFor :: Identifier -> Process MonitorRef
615+getMonitorRefFor ident = do
616+ proc <- ask
617+ liftIO $ modifyMVar (processState proc) $ \st -> do
618+ let counter = st ^. monitorCounter
619+ return ( monitorCounter ^: (+ 1) $ st
620+ , MonitorRef ident counter
621+ )
622+
623+getSpawnRef :: Process SpawnRef
624+getSpawnRef = do
625+ proc <- ask
626+ liftIO $ modifyMVar (processState proc) $ \st -> do
627+ let counter = st ^. spawnCounter
628+ return ( spawnCounter ^: (+ 1) $ st
629+ , SpawnRef counter
630+ )
631+
632+-- | Monitor a process/node/channel
633+monitor' :: Identifier -> Process MonitorRef
634+monitor' ident = do
635+ monitorRef <- getMonitorRefFor ident
636+ sendCtrlMsg Nothing $ Monitor monitorRef
637+ return monitorRef
638+
639+-- | Link to a process/node/channel
640+link' :: Identifier -> Process ()
641+link' = sendCtrlMsg Nothing . Link
642+
643+-- Send a control message
644+sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
645+ -> ProcessSignal -- ^ Message to send
646+ -> Process ()
647+sendCtrlMsg mNid signal = do
648+ proc <- ask
649+ let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
650+ , ctrlMsgSignal = signal
651+ }
652+ case mNid of
653+ Nothing -> do
654+ ctrlChan <- localCtrlChan . processNode <$> ask
655+ liftIO $ writeChan ctrlChan msg
656+ Just nid ->
657+ liftIO $ sendBinary (processNode proc)
658+ (ProcessIdentifier (processId proc))
659+ (NodeIdentifier nid)
660+ WithImplicitReconnect
661+ msg
distributed-process/src/Control/Distributed/Process/Internal/StrictMVar.hsnew file+51
@@ -0,0 +1,51 @@
1+-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
2+module Control.Distributed.Process.Internal.StrictMVar
3+ ( StrictMVar
4+ , newEmptyMVar
5+ , newMVar
6+ , takeMVar
7+ , putMVar
8+ , withMVar
9+ , modifyMVar_
10+ , modifyMVar
11+ ) where
12+
13+import Control.Applicative ((<$>))
14+import Control.Monad ((>=>))
15+import Control.Exception (evaluate)
16+import qualified Control.Concurrent.MVar as MVar
17+ ( MVar
18+ , newEmptyMVar
19+ , newMVar
20+ , takeMVar
21+ , putMVar
22+ , withMVar
23+ , modifyMVar_
24+ , modifyMVar
25+ )
26+
27+newtype StrictMVar a = StrictMVar (MVar.MVar a)
28+
29+newEmptyMVar :: IO (StrictMVar a)
30+newEmptyMVar = StrictMVar <$> MVar.newEmptyMVar
31+
32+newMVar :: a -> IO (StrictMVar a)
33+newMVar x = evaluate x >> StrictMVar <$> MVar.newMVar x
34+
35+takeMVar :: StrictMVar a -> IO a
36+takeMVar (StrictMVar v) = MVar.takeMVar v
37+
38+putMVar :: StrictMVar a -> a -> IO ()
39+putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
40+
41+withMVar :: StrictMVar a -> (a -> IO b) -> IO b
42+withMVar (StrictMVar v) = MVar.withMVar v
43+
44+modifyMVar_ :: StrictMVar a -> (a -> IO a) -> IO ()
45+modifyMVar_ (StrictMVar v) f = MVar.modifyMVar_ v (f >=> evaluate)
46+
47+modifyMVar :: StrictMVar a -> (a -> IO (a, b)) -> IO b
48+modifyMVar (StrictMVar v) f = MVar.modifyMVar v (f >=> evaluateFst)
49+ where
50+ evaluateFst :: (a, b) -> IO (a, b)
51+ evaluateFst (x, y) = evaluate x >> return (x, y)
distributed-process/src/Control/Distributed/Process/Internal/Types.hsnew file+546
@@ -0,0 +1,546 @@
1+-- | Types used throughout the Cloud Haskell framework
2+--
3+-- We collect all types used internally in a single module because
4+-- many of these data types are mutually recursive and cannot be split across
5+-- modules.
6+module Control.Distributed.Process.Internal.Types
7+ ( -- * Node and process identifiers
8+ NodeId(..)
9+ , LocalProcessId(..)
10+ , ProcessId(..)
11+ , Identifier(..)
12+ , nodeOf
13+ , firstNonReservedProcessId
14+ , nullProcessId
15+ -- * Local nodes and processes
16+ , LocalNode(..)
17+ , LocalNodeState(..)
18+ , LocalProcess(..)
19+ , LocalProcessState(..)
20+ , Process(..)
21+ , runLocalProcess
22+ , ImplicitReconnect(..)
23+ -- * Typed channels
24+ , LocalSendPortId
25+ , SendPortId(..)
26+ , TypedChannel(..)
27+ , SendPort(..)
28+ , ReceivePort(..)
29+ -- * Messages
30+ , Message(..)
31+ , createMessage
32+ , messageToPayload
33+ , payloadToMessage
34+ -- * Node controller user-visible data types
35+ , MonitorRef(..)
36+ , ProcessMonitorNotification(..)
37+ , NodeMonitorNotification(..)
38+ , PortMonitorNotification(..)
39+ , ProcessLinkException(..)
40+ , NodeLinkException(..)
41+ , PortLinkException(..)
42+ , DiedReason(..)
43+ , DidUnmonitor(..)
44+ , DidUnlinkProcess(..)
45+ , DidUnlinkNode(..)
46+ , DidUnlinkPort(..)
47+ , SpawnRef(..)
48+ , DidSpawn(..)
49+ , WhereIsReply(..)
50+ -- * Node controller internal data types
51+ , NCMsg(..)
52+ , ProcessSignal(..)
53+ -- * Accessors
54+ , localProcesses
55+ , localPidCounter
56+ , localPidUnique
57+ , localConnections
58+ , localProcessWithId
59+ , localConnectionBetween
60+ , monitorCounter
61+ , spawnCounter
62+ , channelCounter
63+ , typedChannels
64+ , typedChannelWithId
65+ ) where
66+
67+import System.Mem.Weak (Weak)
68+import Data.Map (Map)
69+import Data.Int (Int32)
70+import Data.Typeable (Typeable)
71+import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
72+import qualified Data.ByteString as BSS (ByteString, concat)
73+import qualified Data.ByteString.Lazy as BSL
74+ ( ByteString
75+ , toChunks
76+ , splitAt
77+ , fromChunks
78+ )
79+import Data.Accessor (Accessor, accessor)
80+import qualified Data.Accessor.Container as DAC (mapMaybe)
81+import Control.Category ((>>>))
82+import Control.Exception (Exception)
83+import Control.Concurrent (ThreadId)
84+import Control.Concurrent.Chan (Chan)
85+import Control.Concurrent.STM (TVar)
86+import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
87+import Control.Applicative (Applicative, (<$>), (<*>))
88+import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
89+import Control.Monad.IO.Class (MonadIO)
90+import Control.Distributed.Process.Serializable
91+ ( Fingerprint
92+ , Serializable
93+ , fingerprint
94+ , encodeFingerprint
95+ , sizeOfFingerprint
96+ , decodeFingerprint
97+ , showFingerprint
98+ )
99+import Control.Distributed.Process.Internal.CQueue (CQueue)
100+import Control.Distributed.Process.Internal.StrictMVar (StrictMVar)
101+import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
102+import Control.Distributed.Static (RemoteTable, Closure)
103+
104+--------------------------------------------------------------------------------
105+-- Node and process identifiers --
106+--------------------------------------------------------------------------------
107+
108+-- | Node identifier
109+newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
110+ deriving (Eq, Ord, Binary, Typeable)
111+
112+instance Show NodeId where
113+ show (NodeId addr) = "nid://" ++ show addr
114+
115+-- | A local process ID consists of a seed which distinguishes processes from
116+-- different instances of the same local node and a counter
117+data LocalProcessId = LocalProcessId
118+ { lpidUnique :: {-# UNPACK #-} !Int32
119+ , lpidCounter :: {-# UNPACK #-} !Int32
120+ }
121+ deriving (Eq, Ord, Typeable, Show)
122+
123+-- | Process identifier
124+data ProcessId = ProcessId
125+ { -- | The ID of the node the process is running on
126+ processNodeId :: !NodeId
127+ -- | Node-local identifier for the process
128+ , processLocalId :: {-# UNPACK #-} !LocalProcessId
129+ }
130+ deriving (Eq, Ord, Typeable)
131+
132+instance Show ProcessId where
133+ show (ProcessId (NodeId addr) (LocalProcessId _ lid))
134+ = "pid://" ++ show addr ++ ":" ++ show lid
135+
136+-- | Union of all kinds of identifiers
137+data Identifier =
138+ NodeIdentifier NodeId
139+ | ProcessIdentifier ProcessId
140+ | SendPortIdentifier SendPortId
141+ deriving (Eq, Ord)
142+
143+instance Show Identifier where
144+ show (NodeIdentifier nid) = show nid
145+ show (ProcessIdentifier pid) = show pid
146+ show (SendPortIdentifier cid) = show cid
147+
148+nodeOf :: Identifier -> NodeId
149+nodeOf (NodeIdentifier nid) = nid
150+nodeOf (ProcessIdentifier pid) = processNodeId pid
151+nodeOf (SendPortIdentifier cid) = processNodeId (sendPortProcessId cid)
152+
153+--------------------------------------------------------------------------------
154+-- Special PIDs --
155+--------------------------------------------------------------------------------
156+
157+firstNonReservedProcessId :: Int32
158+firstNonReservedProcessId = 1
159+
160+nullProcessId :: NodeId -> ProcessId
161+nullProcessId nid =
162+ ProcessId { processNodeId = nid
163+ , processLocalId = LocalProcessId { lpidUnique = 0
164+ , lpidCounter = 0
165+ }
166+ }
167+
168+--------------------------------------------------------------------------------
169+-- Local nodes and processes --
170+--------------------------------------------------------------------------------
171+
172+-- | Local nodes
173+data LocalNode = LocalNode
174+ { -- | 'NodeId' of the node
175+ localNodeId :: NodeId
176+ -- | The network endpoint associated with this node
177+ , localEndPoint :: NT.EndPoint
178+ -- | Local node state
179+ , localState :: StrictMVar LocalNodeState
180+ -- | Channel for the node controller
181+ , localCtrlChan :: Chan NCMsg
182+ -- | Runtime lookup table for supporting closures
183+ -- TODO: this should be part of the CH state, not the local endpoint state
184+ , remoteTable :: RemoteTable
185+ }
186+
187+data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
188+ deriving (Eq, Show)
189+
190+-- | Local node state
191+data LocalNodeState = LocalNodeState
192+ { -- | Processes running on this node
193+ _localProcesses :: !(Map LocalProcessId LocalProcess)
194+ -- | Counter to assign PIDs
195+ , _localPidCounter :: !Int32
196+ -- | The 'unique' value used to create PIDs (so that processes on
197+ -- restarted nodes have new PIDs)
198+ , _localPidUnique :: !Int32
199+ -- | Outgoing connections
200+ , _localConnections :: !(Map (Identifier, Identifier)
201+ (NT.Connection, ImplicitReconnect))
202+ }
203+
204+-- | Processes running on our local node
205+data LocalProcess = LocalProcess
206+ { processQueue :: CQueue Message
207+ , processId :: ProcessId
208+ , processState :: StrictMVar LocalProcessState
209+ , processThread :: ThreadId
210+ , processNode :: LocalNode
211+ }
212+
213+-- | Deconstructor for 'Process' (not exported to the public API)
214+runLocalProcess :: LocalProcess -> Process a -> IO a
215+runLocalProcess lproc proc = runReaderT (unProcess proc) lproc
216+
217+-- | Local process state
218+data LocalProcessState = LocalProcessState
219+ { _monitorCounter :: !Int32
220+ , _spawnCounter :: !Int32
221+ , _channelCounter :: !Int32
222+ , _typedChannels :: !(Map LocalSendPortId TypedChannel)
223+ }
224+
225+-- | The Cloud Haskell 'Process' type
226+newtype Process a = Process {
227+ unProcess :: ReaderT LocalProcess IO a
228+ }
229+ deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative)
230+
231+--------------------------------------------------------------------------------
232+-- Typed channels --
233+--------------------------------------------------------------------------------
234+
235+type LocalSendPortId = Int32
236+
237+-- | A send port is identified by a SendPortId.
238+--
239+-- You cannot send directly to a SendPortId; instead, use 'newChan'
240+-- to create a SendPort.
241+data SendPortId = SendPortId {
242+ -- | The ID of the process that will receive messages sent on this port
243+ sendPortProcessId :: {-# UNPACK #-} !ProcessId
244+ -- | Process-local ID of the channel
245+ , sendPortLocalId :: {-# UNPACK #-} !LocalSendPortId
246+ }
247+ deriving (Eq, Ord)
248+
249+instance Show SendPortId where
250+ show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
251+ = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
252+
253+data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
254+
255+-- | The send send of a typed channel (serializable)
256+newtype SendPort a = SendPort {
257+ -- | The (unique) ID of this send port
258+ sendPortId :: SendPortId
259+ }
260+ deriving (Typeable, Binary, Show, Eq, Ord)
261+
262+-- | The receive end of a typed channel (not serializable)
263+data ReceivePort a =
264+ -- | A single receive port
265+ ReceivePortSingle (TQueue a)
266+ -- | A left-biased combination of receive ports
267+ | ReceivePortBiased [ReceivePort a]
268+ -- | A round-robin combination of receive ports
269+ | ReceivePortRR (TVar [ReceivePort a])
270+ deriving Typeable
271+
272+--------------------------------------------------------------------------------
273+-- Messages --
274+--------------------------------------------------------------------------------
275+
276+-- | Messages consist of their typeRep fingerprint and their encoding
277+data Message = Message
278+ { messageFingerprint :: Fingerprint
279+ , messageEncoding :: BSL.ByteString
280+ }
281+
282+instance Show Message where
283+ show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp []
284+
285+-- | Turn any serialiable term into a message
286+createMessage :: Serializable a => a -> Message
287+createMessage a = Message (fingerprint a) (encode a)
288+
289+-- | Serialize a message
290+messageToPayload :: Message -> [BSS.ByteString]
291+messageToPayload (Message fp enc) = encodeFingerprint fp : BSL.toChunks enc
292+
293+-- | Deserialize a message
294+payloadToMessage :: [BSS.ByteString] -> Message
295+payloadToMessage payload = Message fp msg
296+ where
297+ (encFp, msg) = BSL.splitAt (fromIntegral sizeOfFingerprint)
298+ $ BSL.fromChunks payload
299+ fp = decodeFingerprint . BSS.concat . BSL.toChunks $ encFp
300+
301+--------------------------------------------------------------------------------
302+-- Node controller user-visible data types --
303+--------------------------------------------------------------------------------
304+
305+-- | MonitorRef is opaque for regular Cloud Haskell processes
306+data MonitorRef = MonitorRef
307+ { -- | ID of the entity to be monitored
308+ monitorRefIdent :: !Identifier
309+ -- | Unique to distinguish multiple monitor requests by the same process
310+ , monitorRefCounter :: !Int32
311+ }
312+ deriving (Eq, Ord, Show)
313+
314+-- | Message sent by process monitors
315+data ProcessMonitorNotification =
316+ ProcessMonitorNotification MonitorRef ProcessId DiedReason
317+ deriving (Typeable, Show)
318+
319+-- | Message sent by node monitors
320+data NodeMonitorNotification =
321+ NodeMonitorNotification MonitorRef NodeId DiedReason
322+ deriving (Typeable, Show)
323+
324+-- | Message sent by channel (port) monitors
325+data PortMonitorNotification =
326+ PortMonitorNotification MonitorRef SendPortId DiedReason
327+ deriving (Typeable, Show)
328+
329+-- | Exceptions thrown when a linked process dies
330+data ProcessLinkException =
331+ ProcessLinkException ProcessId DiedReason
332+ deriving (Typeable, Show)
333+
334+-- | Exception thrown when a linked node dies
335+data NodeLinkException =
336+ NodeLinkException NodeId DiedReason
337+ deriving (Typeable, Show)
338+
339+-- | Exception thrown when a linked channel (port) dies
340+data PortLinkException =
341+ PortLinkException SendPortId DiedReason
342+ deriving (Typeable, Show)
343+
344+instance Exception ProcessLinkException
345+instance Exception NodeLinkException
346+instance Exception PortLinkException
347+
348+-- | Why did a process die?
349+data DiedReason =
350+ -- | Normal termination
351+ DiedNormal
352+ -- | The process exited with an exception
353+ -- (provided as 'String' because 'Exception' does not implement 'Binary')
354+ | DiedException String
355+ -- | We got disconnected from the process node
356+ | DiedDisconnect
357+ -- | The process node died
358+ | DiedNodeDown
359+ -- | Invalid (process/node/channel) identifier
360+ | DiedUnknownId
361+ deriving (Show, Eq)
362+
363+-- | (Asynchronous) reply from unmonitor
364+newtype DidUnmonitor = DidUnmonitor MonitorRef
365+ deriving (Typeable, Binary)
366+
367+-- | (Asynchronous) reply from unlink
368+newtype DidUnlinkProcess = DidUnlinkProcess ProcessId
369+ deriving (Typeable, Binary)
370+
371+-- | (Asynchronous) reply from unlinkNode
372+newtype DidUnlinkNode = DidUnlinkNode NodeId
373+ deriving (Typeable, Binary)
374+
375+-- | (Asynchronous) reply from unlinkPort
376+newtype DidUnlinkPort = DidUnlinkPort SendPortId
377+ deriving (Typeable, Binary)
378+
379+-- | 'SpawnRef' are used to return pids of spawned processes
380+newtype SpawnRef = SpawnRef Int32
381+ deriving (Show, Binary, Typeable, Eq)
382+
383+-- | (Asynchronius) reply from 'spawn'
384+data DidSpawn = DidSpawn SpawnRef ProcessId
385+ deriving (Show, Typeable)
386+
387+-- | (Asynchronous) reply from 'whereis'
388+data WhereIsReply = WhereIsReply String (Maybe ProcessId)
389+ deriving (Show, Typeable)
390+
391+--------------------------------------------------------------------------------
392+-- Node controller internal data types --
393+--------------------------------------------------------------------------------
394+
395+-- | Messages to the node controller
396+data NCMsg = NCMsg
397+ { ctrlMsgSender :: Identifier
398+ , ctrlMsgSignal :: ProcessSignal
399+ }
400+ deriving Show
401+
402+-- | Signals to the node controller (see 'NCMsg')
403+data ProcessSignal =
404+ Link Identifier
405+ | Unlink Identifier
406+ | Monitor MonitorRef
407+ | Unmonitor MonitorRef
408+ | Died Identifier DiedReason
409+ | Spawn (Closure (Process ())) SpawnRef
410+ | WhereIs String
411+ | Register String (Maybe ProcessId) -- Nothing to unregister
412+ | NamedSend String Message
413+ deriving Show
414+
415+--------------------------------------------------------------------------------
416+-- Binary instances --
417+--------------------------------------------------------------------------------
418+
419+instance Binary LocalProcessId where
420+ put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)
421+ get = LocalProcessId <$> get <*> get
422+
423+instance Binary ProcessId where
424+ put pid = put (processNodeId pid) >> put (processLocalId pid)
425+ get = ProcessId <$> get <*> get
426+
427+instance Binary ProcessMonitorNotification where
428+ put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason
429+ get = ProcessMonitorNotification <$> get <*> get <*> get
430+
431+instance Binary NodeMonitorNotification where
432+ put (NodeMonitorNotification ref pid reason) = put ref >> put pid >> put reason
433+ get = NodeMonitorNotification <$> get <*> get <*> get
434+
435+instance Binary PortMonitorNotification where
436+ put (PortMonitorNotification ref pid reason) = put ref >> put pid >> put reason
437+ get = PortMonitorNotification <$> get <*> get <*> get
438+
439+instance Binary NCMsg where
440+ put msg = put (ctrlMsgSender msg) >> put (ctrlMsgSignal msg)
441+ get = NCMsg <$> get <*> get
442+
443+instance Binary MonitorRef where
444+ put ref = put (monitorRefIdent ref) >> put (monitorRefCounter ref)
445+ get = MonitorRef <$> get <*> get
446+
447+instance Binary ProcessSignal where
448+ put (Link pid) = putWord8 0 >> put pid
449+ put (Unlink pid) = putWord8 1 >> put pid
450+ put (Monitor ref) = putWord8 2 >> put ref
451+ put (Unmonitor ref) = putWord8 3 >> put ref
452+ put (Died who reason) = putWord8 4 >> put who >> put reason
453+ put (Spawn proc ref) = putWord8 5 >> put proc >> put ref
454+ put (WhereIs label) = putWord8 6 >> put label
455+ put (Register label pid) = putWord8 7 >> put label >> put pid
456+ put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)
457+ get = do
458+ header <- getWord8
459+ case header of
460+ 0 -> Link <$> get
461+ 1 -> Unlink <$> get
462+ 2 -> Monitor <$> get
463+ 3 -> Unmonitor <$> get
464+ 4 -> Died <$> get <*> get
465+ 5 -> Spawn <$> get <*> get
466+ 6 -> WhereIs <$> get
467+ 7 -> Register <$> get <*> get
468+ 8 -> NamedSend <$> get <*> (payloadToMessage <$> get)
469+ _ -> fail "ProcessSignal.get: invalid"
470+
471+instance Binary DiedReason where
472+ put DiedNormal = putWord8 0
473+ put (DiedException e) = putWord8 1 >> put e
474+ put DiedDisconnect = putWord8 2
475+ put DiedNodeDown = putWord8 3
476+ put DiedUnknownId = putWord8 4
477+ get = do
478+ header <- getWord8
479+ case header of
480+ 0 -> return DiedNormal
481+ 1 -> DiedException <$> get
482+ 2 -> return DiedDisconnect
483+ 3 -> return DiedNodeDown
484+ 4 -> return DiedUnknownId
485+ _ -> fail "DiedReason.get: invalid"
486+
487+instance Binary DidSpawn where
488+ put (DidSpawn ref pid) = put ref >> put pid
489+ get = DidSpawn <$> get <*> get
490+
491+instance Binary SendPortId where
492+ put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)
493+ get = SendPortId <$> get <*> get
494+
495+instance Binary Identifier where
496+ put (ProcessIdentifier pid) = putWord8 0 >> put pid
497+ put (NodeIdentifier nid) = putWord8 1 >> put nid
498+ put (SendPortIdentifier cid) = putWord8 2 >> put cid
499+ get = do
500+ header <- getWord8
501+ case header of
502+ 0 -> ProcessIdentifier <$> get
503+ 1 -> NodeIdentifier <$> get
504+ 2 -> SendPortIdentifier <$> get
505+ _ -> fail "Identifier.get: invalid"
506+
507+instance Binary WhereIsReply where
508+ put (WhereIsReply label mPid) = put label >> put mPid
509+ get = WhereIsReply <$> get <*> get
510+
511+--------------------------------------------------------------------------------
512+-- Accessors --
513+--------------------------------------------------------------------------------
514+
515+localProcesses :: Accessor LocalNodeState (Map LocalProcessId LocalProcess)
516+localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
517+
518+localPidCounter :: Accessor LocalNodeState Int32
519+localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
520+
521+localPidUnique :: Accessor LocalNodeState Int32
522+localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
523+
524+localConnections :: Accessor LocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
525+localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
526+
527+localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProcess)
528+localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
529+
530+localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
531+localConnectionBetween from to = localConnections >>> DAC.mapMaybe (from, to)
532+
533+monitorCounter :: Accessor LocalProcessState Int32
534+monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
535+
536+spawnCounter :: Accessor LocalProcessState Int32
537+spawnCounter = accessor _spawnCounter (\cnt st -> st { _spawnCounter = cnt })
538+
539+channelCounter :: Accessor LocalProcessState LocalSendPortId
540+channelCounter = accessor _channelCounter (\cnt st -> st { _channelCounter = cnt })
541+
542+typedChannels :: Accessor LocalProcessState (Map LocalSendPortId TypedChannel)
543+typedChannels = accessor _typedChannels (\cs st -> st { _typedChannels = cs })
544+
545+typedChannelWithId :: LocalSendPortId -> Accessor LocalProcessState (Maybe TypedChannel)
546+typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid
src/Control/Distributed/Process/Internal/WeakTQueue.hs → distributed-process/src/Control/Distributed/Process/Internal/WeakTQueue.hsrenamed+4−10
@@ -1,12 +1,10 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE MagicHash, UnboxedTuples #-}
4-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
1-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
5--
2--
6-- Not all functionality from the original module is available: unGetTQueue,
3-- Not all functionality from the original module is available: unGetTQueue,
7-- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd
4-- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd
8-- need to be able to touch# the write end of the queue inside unGetTQueue, but
5-- need to be able to touch# the write end of the queue inside unGetTQueue, but
9-- that means we need a version of touch# that works within the STM monad.
6-- that means we need a version of touch# that works within the STM monad.
7+{-# LANGUAGE MagicHash, UnboxedTuples #-}
10module Control.Distributed.Process.Internal.WeakTQueue (
8module Control.Distributed.Process.Internal.WeakTQueue (
11 -- * Original functionality
9 -- * Original functionality
12 TQueue,
10 TQueue,
@@ -23,8 +21,8 @@ module Control.Distributed.Process.Internal.WeakTQueue (
23import Prelude hiding (read)
21import Prelude hiding (read)
24import GHC.Conc
22import GHC.Conc
25import Data.Typeable (Typeable)
23import Data.Typeable (Typeable)
26−import GHC.IO (IO(IO), unIO)
24+import GHC.IO (IO(IO))
27−import GHC.Exts (mkWeak#)
25+import GHC.Prim (mkWeak#)
28import GHC.Weak (Weak(Weak))
26import GHC.Weak (Weak(Weak))
29
27
30--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
@@ -100,8 +98,4 @@ isEmptyTQueue (TQueue read write) = do
100
98
101mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
99mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
102mkWeakTQueue q@(TQueue _read (TVar write#)) f = IO $ \s ->
100mkWeakTQueue q@(TQueue _read (TVar write#)) f = IO $ \s ->
103−#if MIN_VERSION_base(4,9,0)
104− case mkWeak# write# q (unIO f) s of (# s', w #) -> (# s', Weak w #)
101+ case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
105−#else
106− case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
107−#endif
distributed-process/src/Control/Distributed/Process/Node.hsnew file+771
@@ -0,0 +1,771 @@
1+-- | Local nodes
2+--
3+-- TODO: Calls to 'sendBinary' and co by the node controller may stall the
4+-- node controller.
5+module Control.Distributed.Process.Node
6+ ( LocalNode
7+ , newLocalNode
8+ , closeLocalNode
9+ , forkProcess
10+ , runProcess
11+ , initRemoteTable
12+ , localNodeId
13+ ) where
14+
15+#if ! MIN_VERSION_base(4,6,0)
16+import Prelude hiding (catch)
17+#endif
18+
19+import System.IO (fixIO, hPutStrLn, stderr)
20+import System.Mem.Weak (deRefWeak)
21+import qualified Data.ByteString.Lazy as BSL (fromChunks)
22+import Data.Binary (decode)
23+import Data.Map (Map)
24+import qualified Data.Map as Map
25+ ( empty
26+ , toList
27+ , partitionWithKey
28+ , elems
29+ , filterWithKey
30+ , findWithDefault
31+ , insert
32+ )
33+import Data.Set (Set)
34+import qualified Data.Set as Set (empty, insert, delete, member)
35+import Data.Foldable (forM_)
36+import Data.Maybe (isJust)
37+import Data.Typeable (Typeable)
38+import Control.Category ((>>>))
39+import Control.Applicative ((<$>))
40+import Control.Monad (void, when)
41+import Control.Monad.IO.Class (MonadIO, liftIO)
42+import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
43+import qualified Control.Monad.State.Strict as StateT (get, put)
44+import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
45+import Control.Exception (throwIO, SomeException, Exception, throwTo)
46+import qualified Control.Exception as Exception (catch)
47+import Control.Concurrent (forkIO)
48+import Control.Distributed.Process.Internal.StrictMVar
49+ ( newMVar
50+ , withMVar
51+ , modifyMVar
52+ , modifyMVar_
53+ , newEmptyMVar
54+ , putMVar
55+ , takeMVar
56+ )
57+import Control.Concurrent.Chan (newChan, writeChan, readChan)
58+import Control.Concurrent.STM (atomically)
59+import Control.Distributed.Process.Internal.CQueue (enqueue, newCQueue)
60+import qualified Network.Transport as NT
61+ ( Transport
62+ , EndPoint
63+ , newEndPoint
64+ , receive
65+ , Event(..)
66+ , EventErrorCode(..)
67+ , TransportError(..)
68+ , address
69+ , closeEndPoint
70+ , ConnectionId
71+ , Connection
72+ , close
73+ , EndPointAddress
74+ , Reliability(ReliableOrdered)
75+ )
76+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
77+import qualified Data.Accessor.Container as DAC (mapDefault, mapMaybe)
78+import System.Random (randomIO)
79+import Control.Distributed.Static (RemoteTable, Closure)
80+import qualified Control.Distributed.Static as Static
81+ ( unclosure
82+ , initRemoteTable
83+ )
84+import Control.Distributed.Process.Internal.Types
85+ ( NodeId(..)
86+ , LocalProcessId(..)
87+ , ProcessId(..)
88+ , LocalNode(..)
89+ , LocalNodeState(..)
90+ , LocalProcess(..)
91+ , LocalProcessState(..)
92+ , Process(..)
93+ , DiedReason(..)
94+ , NCMsg(..)
95+ , ProcessSignal(..)
96+ , localPidCounter
97+ , localPidUnique
98+ , localProcessWithId
99+ , localConnections
100+ , MonitorRef(..)
101+ , ProcessMonitorNotification(..)
102+ , NodeMonitorNotification(..)
103+ , PortMonitorNotification(..)
104+ , ProcessLinkException(..)
105+ , NodeLinkException(..)
106+ , PortLinkException(..)
107+ , DidUnmonitor(..)
108+ , DidUnlinkProcess(..)
109+ , DidUnlinkNode(..)
110+ , DidUnlinkPort(..)
111+ , SpawnRef
112+ , DidSpawn(..)
113+ , Message
114+ , TypedChannel(..)
115+ , Identifier(..)
116+ , nodeOf
117+ , SendPortId(..)
118+ , typedChannelWithId
119+ , WhereIsReply(..)
120+ , messageToPayload
121+ , payloadToMessage
122+ , createMessage
123+ , runLocalProcess
124+ , firstNonReservedProcessId
125+ , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
126+ )
127+import Control.Distributed.Process.Serializable (Serializable)
128+import Control.Distributed.Process.Internal.Messaging
129+ ( sendBinary
130+ , sendMessage
131+ , sendPayload
132+ , closeImplicitReconnections
133+ , impliesDeathOf
134+ )
135+import Control.Distributed.Process.Internal.Primitives (expect, register, finally)
136+import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
137+import Control.Distributed.Process.Internal.WeakTQueue (writeTQueue)
138+
139+--------------------------------------------------------------------------------
140+-- Initialization --
141+--------------------------------------------------------------------------------
142+
143+initRemoteTable :: RemoteTable
144+initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
145+
146+-- | Initialize a new local node.
147+newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
148+newLocalNode transport rtable = do
149+ mEndPoint <- NT.newEndPoint transport
150+ case mEndPoint of
151+ Left ex -> throwIO ex
152+ Right endPoint -> do
153+ localNode <- createBareLocalNode endPoint rtable
154+ startServiceProcesses localNode
155+ return localNode
156+
157+-- | Create a new local node (without any service processes running)
158+createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
159+createBareLocalNode endPoint rtable = do
160+ unq <- randomIO
161+ state <- newMVar LocalNodeState
162+ { _localProcesses = Map.empty
163+ , _localPidCounter = firstNonReservedProcessId
164+ , _localPidUnique = unq
165+ , _localConnections = Map.empty
166+ }
167+ ctrlChan <- newChan
168+ let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
169+ , localEndPoint = endPoint
170+ , localState = state
171+ , localCtrlChan = ctrlChan
172+ , remoteTable = rtable
173+ }
174+ void . forkIO $ runNodeController node
175+ void . forkIO $ handleIncomingMessages node
176+ return node
177+
178+-- Like 'Control.Monad.forever' but sans space leak
179+{-# INLINE forever' #-}
180+forever' :: Monad m => m a -> m b
181+forever' a = let a' = a >> a' in a'
182+
183+-- | Start and register the service processes on a node
184+-- (for now, this is only the logger)
185+startServiceProcesses :: LocalNode -> IO ()
186+startServiceProcesses node = do
187+ logger <- forkProcess node . forever' $ do
188+ (time, pid, string) <- expect :: Process (String, ProcessId, String)
189+ liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
190+ runProcess node $ register "logger" logger
191+
192+-- | Force-close a local node
193+--
194+-- TODO: for now we just close the associated endpoint
195+closeLocalNode :: LocalNode -> IO ()
196+closeLocalNode node =
197+ NT.closeEndPoint (localEndPoint node)
198+
199+-- | Run a process on a local node and wait for it to finish
200+runProcess :: LocalNode -> Process () -> IO ()
201+runProcess node proc = do
202+ done <- newEmptyMVar
203+ void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
204+ takeMVar done
205+
206+-- | Spawn a new process on a local node
207+forkProcess :: LocalNode -> Process () -> IO ProcessId
208+forkProcess node proc = modifyMVar (localState node) startProcess
209+ where
210+ startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
211+ startProcess st = do
212+ let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
213+ , lpidUnique = st ^. localPidUnique
214+ }
215+ let pid = ProcessId { processNodeId = localNodeId node
216+ , processLocalId = lpid
217+ }
218+ pst <- newMVar LocalProcessState { _monitorCounter = 0
219+ , _spawnCounter = 0
220+ , _channelCounter = 0
221+ , _typedChannels = Map.empty
222+ }
223+ queue <- newCQueue
224+ (_, lproc) <- fixIO $ \ ~(tid, _) -> do
225+ let lproc = LocalProcess { processQueue = queue
226+ , processId = pid
227+ , processState = pst
228+ , processThread = tid
229+ , processNode = node
230+ }
231+ tid' <- forkIO $ do
232+ reason <- Exception.catch
233+ (runLocalProcess lproc proc >> return DiedNormal)
234+ (return . DiedException . (show :: SomeException -> String))
235+ -- [Unified: Table 4, rules termination and exiting]
236+ modifyMVar_ (localState node) (cleanupProcess pid)
237+ writeChan (localCtrlChan node) NCMsg
238+ { ctrlMsgSender = ProcessIdentifier pid
239+ , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
240+ }
241+ return (tid', lproc)
242+
243+ if lpidCounter lpid == maxBound
244+ then do
245+ newUnique <- randomIO
246+ return ( (localProcessWithId lpid ^= Just lproc)
247+ . (localPidCounter ^= firstNonReservedProcessId)
248+ . (localPidUnique ^= newUnique)
249+ $ st
250+ , pid
251+ )
252+ else
253+ return ( (localProcessWithId lpid ^= Just lproc)
254+ . (localPidCounter ^: (+ 1))
255+ $ st
256+ , pid
257+ )
258+
259+ cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
260+ cleanupProcess pid st = do
261+ let pid' = ProcessIdentifier pid
262+ let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
263+ mapM_ (NT.close . fst) (Map.elems affected)
264+ return $ (localProcessWithId (processLocalId pid) ^= Nothing)
265+ . (localConnections ^= unaffected)
266+ $ st
267+
268+--------------------------------------------------------------------------------
269+-- Handle incoming messages --
270+--------------------------------------------------------------------------------
271+
272+type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
273+
274+data IncomingTarget =
275+ Uninit
276+ | ToProc LocalProcess
277+ | ToChan TypedChannel
278+ | ToNode
279+
280+data ConnectionState = ConnectionState {
281+ _incoming :: !(Map NT.ConnectionId IncomingConnection)
282+ , _incomingFrom :: !(Map NT.EndPointAddress (Set NT.ConnectionId))
283+ }
284+
285+initConnectionState :: ConnectionState
286+initConnectionState = ConnectionState {
287+ _incoming = Map.empty
288+ , _incomingFrom = Map.empty
289+ }
290+
291+incoming :: Accessor ConnectionState (Map NT.ConnectionId IncomingConnection)
292+incoming = accessor _incoming (\conns st -> st { _incoming = conns })
293+
294+incomingAt :: NT.ConnectionId -> Accessor ConnectionState (Maybe IncomingConnection)
295+incomingAt cid = incoming >>> DAC.mapMaybe cid
296+
297+incomingFrom :: NT.EndPointAddress -> Accessor ConnectionState (Set NT.ConnectionId)
298+incomingFrom addr = aux >>> strictMapDefault Set.empty addr
299+ where
300+ aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
301+
302+handleIncomingMessages :: LocalNode -> IO ()
303+handleIncomingMessages node = go initConnectionState
304+ where
305+ go :: ConnectionState -> IO ()
306+ go !st = do
307+ event <- NT.receive endpoint
308+ case event of
309+ NT.ConnectionOpened cid rel theirAddr ->
310+ if rel == NT.ReliableOrdered
311+ then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
312+ . (incomingFrom theirAddr ^: Set.insert cid)
313+ $ st
314+ )
315+ else invalidRequest cid st
316+ NT.Received cid payload ->
317+ case st ^. incomingAt cid of
318+ Just (_, ToProc proc) -> do
319+ let msg = payloadToMessage payload
320+ enqueue (processQueue proc) msg
321+ go st
322+ Just (_, ToChan (TypedChannel chan')) -> do
323+ mChan <- deRefWeak chan'
324+ -- If mChan is Nothing, the process has given up the read end of
325+ -- the channel and we simply ignore the incoming message
326+ forM_ mChan $ \chan -> atomically $
327+ writeTQueue chan . decode . BSL.fromChunks $ payload
328+ go st
329+ Just (_, ToNode) -> do
330+ let ctrlMsg = decode . BSL.fromChunks $ payload
331+ writeChan ctrlChan ctrlMsg
332+ go st
333+ Just (src, Uninit) ->
334+ case decode (BSL.fromChunks payload) of
335+ ProcessIdentifier pid -> do
336+ let lpid = processLocalId pid
337+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
338+ case mProc of
339+ Just proc ->
340+ go (incomingAt cid ^= Just (src, ToProc proc) $ st)
341+ Nothing ->
342+ invalidRequest cid st
343+ SendPortIdentifier chId -> do
344+ let lcid = sendPortLocalId chId
345+ lpid = processLocalId (sendPortProcessId chId)
346+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
347+ case mProc of
348+ Just proc -> do
349+ mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
350+ case mChannel of
351+ Just channel ->
352+ go (incomingAt cid ^= Just (src, ToChan channel) $ st)
353+ Nothing ->
354+ invalidRequest cid st
355+ Nothing ->
356+ invalidRequest cid st
357+ NodeIdentifier nid ->
358+ if nid == localNodeId node
359+ then go (incomingAt cid ^= Just (src, ToNode) $ st)
360+ else invalidRequest cid st
361+ Nothing ->
362+ invalidRequest cid st
363+ NT.ConnectionClosed cid ->
364+ case st ^. incomingAt cid of
365+ Nothing ->
366+ invalidRequest cid st
367+ Just (src, _) ->
368+ go ( (incomingAt cid ^= Nothing)
369+ . (incomingFrom src ^: Set.delete cid)
370+ $ st
371+ )
372+ NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr) _) -> do
373+ -- [Unified table 9, rule node_disconnect]
374+ let nid = NodeIdentifier $ NodeId theirAddr
375+ writeChan ctrlChan NCMsg
376+ { ctrlMsgSender = nid
377+ , ctrlMsgSignal = Died nid DiedDisconnect
378+ }
379+ let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
380+ closeImplicitReconnections node nid
381+ go ( (incomingFrom theirAddr ^= Set.empty)
382+ . (incoming ^: Map.filterWithKey (const . notLost))
383+ $ st
384+ )
385+ NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
386+ fail $ "Cloud Haskell fatal error: end point failed: " ++ str
387+ NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
388+ fail $ "Cloud Haskell fatal error: transport failed: " ++ str
389+ NT.EndPointClosed ->
390+ return ()
391+ NT.ReceivedMulticast _ _ ->
392+ -- If we received a multicast message, something went horribly wrong
393+ -- and we just give up
394+ fail "Cloud Haskell fatal error: received unexpected multicast"
395+
396+ invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
397+ invalidRequest cid st = do
398+ -- TODO: We should treat this as a fatal error on the part of the remote
399+ -- node. That is, we should report the remote node as having died, and we
400+ -- should close incoming connections (this requires a Transport layer
401+ -- extension).
402+ go ( (incomingAt cid ^= Nothing)
403+ $ st
404+ )
405+
406+ state = localState node
407+ endpoint = localEndPoint node
408+ ctrlChan = localCtrlChan node
409+
410+--------------------------------------------------------------------------------
411+-- Top-level access to the node controller --
412+--------------------------------------------------------------------------------
413+
414+runNodeController :: LocalNode -> IO ()
415+runNodeController =
416+ runReaderT (evalStateT (unNC nodeController) initNCState)
417+
418+--------------------------------------------------------------------------------
419+-- Internal data types --
420+--------------------------------------------------------------------------------
421+
422+data NCState = NCState
423+ { -- Mapping from remote processes to linked local processes
424+ _links :: !(Map Identifier (Set ProcessId))
425+ -- Mapping from remote processes to monitoring local processes
426+ , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
427+ -- Process registry
428+ , _registry :: !(Map String ProcessId)
429+ }
430+
431+newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
432+ deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
433+
434+initNCState :: NCState
435+initNCState = NCState { _links = Map.empty
436+ , _monitors = Map.empty
437+ , _registry = Map.empty
438+ }
439+
440+--------------------------------------------------------------------------------
441+-- Core functionality --
442+--------------------------------------------------------------------------------
443+
444+-- [Unified: Table 7]
445+nodeController :: NC ()
446+nodeController = do
447+ node <- ask
448+ forever' $ do
449+ msg <- liftIO $ readChan (localCtrlChan node)
450+
451+ -- [Unified: Table 7, rule nc_forward]
452+ case destNid (ctrlMsgSignal msg) of
453+ Just nid' | nid' /= localNodeId node ->
454+ liftIO $ sendBinary node
455+ (ctrlMsgSender msg)
456+ (NodeIdentifier nid')
457+ WithImplicitReconnect
458+ msg
459+ _ ->
460+ return ()
461+
462+ case msg of
463+ NCMsg (ProcessIdentifier from) (Link them) ->
464+ ncEffectMonitor from them Nothing
465+ NCMsg (ProcessIdentifier from) (Monitor ref) ->
466+ ncEffectMonitor from (monitorRefIdent ref) (Just ref)
467+ NCMsg (ProcessIdentifier from) (Unlink them) ->
468+ ncEffectUnlink from them
469+ NCMsg (ProcessIdentifier from) (Unmonitor ref) ->
470+ ncEffectUnmonitor from ref
471+ NCMsg _from (Died ident reason) ->
472+ ncEffectDied ident reason
473+ NCMsg (ProcessIdentifier from) (Spawn proc ref) ->
474+ ncEffectSpawn from proc ref
475+ NCMsg _from (Register label pid) ->
476+ ncEffectRegister label pid
477+ NCMsg (ProcessIdentifier from) (WhereIs label) ->
478+ ncEffectWhereIs from label
479+ NCMsg from (NamedSend label msg') ->
480+ ncEffectNamedSend from label msg'
481+ unexpected ->
482+ error $ "nodeController: unexpected message " ++ show unexpected
483+
484+-- [Unified: Table 10]
485+ncEffectMonitor :: ProcessId -- ^ Who's watching?
486+ -> Identifier -- ^ Who's being watched?
487+ -> Maybe MonitorRef -- ^ 'Nothing' to link
488+ -> NC ()
489+ncEffectMonitor from them mRef = do
490+ node <- ask
491+ shouldLink <-
492+ if not (isLocal node them)
493+ then return True
494+ else isValidLocalIdentifier them
495+ case (shouldLink, isLocal node (ProcessIdentifier from)) of
496+ (True, _) -> -- [Unified: first rule]
497+ case mRef of
498+ Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
499+ Nothing -> modify' $ linksFor them ^: Set.insert from
500+ (False, True) -> -- [Unified: second rule]
501+ notifyDied from them DiedUnknownId mRef
502+ (False, False) -> -- [Unified: third rule]
503+ -- TODO: this is the right sender according to the Unified semantics,
504+ -- but perhaps having 'them' as the sender would make more sense
505+ -- (see also: notifyDied)
506+ liftIO $ sendBinary node
507+ (NodeIdentifier $ localNodeId node)
508+ (NodeIdentifier $ processNodeId from)
509+ WithImplicitReconnect
510+ NCMsg
511+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
512+ , ctrlMsgSignal = Died them DiedUnknownId
513+ }
514+
515+-- [Unified: Table 11]
516+ncEffectUnlink :: ProcessId -> Identifier -> NC ()
517+ncEffectUnlink from them = do
518+ node <- ask
519+ when (isLocal node (ProcessIdentifier from)) $
520+ case them of
521+ ProcessIdentifier pid ->
522+ postAsMessage from $ DidUnlinkProcess pid
523+ NodeIdentifier nid ->
524+ postAsMessage from $ DidUnlinkNode nid
525+ SendPortIdentifier cid ->
526+ postAsMessage from $ DidUnlinkPort cid
527+ modify' $ linksFor them ^: Set.delete from
528+
529+-- [Unified: Table 11]
530+ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
531+ncEffectUnmonitor from ref = do
532+ node <- ask
533+ when (isLocal node (ProcessIdentifier from)) $
534+ postAsMessage from $ DidUnmonitor ref
535+ modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
536+
537+-- [Unified: Table 12]
538+ncEffectDied :: Identifier -> DiedReason -> NC ()
539+ncEffectDied ident reason = do
540+ node <- ask
541+ (affectedLinks, unaffectedLinks) <- gets (splitNotif ident . (^. links))
542+ (affectedMons, unaffectedMons) <- gets (splitNotif ident . (^. monitors))
543+
544+ let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
545+
546+ forM_ (Map.toList affectedLinks) $ \(them, uss) ->
547+ forM_ uss $ \us ->
548+ when (localOnly <= isLocal node (ProcessIdentifier us)) $
549+ notifyDied us them reason Nothing
550+
551+ forM_ (Map.toList affectedMons) $ \(them, refs) ->
552+ forM_ refs $ \(us, ref) ->
553+ when (localOnly <= isLocal node (ProcessIdentifier us)) $
554+ notifyDied us them reason (Just ref)
555+
556+ modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
557+
558+-- [Unified: Table 13]
559+ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
560+ncEffectSpawn pid cProc ref = do
561+ mProc <- unClosure cProc
562+ -- If the closure does not exist, we spawn a process that throws an exception
563+ -- This allows the remote node to find out what's happening
564+ let proc = case mProc of
565+ Left err -> fail $ "Error: Could not resolve closure: " ++ err
566+ Right p -> p
567+ node <- ask
568+ pid' <- liftIO $ forkProcess node proc
569+ liftIO $ sendMessage node
570+ (NodeIdentifier (localNodeId node))
571+ (ProcessIdentifier pid)
572+ WithImplicitReconnect
573+ (DidSpawn ref pid')
574+
575+-- Unified semantics does not explicitly describe how to implement 'register',
576+-- but mentions it's "very similar to nsend" (Table 14)
577+ncEffectRegister :: String -> Maybe ProcessId -> NC ()
578+ncEffectRegister label mPid =
579+ modify' $ registryFor label ^= mPid
580+ -- An acknowledgement is not necessary. If we want a synchronous register,
581+ -- it suffices to send a whereis requiry immediately after the register
582+ -- (that may not suffice if we do decide for unreliable messaging instead)
583+
584+-- Unified semantics does not explicitly describe 'whereis'
585+ncEffectWhereIs :: ProcessId -> String -> NC ()
586+ncEffectWhereIs from label = do
587+ node <- ask
588+ mPid <- gets (^. registryFor label)
589+ liftIO $ sendMessage node
590+ (NodeIdentifier (localNodeId node))
591+ (ProcessIdentifier from)
592+ WithImplicitReconnect
593+ (WhereIsReply label mPid)
594+
595+-- [Unified: Table 14]
596+ncEffectNamedSend :: Identifier -> String -> Message -> NC ()
597+ncEffectNamedSend from label msg = do
598+ mPid <- gets (^. registryFor label)
599+ -- If mPid is Nothing, we just ignore the named send (as per Table 14)
600+ node <- ask
601+ forM_ mPid $ \pid ->
602+ liftIO $ sendPayload node
603+ from
604+ (ProcessIdentifier pid)
605+ NoImplicitReconnect
606+ (messageToPayload msg)
607+
608+--------------------------------------------------------------------------------
609+-- Auxiliary --
610+--------------------------------------------------------------------------------
611+
612+notifyDied :: ProcessId -- ^ Who to notify?
613+ -> Identifier -- ^ Who died?
614+ -> DiedReason -- ^ How did they die?
615+ -> Maybe MonitorRef -- ^ 'Nothing' for linking
616+ -> NC ()
617+notifyDied dest src reason mRef = do
618+ node <- ask
619+ case (isLocal node (ProcessIdentifier dest), mRef, src) of
620+ (True, Just ref, ProcessIdentifier pid) ->
621+ postAsMessage dest $ ProcessMonitorNotification ref pid reason
622+ (True, Just ref, NodeIdentifier nid) ->
623+ postAsMessage dest $ NodeMonitorNotification ref nid reason
624+ (True, Just ref, SendPortIdentifier cid) ->
625+ postAsMessage dest $ PortMonitorNotification ref cid reason
626+ (True, Nothing, ProcessIdentifier pid) ->
627+ throwException dest $ ProcessLinkException pid reason
628+ (True, Nothing, NodeIdentifier pid) ->
629+ throwException dest $ NodeLinkException pid reason
630+ (True, Nothing, SendPortIdentifier pid) ->
631+ throwException dest $ PortLinkException pid reason
632+ (False, _, _) ->
633+ -- The change in sender comes from [Unified: Table 10]
634+ liftIO $ sendBinary node
635+ (NodeIdentifier $ localNodeId node)
636+ (NodeIdentifier $ processNodeId dest)
637+ WithImplicitReconnect
638+ NCMsg
639+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
640+ , ctrlMsgSignal = Died src reason
641+ }
642+
643+-- | [Unified: Table 8]
644+destNid :: ProcessSignal -> Maybe NodeId
645+destNid (Link ident) = Just $ nodeOf ident
646+destNid (Unlink ident) = Just $ nodeOf ident
647+destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
648+destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
649+destNid (Spawn _ _) = Nothing
650+destNid (Register _ _) = Nothing
651+destNid (WhereIs _) = Nothing
652+destNid (NamedSend _ _) = Nothing
653+-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
654+-- then when a local process dies the monitoring/linking machinery will take
655+-- care of notifying remote nodes
656+destNid (Died _ _) = Nothing
657+
658+-- | Check if a process is local to our own node
659+isLocal :: LocalNode -> Identifier -> Bool
660+isLocal nid ident = nodeOf ident == localNodeId nid
661+
662+-- | Lookup a local closure
663+unClosure :: Typeable a => Closure a -> NC (Either String a)
664+unClosure closure = do
665+ rtable <- remoteTable <$> ask
666+ return (Static.unclosure rtable closure)
667+
668+-- | Check if an identifier refers to a valid local object
669+isValidLocalIdentifier :: Identifier -> NC Bool
670+isValidLocalIdentifier ident = do
671+ node <- ask
672+ liftIO . withMVar (localState node) $ \nSt ->
673+ case ident of
674+ NodeIdentifier nid ->
675+ return $ nid == localNodeId node
676+ ProcessIdentifier pid -> do
677+ let mProc = nSt ^. localProcessWithId (processLocalId pid)
678+ return $ isJust mProc
679+ SendPortIdentifier cid -> do
680+ let pid = sendPortProcessId cid
681+ mProc = nSt ^. localProcessWithId (processLocalId pid)
682+ case mProc of
683+ Nothing -> return False
684+ Just proc -> withMVar (processState proc) $ \pSt -> do
685+ let mCh = pSt ^. typedChannelWithId (sendPortLocalId cid)
686+ return $ isJust mCh
687+
688+--------------------------------------------------------------------------------
689+-- Messages to local processes --
690+--------------------------------------------------------------------------------
691+
692+postAsMessage :: Serializable a => ProcessId -> a -> NC ()
693+postAsMessage pid = postMessage pid . createMessage
694+
695+postMessage :: ProcessId -> Message -> NC ()
696+postMessage pid msg = withLocalProc pid $ \p -> enqueue (processQueue p) msg
697+
698+throwException :: Exception e => ProcessId -> e -> NC ()
699+throwException pid e = withLocalProc pid $ \p ->
700+ throwTo (processThread p) e
701+
702+withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
703+withLocalProc pid p = do
704+ node <- ask
705+ liftIO $ do
706+ -- By [Unified: table 6, rule missing_process] messages to dead processes
707+ -- can silently be dropped
708+ let lpid = processLocalId pid
709+ mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
710+ forM_ mProc p
711+
712+--------------------------------------------------------------------------------
713+-- Accessors --
714+--------------------------------------------------------------------------------
715+
716+links :: Accessor NCState (Map Identifier (Set ProcessId))
717+links = accessor _links (\ls st -> st { _links = ls })
718+
719+monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
720+monitors = accessor _monitors (\ms st -> st { _monitors = ms })
721+
722+registry :: Accessor NCState (Map String ProcessId)
723+registry = accessor _registry (\ry st -> st { _registry = ry })
724+
725+linksFor :: Identifier -> Accessor NCState (Set ProcessId)
726+linksFor ident = links >>> DAC.mapDefault Set.empty ident
727+
728+monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
729+monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
730+
731+registryFor :: String -> Accessor NCState (Maybe ProcessId)
732+registryFor ident = registry >>> DAC.mapMaybe ident
733+
734+-- | @splitNotif ident@ splits a notifications map into those
735+-- notifications that should trigger when 'ident' fails and those links that
736+-- should not.
737+--
738+-- There is a hierarchy between identifiers: failure of a node implies failure
739+-- of all processes on that node, and failure of a process implies failure of
740+-- all typed channels to that process. In other words, if 'ident' refers to a
741+-- node, then the /should trigger/ set will include
742+--
743+-- * the notifications for the node specifically
744+-- * the notifications for processes on that node, and
745+-- * the notifications for typed channels to processes on that node.
746+--
747+-- Similarly, if 'ident' refers to a process, the /should trigger/ set will
748+-- include
749+--
750+-- * the notifications for that process specifically and
751+-- * the notifications for typed channels to that process.
752+--
753+-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
754+splitNotif :: Identifier
755+ -> Map Identifier a
756+ -> (Map Identifier a, Map Identifier a)
757+splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
758+
759+--------------------------------------------------------------------------------
760+-- Auxiliary --
761+--------------------------------------------------------------------------------
762+
763+-- | Modify and evaluate the state
764+modify' :: MonadState s m => (s -> s) -> m ()
765+modify' f = StateT.get >>= \s -> StateT.put $! f s
766+
767+-- | Strict lens for Map
768+strictMapDefault :: Ord key => elem -> key -> Accessor (Map key elem) elem
769+strictMapDefault def key =
770+ accessor (Map.findWithDefault def key)
771+ (\val -> val `seq` Map.insert key val)
src/Control/Distributed/Process/Serializable.hs → distributed-process/src/Control/Distributed/Process/Serializable.hsrenamed+16−26
@@ -1,9 +1,4 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE UndecidableInstances #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE GADTs #-}
6−module Control.Distributed.Process.Serializable
1+module Control.Distributed.Process.Serializable
7 ( Serializable
2 ( Serializable
8 , encodeFingerprint
3 , encodeFingerprint
9 , decodeFingerprint
4 , decodeFingerprint
@@ -12,62 +7,57 @@ module Control.Distributed.Process.Serializable
12 , Fingerprint
7 , Fingerprint
13 , showFingerprint
8 , showFingerprint
14 , SerializableDict(SerializableDict)
9 , SerializableDict(SerializableDict)
15− , TypeableDict(TypeableDict)
16 ) where
10 ) where
17
11
18import Data.Binary (Binary)
12import Data.Binary (Binary)
19−
20−import Data.Typeable (Typeable, typeRepFingerprint, typeOf)
13+import Data.Typeable (Typeable(..))
21−
14+import Data.Typeable.Internal (TypeRep(TypeRep))
22import Numeric (showHex)
15import Numeric (showHex)
23import Control.Exception (throw)
16import Control.Exception (throw)
24import GHC.Fingerprint.Type (Fingerprint(..))
17import GHC.Fingerprint.Type (Fingerprint(..))
25import Data.ByteString (ByteString)
18import Data.ByteString (ByteString)
26import qualified Data.ByteString as BS
19import qualified Data.ByteString as BS
27−import qualified Data.ByteString.Internal as BSI ( unsafeCreate, toForeignPtr )
20+import qualified Data.ByteString.Internal as BSI ( unsafeCreate
21+ , inlinePerformIO
22+ , toForeignPtr
23+ )
28import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
24import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
29import Foreign.ForeignPtr (withForeignPtr)
25import Foreign.ForeignPtr (withForeignPtr)
30−import System.IO.Unsafe (unsafePerformIO)
31
26
32-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
27-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
33data SerializableDict a where
28data SerializableDict a where
34 SerializableDict :: Serializable a => SerializableDict a
29 SerializableDict :: Serializable a => SerializableDict a
35 deriving (Typeable)
30 deriving (Typeable)
36
31
37−-- | Reification of 'Typeable'.
38−data TypeableDict a where
39− TypeableDict :: Typeable a => TypeableDict a
40− deriving (Typeable)
41−
42-- | Objects that can be sent across the network
32-- | Objects that can be sent across the network
43class (Binary a, Typeable a) => Serializable a
33class (Binary a, Typeable a) => Serializable a
44instance (Binary a, Typeable a) => Serializable a
34instance (Binary a, Typeable a) => Serializable a
45
35
46-- | Encode type representation as a bytestring
36-- | Encode type representation as a bytestring
47encodeFingerprint :: Fingerprint -> ByteString
37encodeFingerprint :: Fingerprint -> ByteString
48−encodeFingerprint fp =
38+encodeFingerprint fp =
49 -- Since all CH nodes will run precisely the same binary, we don't have to
39 -- Since all CH nodes will run precisely the same binary, we don't have to
50 -- worry about cross-arch issues here (like endianness)
40 -- worry about cross-arch issues here (like endianness)
51− BSI.unsafeCreate sizeOfFingerprint $ \p -> pokeByteOff p 0 fp
41+ BSI.unsafeCreate sizeOfFingerprint $ \p -> pokeByteOff p 0 fp
52
42
53−-- | Decode a bytestring into a fingerprint. Throws an IO exception on failure
43+-- | Decode a bytestring into a fingerprint. Throws an IO exception on failure
54decodeFingerprint :: ByteString -> Fingerprint
44decodeFingerprint :: ByteString -> Fingerprint
55decodeFingerprint bs
45decodeFingerprint bs
56− | BS.length bs /= sizeOfFingerprint =
46+ | BS.length bs /= sizeOfFingerprint =
57 throw $ userError "decodeFingerprint: Invalid length"
47 throw $ userError "decodeFingerprint: Invalid length"
58− | otherwise = unsafePerformIO $ do
48+ | otherwise = BSI.inlinePerformIO $ do
59 let (fp, offset, _) = BSI.toForeignPtr bs
49 let (fp, offset, _) = BSI.toForeignPtr bs
60− withForeignPtr fp $ \p -> peekByteOff p offset
50+ withForeignPtr fp $ \p -> peekByteOff p offset
61
51
62-- | Size of a fingerprint
52-- | Size of a fingerprint
63sizeOfFingerprint :: Int
53sizeOfFingerprint :: Int
64sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
54sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
65
55
66−-- | The fingerprint of the typeRep of the argument
56+-- | The fingerprint of the typeRep of the argument
67fingerprint :: Typeable a => a -> Fingerprint
57fingerprint :: Typeable a => a -> Fingerprint
68−fingerprint = typeRepFingerprint . typeOf
58+fingerprint a = let TypeRep fp _ _ = typeOf a in fp
69
59
70-- | Show fingerprint (for debugging purposes)
60-- | Show fingerprint (for debugging purposes)
71showFingerprint :: Fingerprint -> ShowS
61showFingerprint :: Fingerprint -> ShowS
72−showFingerprint (Fingerprint hi lo) =
62+showFingerprint (Fingerprint hi lo) =
73 showString "(" . showHex hi . showString "," . showHex lo . showString ")"
63 showString "(" . showHex hi . showString "," . showHex lo . showString ")"
distributed-process/tests/TestAuxiliary.hsnew file+75
@@ -0,0 +1,75 @@
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.hsnew file+742
@@ -0,0 +1,742 @@
1+module Main where
2+
3+#if ! MIN_VERSION_base(4,6,0)
4+import Prelude hiding (catch)
5+#endif
6+
7+import Data.Binary (Binary(..))
8+import Data.Typeable (Typeable)
9+import Data.Foldable (forM_)
10+import Control.Concurrent (forkIO, threadDelay)
11+import Control.Concurrent.MVar
12+ ( MVar
13+ , newEmptyMVar
14+ , putMVar
15+ , takeMVar
16+ , readMVar
17+ )
18+import Control.Monad (replicateM_, replicateM, forever)
19+import Control.Exception (throwIO)
20+import Control.Applicative ((<$>), (<*>))
21+import qualified Network.Transport as NT (Transport, closeEndPoint)
22+import Network.Socket (sClose)
23+import Network.Transport.TCP
24+ ( createTransportExposeInternals
25+ , TransportInternals(socketBetween)
26+ , defaultTCPParameters
27+ )
28+import Control.Distributed.Process
29+import Control.Distributed.Process.Internal.Types
30+ ( NodeId(nodeAddress)
31+ , LocalNode(localEndPoint)
32+ )
33+import Control.Distributed.Process.Node
34+import Control.Distributed.Process.Serializable (Serializable)
35+import TestAuxiliary
36+
37+newtype Ping = Ping ProcessId
38+ deriving (Typeable, Binary, Show)
39+
40+newtype Pong = Pong ProcessId
41+ deriving (Typeable, Binary, Show)
42+
43+-- | The ping server from the paper
44+ping :: Process ()
45+ping = do
46+ Pong partner <- expect
47+ self <- getSelfPid
48+ send partner (Ping self)
49+ ping
50+
51+-- | Quick and dirty synchronous version of whereisRemoteAsync
52+whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
53+whereisRemote nid string = do
54+ whereisRemoteAsync nid string
55+ WhereIsReply _ mPid <- expect
56+ return mPid
57+
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
74+
75+ let numPings = 10000
76+
77+ runProcess localNode $ do
78+ pid <- getSelfPid
79+ replicateM_ numPings $ do
80+ send pingServer (Pong pid)
81+ Ping _ <- expect
82+ return ()
83+
84+ putMVar clientDone ()
85+
86+ takeMVar clientDone
87+
88+-- | Monitor or link to a remote node
89+monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
90+ -> ProcessId -- Process to monitor/link to
91+ -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up
92+ -> Process (Maybe MonitorRef)
93+monitorOrLink mOrL pid mSignal = do
94+ result <- if mOrL then Just <$> monitor pid
95+ else link pid >> return Nothing
96+ -- Monitor is asynchronous, which usually does not matter but if we want a
97+ -- *specific* signal then it does. Therefore we wait an arbitrary delay and
98+ -- hope that this means the monitor has been set up
99+ forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()
100+ return result
101+
102+monitorTestProcess :: ProcessId -- Process to monitor/link to
103+ -> Bool -- 'True' for monitor, 'False' for link
104+ -> Bool -- Should we unmonitor?
105+ -> DiedReason -- Expected cause of death
106+ -> Maybe (MVar ()) -- Signal for 'monitor set up'
107+ -> MVar () -- Signal for successful termination
108+ -> Process ()
109+monitorTestProcess theirAddr mOrL un reason monitorSetup done =
110+ catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
111+ case (un, mRef) of
112+ (True, Nothing) -> do
113+ unlink theirAddr
114+ liftIO $ putMVar done ()
115+ (True, Just ref) -> do
116+ unmonitor ref
117+ liftIO $ putMVar done ()
118+ (False, ref) -> do
119+ ProcessMonitorNotification ref' pid reason' <- expect
120+ True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
121+ liftIO $ putMVar done ()
122+ )
123+ (\(ProcessLinkException pid reason') -> do
124+ True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
125+ liftIO $ putMVar done ()
126+ )
127+
128+
129+-- | Monitor a process on an unreachable node
130+testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> IO ()
131+testMonitorUnreachable transport mOrL un = do
132+ deadProcess <- newEmptyMVar
133+ done <- newEmptyMVar
134+
135+ forkIO $ do
136+ localNode <- newLocalNode transport initRemoteTable
137+ addr <- forkProcess localNode . liftIO $ threadDelay 1000000
138+ closeLocalNode localNode
139+ putMVar deadProcess addr
140+
141+ forkIO $ do
142+ localNode <- newLocalNode transport initRemoteTable
143+ theirAddr <- readMVar deadProcess
144+ runProcess localNode $
145+ monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
146+
147+ takeMVar done
148+
149+-- | Monitor a process which terminates normally
150+testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> IO ()
151+testMonitorNormalTermination transport mOrL un = do
152+ monitorSetup <- newEmptyMVar
153+ monitoredProcess <- newEmptyMVar
154+ done <- newEmptyMVar
155+
156+ forkIO $ do
157+ localNode <- newLocalNode transport initRemoteTable
158+ addr <- forkProcess localNode $
159+ liftIO $ readMVar monitorSetup
160+ putMVar monitoredProcess addr
161+
162+ forkIO $ do
163+ localNode <- newLocalNode transport initRemoteTable
164+ theirAddr <- readMVar monitoredProcess
165+ runProcess localNode $
166+ monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
167+
168+ takeMVar done
169+
170+-- | Monitor a process which terminates abnormally
171+testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> IO ()
172+testMonitorAbnormalTermination transport mOrL un = do
173+ monitorSetup <- newEmptyMVar
174+ monitoredProcess <- newEmptyMVar
175+ done <- newEmptyMVar
176+
177+ let err = userError "Abnormal termination"
178+
179+ forkIO $ do
180+ localNode <- newLocalNode transport initRemoteTable
181+ addr <- forkProcess localNode . liftIO $ do
182+ readMVar monitorSetup
183+ throwIO err
184+ putMVar monitoredProcess addr
185+
186+ forkIO $ do
187+ localNode <- newLocalNode transport initRemoteTable
188+ theirAddr <- readMVar monitoredProcess
189+ runProcess localNode $
190+ monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
191+
192+ takeMVar done
193+
194+-- | Monitor a local process that is already dead
195+testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()
196+testMonitorLocalDeadProcess transport mOrL un = do
197+ processDead <- newEmptyMVar
198+ processAddr <- newEmptyMVar
199+ localNode <- newLocalNode transport initRemoteTable
200+ done <- newEmptyMVar
201+
202+ forkIO $ do
203+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
204+ putMVar processAddr addr
205+
206+ forkIO $ do
207+ theirAddr <- readMVar processAddr
208+ readMVar processDead
209+ runProcess localNode $ do
210+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
211+
212+ takeMVar done
213+
214+-- | Monitor a remote process that is already dead
215+testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> IO ()
216+testMonitorRemoteDeadProcess transport mOrL un = do
217+ processDead <- newEmptyMVar
218+ processAddr <- newEmptyMVar
219+ done <- newEmptyMVar
220+
221+ forkIO $ do
222+ localNode <- newLocalNode transport initRemoteTable
223+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
224+ putMVar processAddr addr
225+
226+ forkIO $ do
227+ localNode <- newLocalNode transport initRemoteTable
228+ theirAddr <- readMVar processAddr
229+ readMVar processDead
230+ runProcess localNode $ do
231+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
232+
233+ takeMVar done
234+
235+-- | Monitor a process that becomes disconnected
236+testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> IO ()
237+testMonitorDisconnect transport mOrL un = do
238+ processAddr <- newEmptyMVar
239+ monitorSetup <- newEmptyMVar
240+ done <- newEmptyMVar
241+
242+ forkIO $ do
243+ localNode <- newLocalNode transport initRemoteTable
244+ addr <- forkProcess localNode . liftIO $ threadDelay 1000000
245+ putMVar processAddr addr
246+ readMVar monitorSetup
247+ NT.closeEndPoint (localEndPoint localNode)
248+
249+ forkIO $ do
250+ localNode <- newLocalNode transport initRemoteTable
251+ theirAddr <- readMVar processAddr
252+ runProcess localNode $ do
253+ monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
254+
255+ takeMVar done
256+
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+
283+-- | Test the math server (i.e., receiveWait)
284+testMath :: NT.Transport -> IO ()
285+testMath transport = do
286+ serverAddr <- newEmptyMVar
287+ clientDone <- newEmptyMVar
288+
289+ -- Server
290+ forkIO $ do
291+ localNode <- newLocalNode transport initRemoteTable
292+ addr <- forkProcess localNode math
293+ putMVar serverAddr addr
294+
295+ -- Client
296+ forkIO $ do
297+ localNode <- newLocalNode transport initRemoteTable
298+ mathServer <- readMVar serverAddr
299+
300+ runProcess localNode $ do
301+ pid <- getSelfPid
302+ send mathServer (Add pid 1 2)
303+ 3 <- expect :: Process Double
304+ send mathServer (Divide pid 8 2)
305+ 4 <- expect :: Process Double
306+ send mathServer (Divide pid 8 0)
307+ DivByZero <- expect
308+ liftIO $ putMVar clientDone ()
309+
310+ takeMVar clientDone
311+
312+-- | Send first message (i.e. connect) to an already terminated process
313+-- (without monitoring); then send another message to a second process on
314+-- the same remote node (we're checking that the remote node did not die)
315+testSendToTerminated :: NT.Transport -> IO ()
316+testSendToTerminated transport = do
317+ serverAddr1 <- newEmptyMVar
318+ serverAddr2 <- newEmptyMVar
319+ clientDone <- newEmptyMVar
320+
321+ forkIO $ do
322+ terminated <- newEmptyMVar
323+ localNode <- newLocalNode transport initRemoteTable
324+ addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
325+ addr2 <- forkProcess localNode $ ping
326+ readMVar terminated
327+ putMVar serverAddr1 addr1
328+ putMVar serverAddr2 addr2
329+
330+ forkIO $ do
331+ localNode <- newLocalNode transport initRemoteTable
332+ server1 <- readMVar serverAddr1
333+ server2 <- readMVar serverAddr2
334+ runProcess localNode $ do
335+ pid <- getSelfPid
336+ send server1 "Hi"
337+ send server2 (Pong pid)
338+ Ping pid' <- expect
339+ True <- return $ pid' == server2
340+ liftIO $ putMVar clientDone ()
341+
342+ takeMVar clientDone
343+
344+-- | Test (non-zero) timeout
345+testTimeout :: NT.Transport -> IO ()
346+testTimeout transport = do
347+ localNode <- newLocalNode transport initRemoteTable
348+ runProcess localNode $ do
349+ Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
350+ return ()
351+
352+-- | Test zero timeout
353+testTimeout0 :: NT.Transport -> IO ()
354+testTimeout0 transport = do
355+ serverAddr <- newEmptyMVar
356+ clientDone <- newEmptyMVar
357+ messagesSent <- newEmptyMVar
358+
359+ forkIO $ do
360+ localNode <- newLocalNode transport initRemoteTable
361+ addr <- forkProcess localNode $ do
362+ liftIO $ readMVar messagesSent >> threadDelay 1000000
363+ -- Variation on the venerable ping server which uses a zero timeout
364+ -- Since we wait for all messages to be sent before doing this receive,
365+ -- we should nevertheless find the right message immediately
366+ Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
367+ self <- getSelfPid
368+ send partner (Ping self)
369+ putMVar serverAddr addr
370+
371+ forkIO $ do
372+ localNode <- newLocalNode transport initRemoteTable
373+ server <- readMVar serverAddr
374+ runProcess localNode $ do
375+ pid <- getSelfPid
376+ -- Send a bunch of messages. A large number of messages that the server
377+ -- is not interested in, and then a single message that it wants
378+ replicateM_ 10000 $ send server "Irrelevant message"
379+ send server (Pong pid)
380+ liftIO $ putMVar messagesSent ()
381+ Ping _ <- expect
382+ liftIO $ putMVar clientDone ()
383+
384+ takeMVar clientDone
385+
386+-- | Test typed channels
387+testTypedChannels :: NT.Transport -> IO ()
388+testTypedChannels transport = do
389+ serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
390+ clientDone <- newEmptyMVar
391+
392+ forkIO $ do
393+ localNode <- newLocalNode transport initRemoteTable
394+ forkProcess localNode $ do
395+ (serverSendPort, rport) <- newChan
396+ liftIO $ putMVar serverChannel serverSendPort
397+ (clientSendPort, i) <- receiveChan rport
398+ sendChan clientSendPort (even i)
399+ return ()
400+
401+ forkIO $ do
402+ localNode <- newLocalNode transport initRemoteTable
403+ serverSendPort <- readMVar serverChannel
404+ runProcess localNode $ do
405+ (clientSendPort, rport) <- newChan
406+ sendChan serverSendPort (clientSendPort, 5)
407+ False <- receiveChan rport
408+ liftIO $ putMVar clientDone ()
409+
410+ takeMVar clientDone
411+
412+-- | Test merging receive ports
413+testMergeChannels :: NT.Transport -> IO ()
414+testMergeChannels transport = do
415+ localNode <- newLocalNode transport initRemoteTable
416+ testFlat localNode True "aaabbbccc"
417+ testFlat localNode False "abcabcabc"
418+ testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
419+ testNested localNode True False "adgadgadgbehbehbehcficficfi"
420+ testNested localNode False True "abcabcabcdefdefdefghighighi"
421+ testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
422+ testBlocked localNode True
423+ testBlocked localNode False
424+ where
425+ -- Single layer of merging
426+ testFlat :: LocalNode -> Bool -> String -> IO ()
427+ testFlat localNode biased expected =
428+ runProcess localNode $ do
429+ rs <- mapM charChannel "abc"
430+ m <- mergePorts biased rs
431+ xs <- replicateM 9 $ receiveChan m
432+ True <- return $ xs == expected
433+ return ()
434+
435+ -- Two layers of merging
436+ testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
437+ testNested localNode biasedInner biasedOuter expected =
438+ runProcess localNode $ do
439+ rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
440+ ms <- mapM (mergePorts biasedInner) rss
441+ m <- mergePorts biasedOuter ms
442+ xs <- replicateM (9 * 3) $ receiveChan m
443+ True <- return $ xs == expected
444+ return ()
445+
446+ -- Test that if no messages are (immediately) available, the scheduler makes no difference
447+ testBlocked :: LocalNode -> Bool -> IO ()
448+ testBlocked localNode biased = do
449+ vs <- replicateM 3 newEmptyMVar
450+
451+ forkProcess localNode $ do
452+ [sa, sb, sc] <- liftIO $ mapM readMVar vs
453+ mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
454+ [ -- a, b, c
455+ (sa, 'a')
456+ , (sb, 'b')
457+ , (sc, 'c')
458+ -- a, c, b
459+ , (sa, 'a')
460+ , (sc, 'c')
461+ , (sb, 'b')
462+ -- b, a, c
463+ , (sb, 'b')
464+ , (sa, 'a')
465+ , (sc, 'c')
466+ -- b, c, a
467+ , (sb, 'b')
468+ , (sc, 'c')
469+ , (sa, 'a')
470+ -- c, a, b
471+ , (sc, 'c')
472+ , (sa, 'a')
473+ , (sb, 'b')
474+ -- c, b, a
475+ , (sc, 'c')
476+ , (sb, 'b')
477+ , (sa, 'a')
478+ ]
479+
480+ runProcess localNode $ do
481+ (ss, rs) <- unzip <$> replicateM 3 newChan
482+ liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
483+ m <- mergePorts biased rs
484+ xs <- replicateM (6 * 3) $ receiveChan m
485+ True <- return $ xs == "abcacbbacbcacabcba"
486+ return ()
487+
488+ mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
489+ mergePorts True = mergePortsBiased
490+ mergePorts False = mergePortsRR
491+
492+ charChannel :: Char -> Process (ReceivePort Char)
493+ charChannel c = do
494+ (sport, rport) <- newChan
495+ replicateM_ 3 $ sendChan sport c
496+ liftIO $ threadDelay 10000 -- Make sure messages have been sent
497+ return rport
498+
499+testTerminate :: NT.Transport -> IO ()
500+testTerminate transport = do
501+ localNode <- newLocalNode transport initRemoteTable
502+
503+ pid <- forkProcess localNode $ do
504+ liftIO $ threadDelay 100000
505+ terminate
506+
507+ runProcess localNode $ do
508+ ref <- monitor pid
509+ ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
510+ True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
511+ return ()
512+
513+testMonitorNode :: NT.Transport -> IO ()
514+testMonitorNode transport = do
515+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
516+
517+ closeLocalNode node1
518+
519+ runProcess node2 $ do
520+ ref <- monitorNode (localNodeId node1)
521+ NodeMonitorNotification ref' nid DiedDisconnect <- expect
522+ True <- return $ ref == ref' && nid == localNodeId node1
523+ return ()
524+
525+testMonitorChannel :: NT.Transport -> IO ()
526+testMonitorChannel transport = do
527+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
528+ gotNotification <- newEmptyMVar
529+
530+ pid <- forkProcess node1 $ do
531+ sport <- expect :: Process (SendPort ())
532+ ref <- monitorPort sport
533+ PortMonitorNotification ref' port' reason <- expect
534+ -- reason might be DiedUnknownId if the receive port is GCed before the
535+ -- monitor is established (TODO: not sure that this is reasonable)
536+ return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
537+ liftIO $ putMVar gotNotification ()
538+
539+ runProcess node2 $ do
540+ (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
541+ send pid sport
542+ liftIO $ threadDelay 100000
543+
544+ takeMVar gotNotification
545+
546+testRegistry :: NT.Transport -> IO ()
547+testRegistry transport = do
548+ node <- newLocalNode transport initRemoteTable
549+
550+ pingServer <- forkProcess node ping
551+
552+ runProcess node $ do
553+ register "ping" pingServer
554+ Just pid <- whereis "ping"
555+ True <- return $ pingServer == pid
556+ us <- getSelfPid
557+ nsend "ping" (Pong us)
558+ Ping pid' <- expect
559+ True <- return $ pingServer == pid'
560+ return ()
561+
562+testRemoteRegistry :: NT.Transport -> IO ()
563+testRemoteRegistry transport = do
564+ node1 <- newLocalNode transport initRemoteTable
565+ node2 <- newLocalNode transport initRemoteTable
566+
567+ pingServer <- forkProcess node1 ping
568+
569+ runProcess node2 $ do
570+ let nid1 = localNodeId node1
571+ registerRemote nid1 "ping" pingServer
572+ Just pid <- whereisRemote nid1 "ping"
573+ True <- return $ pingServer == pid
574+ us <- getSelfPid
575+ nsendRemote nid1 "ping" (Pong us)
576+ Ping pid' <- expect
577+ True <- return $ pingServer == pid'
578+ return ()
579+
580+testSpawnLocal :: NT.Transport -> IO ()
581+testSpawnLocal transport = do
582+ node <- newLocalNode transport initRemoteTable
583+
584+ runProcess node $ do
585+ us <- getSelfPid
586+
587+ pid <- spawnLocal $ do
588+ sport <- expect
589+ sendChan sport (1234 :: Int)
590+
591+ sport <- spawnChannelLocal $ \rport -> do
592+ (1234 :: Int) <- receiveChan rport
593+ send us ()
594+
595+ send pid sport
596+ expect
597+
598+testReconnect :: NT.Transport -> TransportInternals -> IO ()
599+testReconnect transport transportInternals = do
600+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
601+ let nid1 = localNodeId node1
602+ nid2 = localNodeId node2
603+ processA <- newEmptyMVar
604+ [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
605+
606+ forkProcess node1 $ do
607+ us <- getSelfPid
608+ liftIO $ putMVar processA us
609+ msg1 <- expect
610+ msg2 <- expect
611+ True <- return $ msg1 == "message 1" && msg2 == "message 3"
612+ liftIO $ putMVar sendTestOk ()
613+
614+ forkProcess node2 $ do
615+ {-
616+ - Make sure there is no implicit reconnect on normal message sending
617+ -}
618+
619+ them <- liftIO $ readMVar processA
620+ send them "message 1" >> liftIO (threadDelay 100000)
621+
622+ -- Simulate network failure
623+ liftIO $ do
624+ sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
625+ sClose sock
626+ threadDelay 10000
627+
628+ -- Should not arrive
629+ send them "message 2"
630+
631+ -- Should arrive
632+ reconnect them
633+ send them "message 3"
634+
635+ liftIO $ takeMVar sendTestOk
636+
637+ {-
638+ - Test that there *is* implicit reconnect on node controller messages
639+ -}
640+
641+ us <- getSelfPid
642+ registerRemote nid1 "a" us >> liftIO (threadDelay 100000) -- registerRemote is asynchronous
643+
644+ -- Simulate network failure
645+ liftIO $ do
646+ sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
647+ sClose sock
648+ threadDelay 10000
649+
650+ -- This will happen due to implicit reconnect
651+ registerRemote nid1 "b" us
652+
653+ -- Should happen
654+ registerRemote nid1 "c" us
655+
656+ -- Check
657+ Just _ <- whereisRemote nid1 "a"
658+ Just _ <- whereisRemote nid1 "b"
659+ Just _ <- whereisRemote nid1 "c"
660+
661+ liftIO $ putMVar registerTestOk ()
662+
663+ takeMVar registerTestOk
664+
665+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
666+-- in between
667+testMatchAny :: NT.Transport -> IO ()
668+testMatchAny transport = do
669+ proxyAddr <- newEmptyMVar
670+ clientDone <- newEmptyMVar
671+
672+ -- Math server
673+ forkIO $ do
674+ localNode <- newLocalNode transport initRemoteTable
675+ mathServer <- forkProcess localNode math
676+ proxyServer <- forkProcess localNode $ forever $ do
677+ msg <- receiveWait [ matchAny return ]
678+ forward msg mathServer
679+ putMVar proxyAddr proxyServer
680+
681+ -- Client
682+ forkIO $ do
683+ localNode <- newLocalNode transport initRemoteTable
684+ mathServer <- readMVar proxyAddr
685+
686+ runProcess localNode $ do
687+ pid <- getSelfPid
688+ send mathServer (Add pid 1 2)
689+ 3 <- expect :: Process Double
690+ send mathServer (Divide pid 8 2)
691+ 4 <- expect :: Process Double
692+ send mathServer (Divide pid 8 0)
693+ DivByZero <- expect
694+ liftIO $ putMVar clientDone ()
695+
696+ takeMVar clientDone
697+
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)
714+ -- Monitoring processes
715+ --
716+ -- The "missing" combinations in the list below don't make much sense, as
717+ -- we cannot guarantee that the monitor reply or link exception will not
718+ -- 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)
737+ -- Monitoring nodes and channels
738+ , ("MonitorNode", testMonitorNode transport)
739+ , ("MonitorChannel", testMonitorChannel transport)
740+ -- Reconnect
741+ , ("Reconnect", testReconnect transport transportInternals)
742+ ]
distributed-process/tests/TestClosure.hsnew file+448
@@ -0,0 +1,448 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+module Main where
3+
4+import Data.ByteString.Lazy (empty)
5+import Data.Typeable (Typeable)
6+import Control.Monad (join, replicateM, forever)
7+import Control.Exception (IOException, throw)
8+import Control.Concurrent (forkIO, threadDelay)
9+import Control.Concurrent.MVar
10+ ( MVar
11+ , newEmptyMVar
12+ , readMVar
13+ , takeMVar
14+ , putMVar
15+ , modifyMVar_
16+ , newMVar
17+ )
18+import Control.Applicative ((<$>))
19+import System.Random (randomIO)
20+import Network.Transport (Transport)
21+import Network.Transport.TCP
22+ ( createTransportExposeInternals
23+ , defaultTCPParameters
24+ , TransportInternals(socketBetween)
25+ )
26+import Network.Socket (sClose)
27+import Control.Distributed.Process
28+import Control.Distributed.Process.Closure
29+import Control.Distributed.Process.Node
30+import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress))
31+import Control.Distributed.Static (staticLabel, staticClosure)
32+import TestAuxiliary
33+
34+quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
35+quintuple a b c d e = (a, b, c, d, e)
36+
37+sdictInt :: SerializableDict Int
38+sdictInt = SerializableDict
39+
40+factorial :: Int -> Process Int
41+factorial 0 = return 1
42+factorial n = (n *) <$> factorial (n - 1)
43+
44+addInt :: Int -> Int -> Int
45+addInt x y = x + y
46+
47+putInt :: Int -> MVar Int -> IO ()
48+putInt = flip putMVar
49+
50+sendPid :: ProcessId -> Process ()
51+sendPid toPid = do
52+ fromPid <- getSelfPid
53+ send toPid fromPid
54+
55+wait :: Int -> Process ()
56+wait = liftIO . threadDelay
57+
58+isPrime :: Integer -> Process Bool
59+isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
60+ where
61+ sieve :: [Integer] -> [Integer]
62+ sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
63+ sieve [] = error "Uh oh -- we've run out of primes"
64+
65+-- | First argument indicates empty closure environment
66+typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
67+typedPingServer () rport = forever $ do
68+ sport <- receiveChan rport
69+ sendChan sport ()
70+
71+signal :: ProcessId -> Process ()
72+signal pid = send pid ()
73+
74+remotable [ 'factorial
75+ , 'addInt
76+ , 'putInt
77+ , 'sendPid
78+ , 'sdictInt
79+ , 'wait
80+ , 'typedPingServer
81+ , 'isPrime
82+ , 'quintuple
83+ , 'signal
84+ ]
85+
86+randomElement :: [a] -> IO a
87+randomElement xs = do
88+ ix <- randomIO
89+ return (xs !! (ix `mod` length xs))
90+
91+remotableDecl [
92+ [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
93+ dfib (_, reply, 0) = sendChan reply 0
94+ dfib (_, reply, 1) = sendChan reply 1
95+ dfib (nids, reply, n) = do
96+ nid1 <- liftIO $ randomElement nids
97+ nid2 <- liftIO $ randomElement nids
98+ (sport, rport) <- newChan
99+ spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
100+ spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
101+ n1 <- receiveChan rport
102+ n2 <- receiveChan rport
103+ sendChan reply $ n1 + n2
104+ |]
105+ ]
106+
107+-- Just try creating a static polymorphic value
108+staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
109+ => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
110+staticQuintuple = $(mkStatic 'quintuple)
111+
112+factorialClosure :: Int -> Closure (Process Int)
113+factorialClosure = $(mkClosure 'factorial)
114+
115+addIntClosure :: Int -> Closure (Int -> Int)
116+addIntClosure = $(mkClosure 'addInt)
117+
118+putIntClosure :: Int -> Closure (MVar Int -> IO ())
119+putIntClosure = $(mkClosure 'putInt)
120+
121+sendPidClosure :: ProcessId -> Closure (Process ())
122+sendPidClosure = $(mkClosure 'sendPid)
123+
124+sendFac :: Int -> ProcessId -> Closure (Process ())
125+sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
126+
127+factorialOf :: Closure (Int -> Process Int)
128+factorialOf = staticClosure $(mkStatic 'factorial)
129+
130+factorial' :: Int -> Closure (Process Int)
131+factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
132+
133+waitClosure :: Int -> Closure (Process ())
134+waitClosure = $(mkClosure 'wait)
135+
136+testUnclosure :: Transport -> RemoteTable -> IO ()
137+testUnclosure transport rtable = do
138+ node <- newLocalNode transport rtable
139+ done <- newEmptyMVar
140+ forkProcess node $ do
141+ 120 <- join . unClosure $ factorialClosure 5
142+ liftIO $ putMVar done ()
143+ takeMVar done
144+
145+testBind :: Transport -> RemoteTable -> IO ()
146+testBind transport rtable = do
147+ node <- newLocalNode transport rtable
148+ done <- newEmptyMVar
149+ runProcess node $ do
150+ us <- getSelfPid
151+ join . unClosure $ sendFac 6 us
152+ (720 :: Int) <- expect
153+ liftIO $ putMVar done ()
154+ takeMVar done
155+
156+testSendPureClosure :: Transport -> RemoteTable -> IO ()
157+testSendPureClosure transport rtable = do
158+ serverAddr <- newEmptyMVar
159+ serverDone <- newEmptyMVar
160+
161+ forkIO $ do
162+ node <- newLocalNode transport rtable
163+ addr <- forkProcess node $ do
164+ cl <- expect
165+ fn <- unClosure cl :: Process (Int -> Int)
166+ 13 <- return $ fn 6
167+ liftIO $ putMVar serverDone ()
168+ putMVar serverAddr addr
169+
170+ forkIO $ do
171+ node <- newLocalNode transport rtable
172+ theirAddr <- readMVar serverAddr
173+ runProcess node $ send theirAddr (addIntClosure 7)
174+
175+ takeMVar serverDone
176+
177+testSendIOClosure :: Transport -> RemoteTable -> IO ()
178+testSendIOClosure transport rtable = do
179+ serverAddr <- newEmptyMVar
180+ serverDone <- newEmptyMVar
181+
182+ forkIO $ do
183+ node <- newLocalNode transport rtable
184+ addr <- forkProcess node $ do
185+ cl <- expect
186+ io <- unClosure cl :: Process (MVar Int -> IO ())
187+ liftIO $ do
188+ someMVar <- newEmptyMVar
189+ io someMVar
190+ 5 <- readMVar someMVar
191+ putMVar serverDone ()
192+ putMVar serverAddr addr
193+
194+ forkIO $ do
195+ node <- newLocalNode transport rtable
196+ theirAddr <- readMVar serverAddr
197+ runProcess node $ send theirAddr (putIntClosure 5)
198+
199+ takeMVar serverDone
200+
201+testSendProcClosure :: Transport -> RemoteTable -> IO ()
202+testSendProcClosure transport rtable = do
203+ serverAddr <- newEmptyMVar
204+ clientDone <- newEmptyMVar
205+
206+ forkIO $ do
207+ node <- newLocalNode transport rtable
208+ addr <- forkProcess node $ do
209+ cl <- expect
210+ pr <- unClosure cl :: Process (Int -> Process ())
211+ pr 5
212+ putMVar serverAddr addr
213+
214+ forkIO $ do
215+ node <- newLocalNode transport rtable
216+ theirAddr <- readMVar serverAddr
217+ runProcess node $ do
218+ pid <- getSelfPid
219+ send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
220+ 5 <- expect :: Process Int
221+ liftIO $ putMVar clientDone ()
222+
223+ takeMVar clientDone
224+
225+testSpawn :: Transport -> RemoteTable -> IO ()
226+testSpawn transport rtable = do
227+ serverNodeAddr <- newEmptyMVar
228+ clientDone <- newEmptyMVar
229+
230+ forkIO $ do
231+ node <- newLocalNode transport rtable
232+ putMVar serverNodeAddr (localNodeId node)
233+
234+ forkIO $ do
235+ node <- newLocalNode transport rtable
236+ nid <- readMVar serverNodeAddr
237+ runProcess node $ do
238+ pid <- getSelfPid
239+ pid' <- spawn nid (sendPidClosure pid)
240+ pid'' <- expect
241+ True <- return $ pid' == pid''
242+ liftIO $ putMVar clientDone ()
243+
244+ takeMVar clientDone
245+
246+testCall :: Transport -> RemoteTable -> IO ()
247+testCall transport rtable = do
248+ serverNodeAddr <- newEmptyMVar
249+ clientDone <- newEmptyMVar
250+
251+ forkIO $ do
252+ node <- newLocalNode transport rtable
253+ putMVar serverNodeAddr (localNodeId node)
254+
255+ forkIO $ do
256+ node <- newLocalNode transport rtable
257+ nid <- readMVar serverNodeAddr
258+ runProcess node $ do
259+ (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
260+ liftIO $ putMVar clientDone ()
261+
262+ takeMVar clientDone
263+
264+testCallBind :: Transport -> RemoteTable -> IO ()
265+testCallBind transport rtable = do
266+ serverNodeAddr <- newEmptyMVar
267+ clientDone <- newEmptyMVar
268+
269+ forkIO $ do
270+ node <- newLocalNode transport rtable
271+ putMVar serverNodeAddr (localNodeId node)
272+
273+ forkIO $ do
274+ node <- newLocalNode transport rtable
275+ nid <- readMVar serverNodeAddr
276+ runProcess node $ do
277+ (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
278+ liftIO $ putMVar clientDone ()
279+
280+ takeMVar clientDone
281+
282+testSeq :: Transport -> RemoteTable -> IO ()
283+testSeq transport rtable = do
284+ node <- newLocalNode transport rtable
285+ done <- newEmptyMVar
286+ runProcess node $ do
287+ us <- getSelfPid
288+ join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
289+ 120 :: Int <- expect
290+ 720 :: Int <- expect
291+ liftIO $ putMVar done ()
292+ takeMVar done
293+
294+-- Test 'spawnSupervised'
295+--
296+-- Set up a supervisor, spawn a child, then have a third process monitor the
297+-- child. The supervisor then throws an exception, the child dies because it
298+-- was linked to the supervisor, and the third process notices that the child
299+-- dies.
300+testSpawnSupervised :: Transport -> RemoteTable -> IO ()
301+testSpawnSupervised transport rtable = do
302+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
303+ [superPid, childPid] <- replicateM 2 $ newEmptyMVar
304+ thirdProcessDone <- newEmptyMVar
305+
306+ forkProcess node1 $ do
307+ us <- getSelfPid
308+ liftIO $ putMVar superPid us
309+ (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)
310+ liftIO $ do
311+ putMVar childPid child
312+ threadDelay 500000 -- Give the child a chance to link to us
313+ throw supervisorDeath
314+
315+ forkProcess node2 $ do
316+ [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
317+ ref <- monitor child
318+ ProcessMonitorNotification ref' pid' (DiedException e) <- expect
319+ True <- return $ ref' == ref
320+ && pid' == child
321+ && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
322+ liftIO $ putMVar thirdProcessDone ()
323+
324+ takeMVar thirdProcessDone
325+ where
326+ supervisorDeath :: IOException
327+ supervisorDeath = userError "Supervisor died"
328+
329+testSpawnInvalid :: Transport -> RemoteTable -> IO ()
330+testSpawnInvalid transport rtable = do
331+ node <- newLocalNode transport rtable
332+ done <- newEmptyMVar
333+ forkProcess node $ do
334+ (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
335+ ProcessMonitorNotification ref' pid' _reason <- expect
336+ -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
337+ True <- return $ ref' == ref && pid == pid'
338+ liftIO $ putMVar done ()
339+ takeMVar done
340+
341+testClosureExpect :: Transport -> RemoteTable -> IO ()
342+testClosureExpect transport rtable = do
343+ node <- newLocalNode transport rtable
344+ done <- newEmptyMVar
345+ runProcess node $ do
346+ nodeId <- getSelfNode
347+ us <- getSelfPid
348+ them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
349+ send them (1234 :: Int)
350+ (1234 :: Int) <- expect
351+ liftIO $ putMVar done ()
352+ takeMVar done
353+
354+testSpawnChannel :: Transport -> RemoteTable -> IO ()
355+testSpawnChannel transport rtable = do
356+ done <- newEmptyMVar
357+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
358+
359+ forkProcess node1 $ do
360+ pingServer <- spawnChannel
361+ (sdictSendPort sdictUnit)
362+ (localNodeId node2)
363+ ($(mkClosure 'typedPingServer) ())
364+ (sendReply, receiveReply) <- newChan
365+ sendChan pingServer sendReply
366+ receiveChan receiveReply
367+ liftIO $ putMVar done ()
368+
369+ takeMVar done
370+
371+testTDict :: Transport -> RemoteTable -> IO ()
372+testTDict transport rtable = do
373+ done <- newEmptyMVar
374+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
375+ forkProcess node1 $ do
376+ True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
377+ liftIO $ putMVar done ()
378+ takeMVar done
379+
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 ()
388+testFib transport rtable = do
389+ nodes <- replicateM 4 $ newLocalNode transport rtable
390+ done <- newEmptyMVar
391+
392+ forkProcess (head nodes) $ do
393+ (sport, rport) <- newChan
394+ spawnLocal $ dfib (map localNodeId nodes, sport, 10)
395+ 55 <- receiveChan rport :: Process Integer
396+ liftIO $ putMVar done ()
397+
398+ takeMVar done
399+
400+testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> IO ()
401+testSpawnReconnect transport rtable transportInternals = do
402+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
403+ let nid1 = localNodeId node1
404+ nid2 = localNodeId node2
405+ done <- newEmptyMVar
406+ iv <- newMVar (0 :: Int)
407+
408+ incr <- forkProcess node1 $ forever $ do
409+ () <- expect
410+ liftIO $ modifyMVar_ iv (return . (+ 1))
411+
412+ forkProcess node2 $ do
413+ _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
414+ simulateNetworkFailure transportInternals nid2 nid1
415+ _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
416+ _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
417+
418+ liftIO $ threadDelay 100000
419+
420+ count <- liftIO $ takeMVar iv
421+ True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
422+
423+ liftIO $ putMVar done ()
424+
425+ takeMVar done
426+
427+main :: IO ()
428+main = do
429+ Right (transport, transportInternals) <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
430+ let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
431+ runTests
432+ [ ("Unclosure", testUnclosure transport rtable)
433+ , ("Bind", testBind transport rtable)
434+ , ("SendPureClosure", testSendPureClosure transport rtable)
435+ , ("SendIOClosure", testSendIOClosure transport rtable)
436+ , ("SendProcClosure", testSendProcClosure transport rtable)
437+ , ("Spawn", testSpawn transport rtable)
438+ , ("Call", testCall transport rtable)
439+ , ("CallBind", testCallBind transport rtable)
440+ , ("Seq", testSeq transport rtable)
441+ , ("SpawnSupervised", testSpawnSupervised transport rtable)
442+ , ("SpawnInvalid", testSpawnInvalid transport rtable)
443+ , ("ClosureExpect", testClosureExpect transport rtable)
444+ , ("SpawnChannel", testSpawnChannel transport rtable)
445+ , ("TDict", testTDict transport rtable)
446+ , ("Fib", testFib transport rtable)
447+ , ("SpawnReconnect", testSpawnReconnect transport rtable transportInternals)
448+ ]
distributed-static/ChangeLognew file+12
@@ -0,0 +1,12 @@
1+<<date>> Edsko de Vries <edsko@well-typed.com> 0.2.1
2+
3+* Add support for 'staticFlip'
4+
5+2012-08-16 Edsko de Vries <edsko@well-typed.com> 0.2
6+
7+* Hide the 'Closure' constructor and export 'closure' instead so that we are
8+free to change the internal representation
9+
10+2012-08-10 Edsko de Vries <edsko@well-typed.com> 0.1
11+
12+* Initial release
distributed-static/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2012, Edsko de Vries
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Edsko de Vries nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
distributed-static/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-static/distributed-static.cabalnew file+37
@@ -0,0 +1,37 @@
1+Name: distributed-static
2+Version: 0.2.1
3+Synopsis: Compositional, type-safe, polymorphic static values and closures
4+Description: /Towards Haskell in the Cloud/ (Epstein et al, Haskell
5+ Symposium 2011) introduces the concept of /static/ values:
6+ values that are known at compile time. In a distributed
7+ setting where all nodes are running the same executable,
8+ static values can be serialized simply by transmitting a
9+ code pointer to the value. This however requires special
10+ compiler support, which is not yet available in ghc. We
11+ can mimick the behaviour by keeping an explicit mapping
12+ ('RemoteTable') from labels to values (and making sure
13+ that all distributed nodes are using the same
14+ 'RemoteTable'). In this module we implement this mimickry
15+ and various extensions: type safety (including for
16+ polymorphic static values) and compositionality.
17+Homepage: http://www.github.com/haskell-distributed/distributed-process
18+License: BSD3
19+License-File: LICENSE
20+Author: Edsko de Vries
21+Maintainer: edsko@well-typed.com
22+Copyright: Well-Typed LLP
23+Category: Control
24+Build-Type: Simple
25+Cabal-Version: >=1.8
26+
27+Library
28+ Exposed-Modules: Control.Distributed.Static
29+ Build-Depends: base >= 4 && < 5,
30+ rank1dynamic >= 0.1 && < 0.2,
31+ containers >= 0.4 && < 0.6,
32+ bytestring >= 0.9 && < 0.11,
33+ binary >= 0.5 && < 0.6
34+ HS-Source-Dirs: src
35+ Extensions: DeriveDataTypeable,
36+ ScopedTypeVariables
37+ GHC-Options: -Wall
distributed-static/src/Control/Distributed/Static.hsnew file+453
@@ -0,0 +1,453 @@
1+-- | /Towards Haskell in the Cloud/ (Epstein et al, Haskell Symposium 2011)
2+-- introduces the concept of /static/ values: values that are known at compile
3+-- time. In a distributed setting where all nodes are running the same
4+-- executable, static values can be serialized simply by transmitting a code
5+-- pointer to the value. This however requires special compiler support, which
6+-- is not yet available in ghc. We can mimick the behaviour by keeping an
7+-- explicit mapping ('RemoteTable') from labels to values (and making sure that
8+-- all distributed nodes are using the same 'RemoteTable'). In this module
9+-- we implement this mimickry and various extensions.
10+--
11+-- [Dynamic type checking]
12+--
13+-- The paper stipulates that 'Static' values should have a free 'Binary'
14+-- instance:
15+--
16+-- > instance Binary (Static a)
17+--
18+-- This however is not (runtime) type safe: for instance, what would be the
19+-- behaviour of
20+--
21+-- > f :: Static Int -> Static Bool
22+-- > f = decode . encode
23+--
24+-- For this reason we work only with 'Typeable' terms in this module, and
25+-- implement runtime checks
26+--
27+-- > instance Typeable a => Binary (Static a)
28+--
29+-- The above function 'f' typechecks but throws an exception if executed. The
30+-- type representation we use, however, is not the standard
31+-- 'Data.Typeable.TypeRep' from "Data.Typeable" but
32+-- 'Data.Rank1Typeable.TypeRep' from "Data.Rank1Typeable". This means that we
33+-- can represent polymorphic static values (see below for an example).
34+--
35+-- Since the runtime mapping ('RemoteTable') contains values of different types,
36+-- it maps labels ('String's) to 'Data.Rank1Dynamic.Dynamic' values. Again, we
37+-- use the implementation from "Data.Rank1Dynamic" so that we can store
38+-- polymorphic dynamic values.
39+--
40+-- [Compositionality]
41+--
42+-- Static values as described in the paper are not compositional: there is no
43+-- way to combine two static values and get a static value out of it. This
44+-- makes sense when interpreting static strictly as /known at compile time/,
45+-- but it severely limits expressiveness. However, the main motivation for
46+-- 'static' is not that they are known at compile time but rather that
47+-- /they provide a free/ 'Binary' /instance/. We therefore provide two basic
48+-- constructors for 'Static' values:
49+--
50+-- > staticLabel :: String -> Static a
51+-- > staticApply :: Static (a -> b) -> Static a -> Static b
52+--
53+-- The first constructor refers to a label in a 'RemoteTable'. The second
54+-- allows to apply a static function to a static argument, and makes 'Static'
55+-- compositional: once we have 'staticApply' we can implement numerous derived
56+-- combinators on 'Static' values (we define a few in this module; see
57+-- 'staticCompose', 'staticSplit', and 'staticConst').
58+--
59+-- [Closures]
60+--
61+-- Closures in functional programming arise when we partially apply a function.
62+-- A closure is a code pointer together with a runtime data structure that
63+-- represents the value of the free variables of the function. A 'Closure'
64+-- represents these closures explicitly so that they can be serialized:
65+--
66+-- > data Closure a = Closure (Static (ByteString -> a)) ByteString
67+--
68+-- See /Towards Haskell in the Cloud/ for the rationale behind representing
69+-- the function closure environment in serialized ('ByteString') form. Any
70+-- static value can trivially be turned into a 'Closure' ('staticClosure').
71+-- Moreover, since 'Static' is now compositional, we can also define derived
72+-- operators on 'Closure' values ('closureApplyStatic', 'closureApply',
73+-- 'closureCompose', 'closureSplit').
74+--
75+-- [Monomorphic example]
76+--
77+-- Suppose we are working in the context of some distributed environment, with
78+-- a monadic type 'Process' representing processes, 'NodeId' representing node
79+-- addresses and 'ProcessId' representing process addresses. Suppose further
80+-- that we have a primitive
81+--
82+-- > sendInt :: ProcessId -> Int -> Process ()
83+--
84+-- We might want to define
85+--
86+-- > sendIntClosure :: ProcessId -> Closure (Int -> Process ())
87+--
88+-- In order to do that, we need a static version of 'send', and a static
89+-- decoder for 'ProcessId':
90+--
91+-- > sendIntStatic :: Static (ProcessId -> Int -> Process ())
92+-- > sendIntStatic = staticLabel "$send"
93+--
94+-- > decodeProcessIdStatic :: Static (ByteString -> Int)
95+-- > decodeProcessIdStatic = staticLabel "$decodeProcessId"
96+--
97+-- where of course we have to make sure to use an appropriate 'RemoteTable':
98+--
99+-- > rtable :: RemoteTable
100+-- > rtable = registerStatic "$send" (toDynamic sendInt)
101+-- > . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> Int))
102+-- > $ initRemoteTable
103+--
104+-- We can now define 'sendIntClosure':
105+--
106+-- > sendIntClosure :: ProcessId -> Closure (Int -> Process ())
107+-- > sendIntClosure pid = closure decoder (encode pid)
108+-- > where
109+-- > decoder :: Static (ByteString -> Int -> Process ())
110+-- > decoder = sendIntStatic `staticCompose` decodeProcessIdStatic
111+--
112+-- [Polymorphic example]
113+--
114+-- Suppose we wanted to define a primitive
115+--
116+-- > sendIntResult :: ProcessId -> Closure (Process Int) -> Closure (Process ())
117+--
118+-- which turns a process that computes an integer into a process that computes
119+-- the integer and then sends it someplace else.
120+--
121+-- We can define
122+--
123+-- > bindStatic :: (Typeable a, Typeable b) => Static (Process a -> (a -> Process b) -> Process b)
124+-- > bindStatic = staticLabel "$bind"
125+--
126+-- provided that we register this label:
127+--
128+-- > rtable :: RemoteTable
129+-- > rtable = ...
130+-- > . registerStatic "$bind" ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2)
131+-- > $ initRemoteTable
132+--
133+-- (Note that we are using the special 'Data.Rank1Typeable.ANY1' and
134+-- 'Data.Rank1Typeable.ANY2' types from "Data.Rank1Typeable" to represent this
135+-- polymorphic value.) Once we have a static bind we can define
136+--
137+-- > sendIntResult :: ProcessId -> Closure (Process Int) -> Closure (Process ())
138+-- > sendIntResult pid cl = bindStatic `closureApplyStatic` cl `closureApply` sendIntClosure pid
139+--
140+-- [Dealing with qualified types]
141+--
142+-- In the above we were careful to avoid qualified types. Suppose that we have
143+-- instead
144+--
145+-- > send :: Binary a => ProcessId -> a -> Process ()
146+--
147+-- If we now want to define 'sendClosure', analogous to 'sendIntClosure' above,
148+-- we somehow need to include the 'Binary' instance in the closure -- after
149+-- all, we can ship this closure someplace else, where it needs to accept an
150+-- 'a', /then encode it/, and send it off. In order to do this, we need to turn
151+-- the Binary instance into an explicit dictionary:
152+--
153+-- > data BinaryDict a where
154+-- > BinaryDict :: Binary a => BinaryDict a
155+-- >
156+-- > sendDict :: BinaryDict a -> ProcessId -> a -> Process ()
157+-- > sendDict BinaryDict = send
158+--
159+-- Now 'sendDict' is a normal polymorphic value:
160+--
161+-- > sendDictStatic :: Static (BinaryDict a -> ProcessId -> a -> Process ())
162+-- > sendDictStatic = staticLabel "$sendDict"
163+-- >
164+-- > rtable :: RemoteTable
165+-- > rtable = ...
166+-- > . registerStatic "$sendDict" (sendDict :: BinaryDict ANY -> ProcessId -> ANY -> Process ())
167+-- > $ initRemoteTable
168+--
169+-- so that we can define
170+--
171+-- > sendClosure :: Static (BinaryDict a) -> Process a -> Closure (a -> Process ())
172+-- > sendClosure dict pid = closure decoder (encode pid)
173+-- > where
174+-- > decoder :: Static (ByteString -> a -> Process ())
175+-- > decoder = (sendDictStatic `staticApply` dict) `staticCompose` decodeProcessIdStatic
176+--
177+-- [Word of Caution]
178+--
179+-- You should not /define/ functions on 'ANY' and co. For example, the following
180+-- definition of 'rtable' is incorrect:
181+--
182+-- > rtable :: RemoteTable
183+-- > rtable = registerStatic "$sdictSendPort" sdictSendPort
184+-- > $ initRemoteTable
185+-- > where
186+-- > sdictSendPort :: SerializableDict ANY -> SerializableDict (SendPort ANY)
187+-- > sdictSendPort SerializableDict = SerializableDict
188+--
189+-- This definition of 'sdictSendPort' ignores its argument completely, and
190+-- constructs a 'SerializableDict' for the /monomorphic/ type @SendPort ANY@,
191+-- which isn't what you want. Instead, you should do
192+--
193+-- > rtable :: RemoteTable
194+-- > rtable = registerStatic "$sdictSendPort" (sdictSendPort :: SerializableDict ANY -> SerializableDict (SendPort ANY))
195+-- > $ initRemoteTable
196+-- > where
197+-- > sdictSendPort :: forall a. SerializableDict a -> SerializableDict (SendPort a)
198+-- > sdictSendPort SerializableDict = SerializableDict
199+module Control.Distributed.Static
200+ ( -- * Static values
201+ Static
202+ , staticLabel
203+ , staticApply
204+ -- * Derived static combinators
205+ , staticCompose
206+ , staticSplit
207+ , staticConst
208+ , staticFlip
209+ -- * Closures
210+ , Closure
211+ , closure
212+ -- * Derived closure combinators
213+ , staticClosure
214+ , closureApplyStatic
215+ , closureApply
216+ , closureCompose
217+ , closureSplit
218+ -- * Resolution
219+ , RemoteTable
220+ , initRemoteTable
221+ , registerStatic
222+ , unstatic
223+ , unclosure
224+ ) where
225+
226+import Data.Binary
227+ ( Binary(get, put)
228+ , Put
229+ , Get
230+ , putWord8
231+ , getWord8
232+ , encode
233+ , decode
234+ )
235+import Data.ByteString.Lazy (ByteString, empty)
236+import Data.Map (Map)
237+import qualified Data.Map as Map (lookup, empty, insert)
238+import Control.Applicative ((<$>), (<*>))
239+import Control.Arrow as Arrow ((***), app)
240+import Data.Rank1Dynamic (Dynamic, toDynamic, fromDynamic, dynApply)
241+import Data.Rank1Typeable
242+ ( Typeable
243+ , typeOf
244+ , ANY1
245+ , ANY2
246+ , ANY3
247+ , ANY4
248+ , isInstanceOf
249+ )
250+
251+--------------------------------------------------------------------------------
252+-- Introducing static values --
253+--------------------------------------------------------------------------------
254+
255+data StaticLabel =
256+ StaticLabel String
257+ | StaticApply StaticLabel StaticLabel
258+ deriving (Typeable, Show)
259+
260+-- | A static value. Static is opaque; see 'staticLabel' and 'staticApply'.
261+newtype Static a = Static StaticLabel
262+ deriving (Typeable, Show)
263+
264+instance Typeable a => Binary (Static a) where
265+ put (Static label) = putStaticLabel label >> put (typeOf (undefined :: a))
266+ get = do
267+ label <- getStaticLabel
268+ typeRep <- get
269+ case typeOf (undefined :: a) `isInstanceOf` typeRep of
270+ Left err -> fail $ "Static.get: type error: " ++ err
271+ Right () -> return (Static label)
272+
273+-- We don't want StaticLabel to be its own Binary instance
274+putStaticLabel :: StaticLabel -> Put
275+putStaticLabel (StaticLabel string) =
276+ putWord8 0 >> put string
277+putStaticLabel (StaticApply label1 label2) =
278+ putWord8 1 >> putStaticLabel label1 >> putStaticLabel label2
279+
280+getStaticLabel :: Get StaticLabel
281+getStaticLabel = do
282+ header <- getWord8
283+ case header of
284+ 0 -> StaticLabel <$> get
285+ 1 -> StaticApply <$> getStaticLabel <*> getStaticLabel
286+ _ -> fail "StaticLabel.get: invalid"
287+
288+-- | Create a primitive static value.
289+--
290+-- It is the responsibility of the client code to make sure the corresponding
291+-- entry in the 'RemoteTable' has the appropriate type.
292+staticLabel :: String -> Static a
293+staticLabel = Static . StaticLabel
294+
295+-- | Apply two static values
296+staticApply :: Static (a -> b) -> Static a -> Static b
297+staticApply (Static f) (Static x) = Static (StaticApply f x)
298+
299+--------------------------------------------------------------------------------
300+-- Eliminating static values --
301+--------------------------------------------------------------------------------
302+
303+-- | Runtime dictionary for 'unstatic' lookups
304+newtype RemoteTable = RemoteTable (Map String Dynamic)
305+
306+-- | Initial remote table
307+initRemoteTable :: RemoteTable
308+initRemoteTable =
309+ registerStatic "$compose" (toDynamic ((.) :: (ANY2 -> ANY3) -> (ANY1 -> ANY2) -> ANY1 -> ANY3))
310+ . registerStatic "$const" (toDynamic (const :: ANY1 -> ANY2 -> ANY1))
311+ . registerStatic "$split" (toDynamic ((***) :: (ANY1 -> ANY3) -> (ANY2 -> ANY4) -> (ANY1, ANY2) -> (ANY3, ANY4)))
312+ . registerStatic "$app" (toDynamic (app :: (ANY1 -> ANY2, ANY1) -> ANY2))
313+ . registerStatic "$decodeEnvPair" (toDynamic (decode :: ByteString -> (ByteString, ByteString)))
314+ . registerStatic "$flip" (toDynamic (flip :: (ANY1 -> ANY2 -> ANY3) -> ANY2 -> ANY1 -> ANY3))
315+ $ RemoteTable Map.empty
316+
317+-- | Register a static label
318+registerStatic :: String -> Dynamic -> RemoteTable -> RemoteTable
319+registerStatic label dyn (RemoteTable rtable)
320+ = RemoteTable (Map.insert label dyn rtable)
321+
322+-- Pseudo-type: RemoteTable -> Static a -> a
323+resolveStaticLabel :: RemoteTable -> StaticLabel -> Either String Dynamic
324+resolveStaticLabel (RemoteTable rtable) (StaticLabel label) =
325+ case Map.lookup label rtable of
326+ Nothing -> Left $ "Invalid static label '" ++ label ++ "'"
327+ Just d -> Right d
328+resolveStaticLabel rtable (StaticApply label1 label2) = do
329+ f <- resolveStaticLabel rtable label1
330+ x <- resolveStaticLabel rtable label2
331+ f `dynApply` x
332+
333+-- | Resolve a static value
334+unstatic :: Typeable a => RemoteTable -> Static a -> Either String a
335+unstatic rtable (Static static) = do
336+ dyn <- resolveStaticLabel rtable static
337+ fromDynamic dyn
338+
339+--------------------------------------------------------------------------------
340+-- Closures --
341+--------------------------------------------------------------------------------
342+
343+-- | A closure is a static value and an encoded environment
344+data Closure a = Closure (Static (ByteString -> a)) ByteString
345+ deriving (Typeable, Show)
346+
347+instance Typeable a => Binary (Closure a) where
348+ put (Closure static env) = put static >> put env
349+ get = Closure <$> get <*> get
350+
351+closure :: Static (ByteString -> a) -- ^ Decoder
352+ -> ByteString -- ^ Encoded closure environment
353+ -> Closure a
354+closure = Closure
355+
356+-- | Resolve a closure
357+unclosure :: Typeable a => RemoteTable -> Closure a -> Either String a
358+unclosure rtable (Closure static env) = do
359+ f <- unstatic rtable static
360+ return (f env)
361+
362+-- | Convert a static value into a closure.
363+staticClosure :: Typeable a => Static a -> Closure a
364+staticClosure static = closure (staticConst static) empty
365+
366+--------------------------------------------------------------------------------
367+-- Predefined static values --
368+--------------------------------------------------------------------------------
369+
370+-- | Static version of ('Prelude..')
371+composeStatic :: (Typeable a, Typeable b, Typeable c)
372+ => Static ((b -> c) -> (a -> b) -> a -> c)
373+composeStatic = staticLabel "$compose"
374+
375+-- | Static version of 'const'
376+constStatic :: (Typeable a, Typeable b)
377+ => Static (a -> b -> a)
378+constStatic = staticLabel "$const"
379+
380+-- | Static version of ('Arrow.***')
381+splitStatic :: (Typeable a, Typeable a', Typeable b, Typeable b')
382+ => Static ((a -> b) -> (a' -> b') -> (a, a') -> (b, b'))
383+splitStatic = staticLabel "$split"
384+
385+-- | Static version of 'Arrow.app'
386+appStatic :: (Typeable a, Typeable b)
387+ => Static ((a -> b, a) -> b)
388+appStatic = staticLabel "$app"
389+
390+-- | Static version of 'flip'
391+flipStatic :: (Typeable a, Typeable b, Typeable c)
392+ => Static ((a -> b -> c) -> b -> a -> c)
393+flipStatic = staticLabel "$flip"
394+
395+--------------------------------------------------------------------------------
396+-- Combinators on static values --
397+--------------------------------------------------------------------------------
398+
399+-- | Static version of ('Prelude..')
400+staticCompose :: (Typeable a, Typeable b, Typeable c)
401+ => Static (b -> c) -> Static (a -> b) -> Static (a -> c)
402+staticCompose g f = composeStatic `staticApply` g `staticApply` f
403+
404+-- | Static version of ('Control.Arrow.***')
405+staticSplit :: (Typeable a, Typeable a', Typeable b, Typeable b')
406+ => Static (a -> b) -> Static (a' -> b') -> Static ((a, a') -> (b, b'))
407+staticSplit f g = splitStatic `staticApply` f `staticApply` g
408+
409+-- | Static version of 'Prelude.const'
410+staticConst :: (Typeable a, Typeable b)
411+ => Static a -> Static (b -> a)
412+staticConst x = constStatic `staticApply` x
413+
414+-- | Static version of 'Prelude.flip'
415+staticFlip :: (Typeable a, Typeable b, Typeable c)
416+ => Static (a -> b -> c) -> Static (b -> a -> c)
417+staticFlip f = flipStatic `staticApply` f
418+
419+--------------------------------------------------------------------------------
420+-- Combinators on Closures --
421+--------------------------------------------------------------------------------
422+
423+-- | Apply a static function to a closure
424+closureApplyStatic :: (Typeable a, Typeable b)
425+ => Static (a -> b) -> Closure a -> Closure b
426+closureApplyStatic f (Closure decoder env) =
427+ closure (f `staticCompose` decoder) env
428+
429+decodeEnvPairStatic :: Static (ByteString -> (ByteString, ByteString))
430+decodeEnvPairStatic = staticLabel "$decodeEnvPair"
431+
432+-- | Closure application
433+closureApply :: forall a b. (Typeable a, Typeable b)
434+ => Closure (a -> b) -> Closure a -> Closure b
435+closureApply (Closure fdec fenv) (Closure xdec xenv) =
436+ closure decoder (encode (fenv, xenv))
437+ where
438+ decoder :: Static (ByteString -> b)
439+ decoder = appStatic
440+ `staticCompose`
441+ (fdec `staticSplit` xdec)
442+ `staticCompose`
443+ decodeEnvPairStatic
444+
445+-- | Closure composition
446+closureCompose :: (Typeable a, Typeable b, Typeable c)
447+ => Closure (b -> c) -> Closure (a -> b) -> Closure (a -> c)
448+closureCompose g f = composeStatic `closureApplyStatic` g `closureApply` f
449+
450+-- | Closure version of ('Arrow.***')
451+closureSplit :: (Typeable a, Typeable a', Typeable b, Typeable b')
452+ => Closure (a -> b) -> Closure (a' -> b') -> Closure ((a, a') -> (b, b'))
453+closureSplit f g = splitStatic `closureApplyStatic` f `closureApply` g
doc/InternalsTransportMulticast.txtnew file+181
@@ -0,0 +1,181 @@
1+Adding Support for Multicast to the Transport API
2+=================================================
3+
4+This document describes various design options for adding support for multicast
5+to the Transport API.
6+
7+Creating a new multicast group
8+------------------------------
9+
10+We can either have this as part of the transport
11+
12+ data Transport = Transport {
13+ ...
14+ , newMulticastGroup :: IO (Either Error MulticastGroup)
15+ }
16+
17+ data MulticastGroup = MulticastGroup {
18+ ...
19+ , multicastAddress :: MulticastAddress
20+ , deleteMulticastGroup :: IO ()
21+ }
22+
23+or as part of an endpoint:
24+
25+ data Transport = Transport {
26+ newEndPoint :: IO (Either Error EndPoint)
27+ }
28+
29+ data EndPoint = EndPoint {
30+ ...
31+ , newMulticastGroup :: IO (Either Error MulticastGroup)
32+ }
33+
34+It should probably be part of the `Transport`, as there is no real connection
35+between an endpoint and the creation of the multigroup (however, see section
36+"Sending messages to a multicast group").
37+
38+Subscribing to a multicast group
39+--------------------------------
40+
41+This should be part of an endpoint; subscribing basically means that the
42+endpoint wants to receive events when multicast messages are sent.
43+
44+We could reify a subscription:
45+
46+ data EndPoint = EndPoint {
47+ ...
48+ , multicastSubscribe :: MulticastAddress -> IO MulticastSubscription
49+ }
50+
51+ data MulticastSubscription = MulticastSubscription {
52+ ...
53+ , multicastSubscriptionClose :: IO ()
54+ }
55+
56+but this suggests that one might have multiple subscriptions to the same group
57+which can be distinguished, which is misleading. Probably better to have:
58+
59+ data EndPoint = EndPoint {
60+ multicastSubscribe :: MulticastAddress -> IO ()
61+ , multicastUnsubscribe :: MulticastAddress -> IO ()
62+ }
63+
64+Sending messages to a multicast group
65+-------------------------------------
66+
67+An important feature of the Transport API is that we are clear about which
68+operations are *lightweight* and which are not. For instance, creating new
69+endpoints is not lightweight, but opening new connections to endpoints is (as
70+light-weight as possible).
71+
72+Clearly the creation of a new multicast group is a heavyweight operation. It is
73+less evident however if we can support multiple lightweight "connections" to the
74+same multicast group, and if so, whether it is useful.
75+
76+If we decide that multiple lightweight connections to the multigroup is useful,
77+one option might be
78+
79+ data EndPoint = EndPoint {
80+ ...
81+ , connect :: Address -> Reliability -> IO (Either Error Connection)
82+ , multicastConnect :: MulticastAddress -> IO (Either Error Connection)
83+ }
84+
85+ data Connection = Connection {
86+ connectionId :: ConnectionId
87+ , send :: [ByteString] -> IO ()
88+ , close :: IO ()
89+ , maxMsgSize :: Maybe Int
90+ }
91+
92+ data Event =
93+ Receive ConnectionId [ByteString]
94+ | ConnectionClosed ConnectionId
95+ | ConnectionOpened ConnectionId ConnectionType Reliability Address
96+
97+The advantage of this approach is it's consistency with the rest of the
98+interface. The problem is that with multicast we cannot reliably send any
99+control messages, so we cannot make sure that the subscribers of the multicast
100+group will receive ConnectionOpened events when an endpoint creates a new
101+connection. Since we don't support these "connectionless connections" anywhere
102+else in the API this seems inconsistent with the rest of the design (this
103+implies that an "unreliable" Transport over UDP still needs to have reliable
104+control messages). (On the other hand, if we were going to support reliable
105+multicast protocols, then that would fit this design).
106+
107+If we don't want to support multiple lightweight connections to a multicast
108+group then a better design would be
109+
110+ data EndPoint = EndPoint {
111+ , connect :: Address -> Reliability -> IO (Either Error Connection)
112+ , multicastSend :: MulticastAddress -> [ByteString] -> IO ()
113+ }
114+
115+ data Event =
116+ ...
117+ | MulticastReceive Address [ByteString]
118+
119+or alternatively
120+
121+ data EndPoint = EndPoint {
122+ ...
123+ , resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup)
124+ }
125+
126+ data MulticastGroup = MulticastGroup {
127+ , ...
128+ , send :: [ByteString] -> IO ()
129+ }
130+
131+If we do this however we need to make sure that newGroup is part an `EndPoint`,
132+not the `Transport`, otherwise `send` will not know the source of the message.
133+The version with `resolveMulticastGroup` has the additional benefit that in
134+"real" implementations we will probably need to allocate some resources before
135+we can send to the multicast group, and need to deallocate these resources at
136+some point too.
137+
138+The current solution
139+--------------------
140+
141+The above considerations lead to the following tentative proposal:
142+
143+ data Transport = Transport {
144+ newEndPoint :: IO (Either Error EndPoint)
145+ }
146+
147+ data EndPoint = EndPoint {
148+ receive :: IO Event
149+ , address :: Address
150+ , connect :: Address -> Reliability -> IO (Either Error Connection)
151+ , newMulticastGroup :: IO (Either Error MulticastGroup)
152+ , resolveMulticastGroup :: MulticastAddress -> IO (Either Error MulticastGroup)
153+ }
154+
155+ data Connection = Connection {
156+ send :: [ByteString] -> IO ()
157+ , close :: IO ()
158+ }
159+
160+ data Event =
161+ Receive ConnectionId [ByteString]
162+ | ConnectionClosed ConnectionId
163+ | ConnectionOpened ConnectionId Reliability Address
164+ | MulticastReceive MulticastAddress [ByteString]
165+
166+ data MulticastGroup = MulticastGroup {
167+ multicastAddress :: MulticastAddress
168+ , deleteMulticastGroup :: IO ()
169+ , maxMsgSize :: Maybe Int
170+ , multicastSend :: [ByteString] -> IO ()
171+ , multicastSubscribe :: IO ()
172+ , multicastUnsubscribe :: IO ()
173+ , multicastClose :: IO ()
174+ }
175+
176+where `multicastClose` indicates to the runtime that this endpoint no longer
177+wishes to send to this multicast group, and we can therefore deallocate the
178+resources we needed to send to the group (these resources can be allocated on
179+`resolveMulticastGroup` or on the first `multicastSend`; the advantage of the
180+latter is that is somebody resolves a group only to subscribe to it, not to
181+send to it, we don't allocate any unneeded resources).
doc/InternalsTransportTCP.txtnew file+81
@@ -0,0 +1,81 @@
1+The TCP Transport
2+=================
3+
4+Overview
5+--------
6+
7+When a TCP transport is created a new server is started which listens on a
8+given port number on the local host. In order to support multiple connections
9+the transport maintains a set of channels, one per connection, represented as a
10+pair of a `MVar [ByteString]` and a list of pairs `(ThreadId, Socket)` of
11+threads that are listening on this channel. A source end then corresponds to a
12+hostname (the hostname used by clients to identity the local host), port
13+number, and channel ID; a receive end simply corresponds to a channel ID.
14+
15+When `mkTransport` creates a new transport it spawns a thread that listens for
16+incoming connections, running `procConnections` (see below). The set of
17+channels (connections) associated with the transport is initialized to be
18+empty.
19+
20+`newConnectionWith` creates a new channel and add its to the transport channel
21+map (with an empty list of associated threads).
22+
23+To serialize the source end we encode the triple of the local host name, port
24+number, and channel ID, and to deserialize we just decode the same triple
25+(deserialize does not need any other properties of the TCP transport).
26+
27+To connect to the source end we create a new socket, connect to the server at
28+the IP address specified in the TCPConfig, and send the channel number over the
29+connection. Then to `closeSourceEnd` we simply close the socket, and to send a
30+bunch of byte strings we output them on the socket.
31+
32+To receive from the target end we just read from the channel associated with
33+the target end. To `closeTargetEnd` we find kill all threads associated with
34+the channel and close their sockets.
35+
36+When somebody connects to server (running `procConnections`), he first sends a
37+channel ID. `procConnections` then spawns a new thread running
38+`procMessages` which listens for bytestrings on the socket and output them on
39+the specified channel. The ID of this new thread (and the socket it uses) are
40+added to the channel map of the transport.
41+
42+`closeTransport` kills the server thread and all threads that were listening on
43+the channels associated with the transport, and closes all associated sockets.
44+
45+Improving Latency
46+-----------------
47+
48+A series of benchmarks has shown that
49+
50+* The use of `-threaded` triples the latency.
51+
52+* Prepending a header to messages has a negligible effect on latency, even when
53+ sending very small packets. However, the way that we turn the length from an
54+ `Int32` to a `ByteString` _does_ have a significant impact; in particular,
55+ using `Data.Serialize` is very slow (and using Blaze.ByteString not much
56+ better). This is fast:
57+
58+ foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
59+
60+ encodeLength :: Int32 -> IO ByteString
61+ encodeLength i32 =
62+ BSI.create 4 $ \p ->
63+ pokeByteOff p 0 (htonl (fromIntegral i32))
64+
65+* We do not need to use `blaze-builder` or related;
66+ `Network.Socket.Bytestring.sendMany` uses vectored I/O. On the client side
67+ doing a single `recv` to try and read the message header and message, rather
68+ one to read the header and one to read the payload improves latency, but only
69+ by a tiny amount.
70+
71+* Indirection through an `MVar` or a `Chan` does not have an observable effect
72+ on latency.
73+
74+* When two nodes _A_ and _B_ communicate, latency is worse when they
75+ communicate over two pairs of sockets (used unidirectionally) rather than one
76+ pair (used bidirectionally) by about 20%. This is not improved by using
77+ `TCP_NODELAY`, and might be because [acknowledgements cannot piggyback with
78+ payload][1] this way. It might thus be worthwhile to try and reuse TCP
79+ connections (or use UDP).
80+
81+[1]: http://lists.freebsd.org/pipermail/freebsd-hackers/2009-March/028006.html "2 uni-directional TCP connection good?"
doc/NetworkTCP.pngnew file

Binary file not shown.

doc/NetworkTCP.svgnew file+657
@@ -0,0 +1,657 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:xlink="http://www.w3.org/1999/xlink"
11+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13+ width="1024px"
14+ height="768px"
15+ id="svg4129"
16+ version="1.1"
17+ inkscape:version="0.48.2 r9819"
18+ sodipodi:docname="NetworkTCP.svg"
19+ inkscape:export-filename="/tmp/distributed-process/doc/NetworkTCP.png"
20+ inkscape:export-xdpi="90.040001"
21+ inkscape:export-ydpi="90.040001">
22+ <defs
23+ id="defs4131">
24+ <linearGradient
25+ id="linearGradient4726">
26+ <stop
27+ style="stop-color:#800000;stop-opacity:1;"
28+ offset="0"
29+ id="stop4728" />
30+ <stop
31+ style="stop-color:#ffffff;stop-opacity:1;"
32+ offset="1"
33+ id="stop4730" />
34+ </linearGradient>
35+ <linearGradient
36+ id="linearGradient4647">
37+ <stop
38+ style="stop-color:#008080;stop-opacity:1;"
39+ offset="0"
40+ id="stop4649" />
41+ <stop
42+ style="stop-color:#ffffff;stop-opacity:1;"
43+ offset="1"
44+ id="stop4651" />
45+ </linearGradient>
46+ <linearGradient
47+ inkscape:collect="always"
48+ xlink:href="#linearGradient4647-0"
49+ id="linearGradient4655-9"
50+ x1="-666.70703"
51+ y1="18.113195"
52+ x2="952.4931"
53+ y2="345.05661"
54+ gradientUnits="userSpaceOnUse"
55+ gradientTransform="matrix(0.43409118,0,0,1.0013813,19.021275,0.01045425)" />
56+ <linearGradient
57+ id="linearGradient4647-0">
58+ <stop
59+ style="stop-color:#008080;stop-opacity:1;"
60+ offset="0"
61+ id="stop4649-1" />
62+ <stop
63+ style="stop-color:#ffffff;stop-opacity:1;"
64+ offset="1"
65+ id="stop4651-0" />
66+ </linearGradient>
67+ <linearGradient
68+ inkscape:collect="always"
69+ xlink:href="#linearGradient4647-9"
70+ id="linearGradient4655-8"
71+ x1="-666.70703"
72+ y1="18.113195"
73+ x2="952.4931"
74+ y2="345.05661"
75+ gradientUnits="userSpaceOnUse"
76+ gradientTransform="matrix(0.43409118,0,0,1.0013813,19.021275,0.01045425)" />
77+ <linearGradient
78+ id="linearGradient4647-9">
79+ <stop
80+ style="stop-color:#008080;stop-opacity:1;"
81+ offset="0"
82+ id="stop4649-4" />
83+ <stop
84+ style="stop-color:#ffffff;stop-opacity:1;"
85+ offset="1"
86+ id="stop4651-7" />
87+ </linearGradient>
88+ <linearGradient
89+ inkscape:collect="always"
90+ xlink:href="#linearGradient4726-0"
91+ id="linearGradient4732-1"
92+ x1="484.52829"
93+ y1="88.754707"
94+ x2="18.113218"
95+ y2="336.90564"
96+ gradientUnits="userSpaceOnUse" />
97+ <linearGradient
98+ inkscape:collect="always"
99+ id="linearGradient4726-0">
100+ <stop
101+ style="stop-color:#800000;stop-opacity:1;"
102+ offset="0"
103+ id="stop4728-2" />
104+ <stop
105+ style="stop-color:#800000;stop-opacity:0;"
106+ offset="1"
107+ id="stop4730-5" />
108+ </linearGradient>
109+ <linearGradient
110+ gradientTransform="translate(539.26415,1.83019)"
111+ y2="336.90564"
112+ x2="18.113218"
113+ y1="88.754707"
114+ x1="484.52829"
115+ gradientUnits="userSpaceOnUse"
116+ id="linearGradient4755"
117+ xlink:href="#linearGradient4726-0"
118+ inkscape:collect="always" />
119+ <linearGradient
120+ inkscape:collect="always"
121+ xlink:href="#linearGradient4726-6"
122+ id="linearGradient4732-4"
123+ x1="484.52829"
124+ y1="88.754707"
125+ x2="18.113218"
126+ y2="336.90564"
127+ gradientUnits="userSpaceOnUse" />
128+ <linearGradient
129+ id="linearGradient4726-6">
130+ <stop
131+ style="stop-color:#800000;stop-opacity:1;"
132+ offset="0"
133+ id="stop4728-4" />
134+ <stop
135+ style="stop-color:#ffffff;stop-opacity:1;"
136+ offset="1"
137+ id="stop4730-53" />
138+ </linearGradient>
139+ <linearGradient
140+ inkscape:collect="always"
141+ xlink:href="#linearGradient4726-4"
142+ id="linearGradient4732-6"
143+ x1="484.52829"
144+ y1="88.754707"
145+ x2="18.113218"
146+ y2="336.90564"
147+ gradientUnits="userSpaceOnUse" />
148+ <linearGradient
149+ id="linearGradient4726-4">
150+ <stop
151+ style="stop-color:#800000;stop-opacity:1;"
152+ offset="0"
153+ id="stop4728-40" />
154+ <stop
155+ style="stop-color:#ffffff;stop-opacity:1;"
156+ offset="1"
157+ id="stop4730-7" />
158+ </linearGradient>
159+ <linearGradient
160+ inkscape:collect="always"
161+ xlink:href="#linearGradient4726-3"
162+ id="linearGradient4732-2"
163+ x1="484.52829"
164+ y1="88.754707"
165+ x2="18.113218"
166+ y2="336.90564"
167+ gradientUnits="userSpaceOnUse" />
168+ <linearGradient
169+ id="linearGradient4726-3">
170+ <stop
171+ style="stop-color:#800000;stop-opacity:1;"
172+ offset="0"
173+ id="stop4728-8" />
174+ <stop
175+ style="stop-color:#ffffff;stop-opacity:1;"
176+ offset="1"
177+ id="stop4730-1" />
178+ </linearGradient>
179+ </defs>
180+ <sodipodi:namedview
181+ id="base"
182+ pagecolor="#ffffff"
183+ bordercolor="#666666"
184+ borderopacity="1.0"
185+ inkscape:pageopacity="0.0"
186+ inkscape:pageshadow="2"
187+ inkscape:zoom="0.70710678"
188+ inkscape:cx="510.54885"
189+ inkscape:cy="435.44033"
190+ inkscape:current-layer="layer1"
191+ inkscape:document-units="px"
192+ showgrid="false"
193+ showguides="true"
194+ inkscape:guide-bbox="true"
195+ inkscape:window-width="1393"
196+ inkscape:window-height="789"
197+ inkscape:window-x="47"
198+ inkscape:window-y="3"
199+ inkscape:window-maximized="0">
200+ <sodipodi:guide
201+ orientation="1,0"
202+ position="984,175"
203+ id="guide4870" />
204+ </sodipodi:namedview>
205+ <metadata
206+ id="metadata4134">
207+ <rdf:RDF>
208+ <cc:Work
209+ rdf:about="">
210+ <dc:format>image/svg+xml</dc:format>
211+ <dc:type
212+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
213+ <dc:title />
214+ </cc:Work>
215+ </rdf:RDF>
216+ </metadata>
217+ <g
218+ id="layer1"
219+ inkscape:label="Layer 1"
220+ inkscape:groupmode="layer">
221+ <rect
222+ style="fill:#41bff6;fill-opacity:1;stroke:#008080;stroke-width:1.24376427999999994;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
223+ id="rect4137-5"
224+ width="412.79617"
225+ height="287.24374"
226+ x="571.56415"
227+ y="34.192436" />
228+ <rect
229+ style="fill:#41bff6;fill-opacity:1;stroke:#008080;stroke-width:1.24167538000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
230+ id="rect4137"
231+ width="412.79617"
232+ height="286.27966"
233+ x="33.567425"
234+ y="34.473083" />
235+ <rect
236+ style="fill:#41bff6;fill-opacity:1;stroke:#008080;stroke-width:1.89734482999999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
237+ id="rect4137-6"
238+ width="950.33942"
239+ height="290.35135"
240+ x="32.696255"
241+ y="448.07922" />
242+ <rect
243+ style="fill:#999999;stroke:none;stroke-width:1.97458196000000008;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
244+ id="rect4657-9"
245+ width="582.76776"
246+ height="145.3952"
247+ x="109.61612"
248+ y="-307.5813"
249+ transform="matrix(0,1,-1,0,0,0)" />
250+ <rect
251+ style="fill:#999999;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1;opacity:1"
252+ id="rect4657"
253+ width="582.76776"
254+ height="149.16254"
255+ x="256.99347"
256+ y="124.76707" />
257+ <text
258+ xml:space="preserve"
259+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
260+ x="36.887089"
261+ y="73.584908"
262+ id="text4659"
263+ sodipodi:linespacing="125%"><tspan
264+ sodipodi:role="line"
265+ id="tspan4661"
266+ x="36.887089"
267+ y="73.584908"
268+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.<tspan
269+ style="font-weight:bold;-inkscape-font-specification:Courier New Bold"
270+ id="tspan4038">1</tspan>:<tspan
271+ style="font-weight:normal;-inkscape-font-specification:Courier New;fill:#800000"
272+ id="tspan4036">1080</tspan></tspan></text>
273+ <text
274+ xml:space="preserve"
275+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
276+ x="574.88385"
277+ y="73.30426"
278+ id="text4659-2"
279+ sodipodi:linespacing="125%"><tspan
280+ sodipodi:role="line"
281+ id="tspan4661-9"
282+ x="574.88385"
283+ y="73.30426"
284+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.<tspan
285+ style="font-weight:bold;-inkscape-font-specification:Courier New Bold"
286+ id="tspan4042">1</tspan>:<tspan
287+ style="fill:#800000"
288+ id="tspan4044">1081</tspan></tspan></text>
289+ <rect
290+ style="fill:#999999;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
291+ id="rect4657-7"
292+ width="582.76776"
293+ height="149.16254"
294+ x="229.61612"
295+ y="535.4187" />
296+ <text
297+ xml:space="preserve"
298+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
299+ x="321.0159"
300+ y="488.19104"
301+ id="text4659-9"
302+ sodipodi:linespacing="125%"><tspan
303+ sodipodi:role="line"
304+ id="tspan4661-90"
305+ x="321.0159"
306+ y="488.19104"
307+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.<tspan
308+ style="font-weight:bold;-inkscape-font-specification:Courier New Bold"
309+ id="tspan4046">2</tspan>:<tspan
310+ style="fill:#800000"
311+ id="tspan4048">1080</tspan></tspan></text>
312+ <rect
313+ style="fill:#ffa23a;stroke:#340000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
314+ id="rect4724"
315+ width="366.79245"
316+ height="200.15094"
317+ x="54.339622"
318+ y="95.094337" />
319+ <text
320+ xml:space="preserve"
321+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
322+ x="62.264153"
323+ y="117.35849"
324+ id="text4734"
325+ sodipodi:linespacing="125%"><tspan
326+ sodipodi:role="line"
327+ id="tspan4736"
328+ x="62.264153"
329+ y="117.35849"
330+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.1:1080:0</tspan></text>
331+ <rect
332+ style="fill:#ffa23a;fill-opacity:1;stroke:#340000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
333+ id="rect4724-0"
334+ width="366.79245"
335+ height="200.15094"
336+ x="597.60376"
337+ y="98.92453" />
338+ <text
339+ xml:space="preserve"
340+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
341+ x="605.52832"
342+ y="121.18868"
343+ id="text4734-8"
344+ sodipodi:linespacing="125%"><tspan
345+ sodipodi:role="line"
346+ id="tspan4736-8"
347+ x="605.52832"
348+ y="121.18868"
349+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.1:1081:0</tspan></text>
350+ <rect
351+ style="fill:#ffa23a;fill-opacity:1;stroke:#340000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
352+ id="rect4724-6"
353+ width="366.79245"
354+ height="200.15094"
355+ x="53.60376"
356+ y="511.92453" />
357+ <text
358+ xml:space="preserve"
359+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier New;-inkscape-font-specification:Courier New"
360+ x="63.52832"
361+ y="534.18872"
362+ id="text4734-0"
363+ sodipodi:linespacing="125%"><tspan
364+ sodipodi:role="line"
365+ id="tspan4736-9"
366+ x="63.52832"
367+ y="534.18872">198.51.100.2:1080:0</tspan></text>
368+ <rect
369+ style="fill:#ffa23a;fill-opacity:1;stroke:#340000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
370+ id="rect4724-06"
371+ width="366.79245"
372+ height="200.15094"
373+ x="596.60376"
374+ y="510.92456" />
375+ <text
376+ xml:space="preserve"
377+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
378+ x="604.52832"
379+ y="533.18872"
380+ id="text4734-02"
381+ sodipodi:linespacing="125%"><tspan
382+ sodipodi:role="line"
383+ id="tspan4736-4"
384+ x="604.52832"
385+ y="533.18872"
386+ style="-inkscape-font-specification:Courier New;font-family:Courier New;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">198.51.100.2:1080:1</tspan></text>
387+ <path
388+ sodipodi:type="arc"
389+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
390+ id="path5106"
391+ sodipodi:cx="215.5"
392+ sodipodi:cy="197.5"
393+ sodipodi:rx="14.5"
394+ sodipodi:ry="14.5"
395+ d="m 230,197.5 a 14.5,14.5 0 1 1 -29,0 14.5,14.5 0 1 1 29,0 z"
396+ transform="translate(22.235847,-2.3301926)" />
397+ <path
398+ sodipodi:type="arc"
399+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
400+ id="path5106-0"
401+ sodipodi:cx="215.5"
402+ sodipodi:cy="197.5"
403+ sodipodi:rx="14.5"
404+ sodipodi:ry="14.5"
405+ d="m 230,197.5 a 14.5,14.5 0 1 1 -29,0 14.5,14.5 0 1 1 29,0 z"
406+ transform="translate(565.49998,1.5)" />
407+ <path
408+ sodipodi:type="arc"
409+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
410+ id="path5106-7"
411+ sodipodi:cx="215.5"
412+ sodipodi:cy="197.5"
413+ sodipodi:rx="14.5"
414+ sodipodi:ry="14.5"
415+ d="m 230,197.5 a 14.5,14.5 0 1 1 -29,0 14.5,14.5 0 1 1 29,0 z"
416+ transform="translate(564.49998,413.50003)" />
417+ <path
418+ sodipodi:type="arc"
419+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
420+ id="path5106-06"
421+ sodipodi:cx="215.5"
422+ sodipodi:cy="197.5"
423+ sodipodi:rx="14.5"
424+ sodipodi:ry="14.5"
425+ d="m 230,197.5 a 14.5,14.5 0 1 1 -29,0 14.5,14.5 0 1 1 29,0 z"
426+ transform="translate(21.499985,414.5)" />
427+ <path
428+ sodipodi:type="star"
429+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
430+ id="path5217"
431+ sodipodi:sides="3"
432+ sodipodi:cx="-58"
433+ sodipodi:cy="224"
434+ sodipodi:r1="15.033296"
435+ sodipodi:r2="7.5166483"
436+ sodipodi:arg1="-1.5707963"
437+ sodipodi:arg2="-0.52359878"
438+ inkscape:flatsided="true"
439+ inkscape:rounded="0"
440+ inkscape:randomized="0"
441+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
442+ inkscape:transform-center-y="-3.7583241"
443+ transform="translate(250,279.48335)" />
444+ <path
445+ sodipodi:type="star"
446+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
447+ id="path5217-1"
448+ sodipodi:sides="3"
449+ sodipodi:cx="-58"
450+ sodipodi:cy="224"
451+ sodipodi:r1="15.033296"
452+ sodipodi:r2="7.5166483"
453+ sodipodi:arg1="-1.5707963"
454+ sodipodi:arg2="-0.52359878"
455+ inkscape:flatsided="true"
456+ inkscape:rounded="0"
457+ inkscape:randomized="0"
458+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
459+ inkscape:transform-center-y="-3.7583241"
460+ transform="translate(336,278.75832)" />
461+ <path
462+ sodipodi:type="star"
463+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
464+ id="path5217-0"
465+ sodipodi:sides="3"
466+ sodipodi:cx="-58"
467+ sodipodi:cy="224"
468+ sodipodi:r1="15.033296"
469+ sodipodi:r2="7.5166483"
470+ sodipodi:arg1="-1.5707963"
471+ sodipodi:arg2="-0.52359878"
472+ inkscape:flatsided="true"
473+ inkscape:rounded="0"
474+ inkscape:randomized="0"
475+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
476+ inkscape:transform-center-y="-3.7583241"
477+ transform="translate(292,278.75832)" />
478+ <path
479+ sodipodi:type="star"
480+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
481+ id="path5217-7"
482+ sodipodi:sides="3"
483+ sodipodi:cx="-58"
484+ sodipodi:cy="224"
485+ sodipodi:r1="15.033296"
486+ sodipodi:r2="7.5166483"
487+ sodipodi:arg1="-1.5707963"
488+ sodipodi:arg2="-0.52359878"
489+ inkscape:flatsided="true"
490+ inkscape:rounded="0"
491+ inkscape:randomized="0"
492+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
493+ transform="matrix(0,1,-1,0,654,214.75832)"
494+ inkscape:transform-center-x="-3.7583239" />
495+ <path
496+ sodipodi:type="star"
497+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
498+ id="path5217-4"
499+ sodipodi:sides="3"
500+ sodipodi:cx="-58"
501+ sodipodi:cy="224"
502+ sodipodi:r1="15.033296"
503+ sodipodi:r2="7.5166483"
504+ sodipodi:arg1="-1.5707963"
505+ sodipodi:arg2="-0.52359878"
506+ inkscape:flatsided="true"
507+ inkscape:rounded="0"
508+ inkscape:randomized="0"
509+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
510+ transform="matrix(0,1,-1,0,653,299.75832)"
511+ inkscape:transform-center-x="-3.7583239" />
512+ <path
513+ sodipodi:type="star"
514+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
515+ id="path5217-49"
516+ sodipodi:sides="3"
517+ sodipodi:cx="-58"
518+ sodipodi:cy="224"
519+ sodipodi:r1="15.033296"
520+ sodipodi:r2="7.5166483"
521+ sodipodi:arg1="-1.5707963"
522+ sodipodi:arg2="-0.52359878"
523+ inkscape:flatsided="true"
524+ inkscape:rounded="0"
525+ inkscape:randomized="0"
526+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
527+ transform="matrix(0,-1,1,0,365,140.75832)"
528+ inkscape:transform-center-x="3.7583235" />
529+ <path
530+ sodipodi:type="star"
531+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
532+ id="path5217-70"
533+ sodipodi:sides="3"
534+ sodipodi:cx="-58"
535+ sodipodi:cy="224"
536+ sodipodi:r1="15.033296"
537+ sodipodi:r2="7.5166483"
538+ sodipodi:arg1="-1.5707963"
539+ sodipodi:arg2="-0.52359878"
540+ inkscape:flatsided="true"
541+ inkscape:rounded="0"
542+ inkscape:randomized="0"
543+ d="m -58,208.9667 13.019216,22.54995 -26.038432,0 z"
544+ transform="matrix(0,1,-1,0,653,665.75832)"
545+ inkscape:transform-center-x="-3.7583239" />
546+ <path
547+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
548+ id="path5272"
549+ d="m 193.68926,500.34844 c -0.0623,-0.011 -3.1352,-0.097 -2.77128,-1.41324 0.2615,-0.94576 1.83318,-1.37784 1.76404,-2.35665 -0.0476,-0.67399 -2.3566,0.0555 -1.96552,-0.49551 3.61436,-5.09213 7.40016,-2.72086 3.75441,-4.53186 -1.02814,0.14511 -2.14021,0.86727 -3.08442,0.43531 -0.51327,-0.2348 0.48469,-1.05111 0.89792,-1.43558 1.1048,-1.02792 2.49187,-1.72109 3.59807,-2.74751 0.46707,-0.43338 1.70248,-1.66102 1.07001,-1.58392 -5.6701,0.69128 -9.05745,4.75136 -4.37921,0.0443 -0.70285,-0.0451 -1.58092,0.33122 -2.10855,-0.13529 -0.36515,-0.32287 0.13779,-0.98089 0.34974,-1.41981 0.746,-1.54482 2.04183,-2.71312 3.1089,-4.00945 0.4506,-0.54741 1.18336,-0.99124 1.28844,-1.69243 0.0528,-0.35257 -0.64405,-0.30591 -0.96608,-0.45886 -0.29448,-0.23467 -0.54733,-0.53426 -0.88345,-0.704 -4.59528,-2.32065 4.03029,4.51864 2.22179,-3.3353 -0.30962,-1.34461 -4.25255,2.34931 -4.01941,0.98936 0.49925,-2.91216 11.67979,-5.28818 -0.97921,-1.80297 2.24958,4.51807 3.74218,-3.55163 2.31947,-3.58424 -1.2249,-0.0281 -2.37028,0.65039 -3.58472,0.8125 -0.52552,0.0702 -1.82785,0.34942 -1.58584,-0.1223 0.5628,-1.09696 1.92657,-1.54028 2.85501,-2.35149 0.70996,-0.62032 1.37164,-1.29381 2.05746,-1.94071 -0.89522,0.15512 -1.79044,0.31024 -2.68566,0.46535 0.40052,-0.43099 1.78504,-1.21739 1.20156,-1.29297 -1.00554,-0.13025 -3.61463,1.73864 -2.84352,1.08028 2.61678,-2.23416 7.40375,-3.51247 2.7139,-2.27363 -0.68863,-0.56914 -4.0411,1.72327 -3.46573,0.69506 6.10702,-10.91337 6.03578,15.71084 15.20135,-9.25342 1.34953,-3.67571 -7.11657,3.28455 -10.78034,4.66617 -0.56483,0.21299 -1.20609,0.0542 -1.80913,0.0813 0.76568,-2.82657 9.84801,-2.83331 8.08522,-5.59671 -1.3756,-2.15643 -4.67918,2.20115 -7.19335,2.67169 -0.59179,0.11076 0.56857,-1.08069 0.98997,-1.51069 0.9071,-0.92561 3.07293,-1.22924 2.96045,-2.52034 -0.0925,-1.06133 -2.0796,0.46382 -3.1194,0.69573 -1.9254,-1.61777 -0.23831,-2.25512 1.24604,-3.92389 0.51908,-0.58358 2.26263,-1.8224 1.48163,-1.81517 -4.72816,0.0437 -15.68126,8.0904 3.31529,-3.96387 -2.08474,0.72265 -4.12918,1.57418 -6.25422,2.16795 -6.19352,1.73056 1.93855,-2.61924 2.43539,-3.11862 0.23788,-0.23911 -1.011,-0.39289 -0.78275,-0.64119 1.19525,-1.30024 3.85496,-1.42731 4.26366,-3.14549 0.29345,-1.23365 -2.47514,0.55288 -3.71271,0.82932 -0.47093,1.29702 -2.86607,-0.7275 -2.30369,-1.43649 1.13698,-1.43339 6.00313,-3.6915 4.17835,-3.55914 -3.87338,0.28095 -7.04111,4.03354 -10.92463,4.04848 -2.19049,0.008 3.44176,-2.71129 5.19354,-4.02643 2.07031,-1.55429 4.18104,-3.054 6.27156,-4.581 -2.38158,0.72626 -4.66588,1.94492 -7.14472,2.17878 -0.53269,0.0503 0.25632,-1.04063 0.43321,-1.5456 0.96518,-2.7553 0.80112,-1.64828 3.58926,-4.46916 0.67747,-0.68544 2.91667,-2.22292 1.95765,-2.12762 -2.73974,0.27225 -5.202,1.80566 -7.80301,2.70849 -8.68307,1.74603 11.86244,-6.07092 8.35137,-7.01022 -1.96335,-0.52524 -6.69769,3.57812 -5.84576,1.73289 1.22888,-2.6617 5.30375,-2.7549 7.30622,-4.89614 0.68666,-0.73425 -2.00109,0.19549 -3.00164,0.29323 0.87732,-0.86011 1.75464,-1.72023 2.63196,-2.58035 -9.58228,3.08729 -5.78397,4.19264 -6.58589,0.97012 -0.0902,-0.3624 -0.0624,-0.93313 -0.42075,-1.03836 -0.91483,-0.26865 -3.5847,0.69399 -2.8594,0.0751 8.61485,-7.35105 17.70344,-8.79375 5.26595,-5.85043 1.60054,-3.41758 1.28401,-1.02057 -0.60908,-2.58794 -0.3464,-0.2868 -0.289,-0.89839 -0.62771,-1.19424 -0.43227,-0.37759 -1.86567,-0.23189 -1.56403,-0.7202 2.34987,-3.80416 8.10292,-4.17851 0.0503,-2.68926 -1.15677,-3.65997 -0.0599,1.01346 5.25047,-5.42269 0.33771,-0.40931 -1.12483,-0.33282 -1.37625,-0.80012 -0.22134,-0.4114 0.2271,-0.9209 0.19939,-1.38724 -0.20588,-3.46551 -0.71496,1.54991 -0.16416,-2.39216 -5.64005,3.38956 6.40177,-4.28155 4.35608,-4.32495 -1.7082,-0.0362 -3.23754,1.16492 -4.92891,1.40681 -0.55643,0.0796 -2.04039,-0.0979 -1.61937,-0.47032 2.59221,-2.29286 6.19284,-3.30617 8.64724,-5.74599 0.70785,-0.70364 -1.99446,0.0827 -2.99169,0.12404 -0.76412,-0.0589 -2.58888,0.52999 -2.29238,-0.17673 0.62545,-1.49077 4.87787,-2.0553 3.688,-3.14973 -1.3995,-1.28723 -3.63271,1.17661 -5.51093,1.47304 -0.6753,0.10658 -1.96892,0.39814 -2.03141,-0.28266 2.03577,-2.47367 2.13582,-1.3059 1.42131,-3.21609 0.58154,-2.04915 3.24163,-2.36974 4.11535,-4.097 -0.79817,-0.13876 -2.2949,0.11553 -1.46683,-1.35383 0.38381,-0.68104 2.31695,-1.56406 1.55829,-1.75265 -1.35197,-0.33608 -2.68049,0.76035 -4.02074,1.14053 10.77595,-7.1105 3.36075,-1.66253 -0.19448,-1.41063 -0.80754,0.0572 1.50647,-0.94746 1.70133,-1.73322 0.0991,-0.39958 -0.87355,-0.0758 -1.18632,-0.34348 -0.22941,-0.19634 -0.44034,-0.65146 -0.2367,-0.87442 1.11915,-1.22532 2.58807,-2.07788 3.88211,-3.11681 0.46226,-0.59368 2.13907,-1.7945 1.38677,-1.78102 -14.87693,0.26652 -1.27125,4.70958 -2.16014,-1.06114 -0.19906,-1.29234 -2.99053,1.78966 -3.84104,0.79648 -2.43359,-2.8418 7.84171,-2.59072 -0.18106,-2.32196 1.26696,-1.11062 2.53393,-2.22125 3.80089,-3.33188 -0.37381,-0.36169 -1.34521,-0.61552 -1.12142,-1.08506 0.6859,-1.4391 2.50279,-2.08411 3.29206,-3.46921 0.30719,-0.53911 0.33063,-1.66965 -0.26531,-1.84246 -1.52437,-0.44204 -4.51329,1.95947 -4.74554,0.38939 -0.28847,-1.95008 2.79003,-2.78566 4.18504,-4.17849 -2.84688,0.77924 -5.61536,1.94441 -8.54064,2.33772 -0.86059,0.11571 -2.88311,-0.10077 -2.4594,-0.85871 1.22634,-2.19374 3.76055,-3.33687 5.69902,-4.93647 2.28197,-1.88304 5.17285,-3.1505 6.95922,-5.5089 0.48483,-0.64009 -1.53136,-0.56213 -2.33353,-0.59804 -1.34734,-0.0603 -2.6893,0.20862 -4.03395,0.31293 0.68463,-0.93535 1.93126,-1.65341 2.0539,-2.80605 0.14342,-1.34792 -3.60803,-0.10431 -3.67727,-1.17576 -0.0618,-0.95616 1.17488,-1.51652 1.82059,-2.22442 3.91139,-4.28816 5.04581,-3.35568 -6.18155,0.79961 3.2876,-2.35602 5.31447,-2.73015 6.79781,-5.80474 0.18346,-0.38028 0.74816,-1.16656 0.32973,-1.223 -1.22167,-0.16479 -2.80483,1.70893 -3.61503,0.77985 -0.71461,-0.81948 1.69757,-1.45353 2.17622,-2.42979 0.17131,-0.3494 -0.3057,-0.71572 -0.45855,-1.07358 0.20589,-0.53947 1.0395,-1.22409 0.61769,-1.61843 -0.59794,-0.559 -1.71607,0.25692 -2.45368,-0.098 -0.35235,-0.16954 -0.12335,-0.77263 -0.16402,-1.16153 -0.35068,-3.35404 1.76007,-2.61899 -2.06169,-3.55371 0.97288,-0.96358 6.7044,-6.50628 7.49127,-7.802 0.42498,-0.6998 1.09682,-2.25813 0.2981,-2.43805 -2.14748,-0.48373 -4.40681,1.64541 -6.52208,1.03607 -0.89757,-0.25856 0.33999,-1.8897 0.85608,-2.66825 0.97958,-1.47772 2.36172,-2.64483 3.54258,-3.96725 0.0597,-0.68098 0.49091,-1.4346 0.17912,-2.04295 -1.44492,-2.81925 -3.89682,2.01552 0.0472,-3.44476 0.58877,-0.81511 2.85288,-2.38727 1.84737,-2.38469 -5.49626,0.0141 -12.52399,6.39939 -2.52177,-1.41194 0.1794,-0.46573 0.24889,-0.99051 0.53821,-1.39719 4.68594,-6.58674 -0.51929,4.47458 -3.32287,-0.5112 -0.864,-1.53652 3.5875,-1.65903 4.094,-3.34747 0.30845,-1.02819 -2.13504,0.50427 -3.19953,0.36577 -0.41883,-0.0545 -0.59573,-0.59889 -0.8936,-0.89833 0.92251,-0.92192 1.84502,-1.84384 2.76752,-2.76576 -1.55796,0.31564 -5.53053,2.28596 -4.67389,0.94692 1.58563,-2.47854 5.13653,-2.97992 7.27047,-5.00568 0.39453,-0.37453 -1.07912,-0.19012 -1.5841,-0.39242 -2.59907,-1.04122 1.45702,-1.10305 -3.99527,-0.0413 0.23249,-0.50921 2.4381,-3.75255 2.14815,-3.95163 -0.6386,-0.43846 -1.56195,0.16288 -2.3238,0.0227 -0.41951,-0.0772 -0.73377,-0.43517 -1.10066,-0.65276 -1.05703,0.094 -2.11405,0.18798 -3.17108,0.28197 14.56659,-10.91162 6.60855,-1.66215 5.19861,-5.97316 -0.7148,-2.18559 1.24321,-1.65251 -0.51252,-3.60095 -0.48504,-0.53828 -1.8482,-0.40764 -1.85561,-1.13218 -0.0121,-1.18458 1.20852,-2.03788 1.81278,-3.05682 -9.27694,-0.46414 -0.43422,1.04567 -1.03621,-3.77995 -0.14103,-1.13058 -2.45562,0.40168 -3.41099,-0.2191 -0.53557,-0.348 -0.27624,-1.42029 0.13193,-1.91155 1.52211,-1.83192 3.75639,-2.96142 5.37142,-4.71197 0.69547,-0.75384 0.97981,-1.80216 1.46971,-2.70325 -0.96071,-0.0793 -1.91817,-0.23172 -2.88212,-0.23794 -8.55832,-0.0551 2.30363,2.43158 5.84613,-5.75478 1.04199,-2.40794 -6.05494,4.67811 -7.47329,2.47082 -1.97994,-3.08124 13.63059,-6.08404 2.2912,-3.51859 -0.25021,-0.2252 -0.8741,-0.36244 -0.75064,-0.67561 0.33861,-0.85888 1.28474,-1.33325 1.8349,-2.07463 0.3234,-0.4358 1.30072,-1.54508 0.76935,-1.4348 -2.61741,0.54321 -4.9249,2.12483 -7.50279,2.83224 -0.26475,0.0727 -0.6308,-0.3697 -0.53436,-0.62673 0.22699,-0.60501 0.87117,-0.95461 1.30676,-1.43191 8.33195,-4.95149 5.03236,-1.13737 6.11797,-4.3184 0.10381,-0.30416 0.66881,-0.79561 0.36265,-0.89337 -0.70346,-0.22461 -2.72427,0.82235 -2.19373,0.30871 3.86981,-3.74657 7.01351,-2.20799 4.21301,-3.35731 6.47296,-4.28612 -2.20647,1.73687 -2.12215,-0.0258 0.0771,-1.61222 3.0843,-1.12915 4.34522,-2.13676 0.70337,-0.56207 -1.70011,0.66929 -2.59767,0.74027 -0.317,0.0251 0.78281,-0.5597 2.49822,-1.51137 0.46567,-0.53737 0.66407,-0.83263 1.36374,-1.22722 0.98725,-0.55676 2.06782,-0.93963 3.04281,-1.51759 0.0706,-0.0419 -0.0426,-0.23655 -0.12151,-0.21413 -5.29503,1.50384 -4.40257,3.36258 -5.22385,1.37394 3.06387,-1.38383 6.12774,-2.76767 9.1916,-4.1515 -1.02417,0.2612 -2.73589,1.78554 -3.07252,0.78362 -0.34305,-1.02104 2.16911,-0.75459 2.7855,-1.63792 0.3836,-0.54973 -1.31058,0.6231 -1.95917,0.45373 -0.38527,-0.1006 0.60234,-0.53362 0.95654,-0.71554 1.47371,-0.75691 3.00581,-1.3943 4.50871,-2.09145 -0.63184,0.1759 -1.24071,0.5651 -1.89552,0.52772 -0.3639,-0.0208 0.62135,-0.38155 0.92504,-0.58312 3.01431,-2.00081 5.71058,-4.58179 9.02869,-6.02384 1.54675,-0.67223 -4.64784,3.89499 -4.52219,5.19822 0.0355,0.36779 0.73819,0.0341 1.10729,0.0512 4.32563,-0.80488 7.35768,-4.13082 11.1,-6.17039 0.43499,-0.23707 -1.10983,0.83769 -0.82002,1.23946 0.2765,0.38331 0.88036,-0.35243 1.29073,-0.58691 0.67659,-0.38659 1.30782,-0.84766 1.96173,-1.27149 -5.61569,3.81123 5.847,-4.12908 4.57733,-2.85552 -1.45264,1.4571 -6.72903,3.75042 -4.68856,4.01464 2.53042,0.32765 4.77089,-2.05254 6.66849,-3.75827 1.18442,-1.06466 -3.01765,1.13692 -4.60412,1.27648 -0.49565,0.11429 -1.88012,0.66559 -1.48695,0.34287 0.77429,-0.63555 1.7993,-0.88123 2.69333,-1.33308 0.97151,-0.49102 1.95644,-0.95806 2.9031,-1.49543 2.92382,-1.0436 -0.19707,-0.51294 -0.0215,-0.62582 1.25048,-0.80387 2.65543,-1.33729 3.98314,-2.00593 0,0 4.7807,-1.95243 4.7807,-1.95243 l 0,0 c -1.85659,1.05808 -3.72243,2.42084 -5.65484,3.34966 -9.08762,4.36805 -8.0472,3.12416 2.63095,-0.35854 0.20723,-0.0676 -0.32075,0.29522 -0.48112,0.44283 -2.06995,0.87954 -4.1399,1.75908 -6.20986,2.63862 0.9538,-0.27297 3.69622,-1.35491 2.86139,-0.81891 -2.24338,1.44037 -4.80919,2.30726 -7.15434,3.57521 -0.84863,0.45883 -2.71756,2.51318 -2.41323,1.5977 0.55196,-1.66044 2.99692,-2.07528 3.70559,-3.71809 2.47079,-5.12674 -0.15677,0.50283 -12.08956,8.85874 -0.3399,0.23801 0.52938,-0.68968 0.57986,-1.10155 0.0246,-0.20093 -0.29494,-0.28758 -0.38754,-0.4676 -0.11097,-0.21572 -0.0206,-0.54638 -0.21125,-0.69644 -0.23265,-0.18316 -0.58274,-0.10535 -0.8741,-0.15803 -1.08077,0.53197 -2.16154,1.06394 -3.2423,1.59591 0.61123,-0.74052 2.73074,-2.56403 1.8337,-2.22155 -2.15024,0.82093 -3.72623,2.75117 -5.80559,3.73796 -0.98272,0.46636 -3.62463,1.55286 -3.21762,0.54411 0.0771,-0.19114 4.26615,-1.34033 5.19748,-1.60123 -0.002,0.84843 0.0797,0.35024 -1.54628,1.13776 -2.43685,1.18026 -4.9713,2.16361 -7.35391,3.44985 -0.17851,0.0964 0.36821,0.22917 0.57035,0.21223 0.76295,-0.0639 2.88998,-0.87285 2.25259,-0.44871 -1.73923,1.15733 -3.76736,1.80676 -5.65104,2.71013 10.64971,-3.49618 2.1377,-0.93772 0.0559,0.71953 -0.87207,0.69424 4.11786,-1.63073 3.18387,-1.02234 -2.76085,1.79836 -4.83888,1.37533 -6.50296,3.57948 0.91407,-0.27289 1.78835,-0.80636 2.74221,-0.81867 0.91196,-0.0118 -3.46726,1.47612 -1.8579,1.29003 0.88484,-0.10231 1.72074,-1.001 2.56995,-0.7322 1.4859,0.47035 -9.20657,3.22381 1.57572,0.11558 -2.36017,1.26735 -4.72034,2.53469 -7.08052,3.80204 0.11469,0.19623 0.51051,0.43392 0.34406,0.58869 -1.0316,0.95918 -4.91768,2.38029 -3.50929,2.35433 2.37641,-0.0438 4.3475,-2.14947 6.70962,-2.41333 0.99204,-0.11081 -4.76651,2.1144 -1.8757,2.33444 -2.47051,2.27022 -1.40706,0.62544 -0.30171,1.4434 0.23656,0.17505 0.58096,0.61391 0.35865,0.80673 -10.38418,9.00668 -4.16531,0.51016 -6.4293,6.51935 2.63048,-0.9705 5.18095,-2.19421 7.89145,-2.91149 0.52827,-0.1398 2.01774,0.0636 1.58915,0.40263 -3.37773,2.67161 -8.32186,3.49544 -10.88911,6.95316 -1.61096,2.16972 5.0682,-1.89717 7.67205,-2.62025 1.25504,-0.34852 2.5621,-0.62886 3.86383,-0.5832 0.65675,0.023 1.20643,0.52147 1.80965,0.7822 -3.46414,2.44363 -7.191,4.55195 -10.39243,7.33089 -0.58409,0.50701 -1.35877,1.98436 -0.62675,2.2341 1.58563,0.54094 3.29876,-0.62716 4.96942,-0.75257 1.2209,-0.0917 4.12945,-1.03159 3.67154,0.10389 -2.69944,6.69376 -9.76124,4.11086 -2.82461,5.80978 6.90857,0.95619 0.9429,-0.72532 -1.57226,5.00935 -0.34511,0.78688 2.32093,-0.12806 2.47595,0.71707 0.20152,1.09859 -1.35608,1.79559 -1.82949,2.80722 -0.6057,1.2943 -0.13837,2.97783 -1.37315,4.06119 6.75468,-0.97119 2.63252,-1.01551 -0.36648,2.73408 -0.22941,0.28683 0.44063,0.77321 0.80719,0.75004 1.40095,-0.0885 4.53648,-2.38467 4.07603,-1.05858 -1.26042,3.62998 -8.23101,3.63354 -3.0188,3.37711 -1.05416,3.23385 -0.10913,-0.73895 4.56561,-0.24918 0.70549,0.0739 -0.68816,1.25315 -1.15449,1.78768 -0.64077,0.73448 -4.96366,4.6973 -5.48665,5.53109 -0.22878,0.36475 0.0942,0.85596 0.14131,1.28395 6.80006,0.91876 -1.15929,-0.83571 1.30873,1.95101 0.51573,0.58233 2.76862,-0.57122 2.33248,0.0729 -1.14053,1.68436 -4.05337,1.81352 -4.87103,3.67613 -0.54479,1.24101 4.28517,-2.52858 4.06555,0.0598 -0.63833,0.76458 -1.43148,1.42297 -1.915,2.29376 -0.0769,0.1384 -0.3511,2.69941 -0.12976,2.8934 0.56558,0.4957 2.22633,-0.20159 2.18825,0.54951 -0.064,1.2629 -1.57761,1.97665 -2.36641,2.96498 -0.019,0.66037 -0.0381,1.32074 -0.0571,1.98111 0.94651,0.22022 2.00211,0.1676 2.83952,0.66067 1.70887,1.0062 0.93959,2.64925 0.89198,4.10584 -0.0661,2.02294 0.18274,3.87611 1.1481,5.67875 -1.06661,1.09941 -2.13322,2.19882 -3.19983,3.29823 0.2676,0.37359 1.04509,0.73029 0.80279,1.12077 -0.8266,1.33216 -3.21601,1.62884 -3.46899,3.17607 -0.16227,0.99251 2.62242,-1.24441 3.00082,-0.31263 1.09046,2.68514 -8.21468,3.66408 1.80952,1.70153 -1.63218,1.29533 -4.86877,1.80245 -4.89653,3.88598 -0.0224,1.67689 3.19324,-1.46022 4.86198,-1.29358 0.65024,0.0649 -1.66078,1.77119 -1.014,1.67783 7.46951,-1.07821 11.3939,-6.41009 0.77149,1.84447 -0.88572,0.68828 -1.57915,1.5935 -2.36873,2.39026 1.30757,-0.26932 2.64879,-1.20722 3.92271,-0.80795 0.63932,0.20037 -0.58657,1.23688 -1.06824,1.70258 -1.32676,1.28278 -3.70274,1.74138 -4.29988,3.48758 -0.15335,0.44846 6.75296,-0.67501 6.46415,-0.24736 -1.58594,2.34843 -4.48718,3.46216 -6.73077,5.19323 1.3217,-0.0924 2.67555,-0.58142 3.96508,-0.27724 0.51852,0.1223 1.02338,0.92317 0.77805,1.39606 -0.77215,1.48839 -2.31758,2.42555 -3.41729,3.69131 -0.96138,1.10654 -2.78709,1.9559 -2.76247,3.42152 0.0163,0.96872 1.93043,0.30116 2.83102,0.65839 0.65744,0.26079 1.18121,0.77827 1.77181,1.16741 -2.81262,4.75928 -2.80872,3.10652 4.67589,1.64938 -1.26041,3.25333 -6.0635,3.67638 -8.34922,6.31233 -0.54709,0.63092 1.63108,0.59247 2.45866,0.48082 6.96578,-0.93979 6.8916,-4.14914 6.40783,-0.21327 -1.18995,1.02522 -2.55904,1.87345 -3.56986,3.07565 -0.54677,0.6503 0.29383,1.66499 0.74286,1.93962 0.0538,0.0329 2.28883,0.36373 1.51653,1.38907 -0.74758,0.99254 -1.85029,1.65906 -2.77544,2.48858 -2.01492,4.0117 0.0735,-1.05844 4.36134,-0.0885 1.08359,0.24512 -1.77515,1.36522 -2.48925,2.21628 -0.52276,0.62303 0.058,4.29809 2.41131,0.37226 -1.92105,1.23721 -3.8421,2.47441 -5.76316,3.71162 0.3956,0.10122 0.79231,0.40919 1.18678,0.30368 2.2344,-0.59765 5.44044,-4.40981 6.52306,-2.36587 3.53427,6.67249 -6.90959,1.38524 -6.55868,5.44393 0.0324,0.37432 0.7442,0.19661 1.04558,0.42095 0.25204,0.18763 0.3661,0.51079 0.54915,0.76618 0.0712,0.37017 -0.10001,0.90141 0.21364,1.11051 0.53553,0.35702 2.28266,-0.21938 1.90691,0.30318 -1.19701,1.66473 -3.90255,1.96694 -4.84762,3.78655 -0.45816,0.88212 2.94486,-1.17713 2.97633,-0.18363 0.0919,2.90267 -10.32021,4.68553 2.55412,1.32332 -13.88816,10.05064 -4.21703,1.92319 -0.25957,2.91804 1.17513,0.29542 -1.99914,1.5097 -2.55359,2.58711 -0.16659,0.32374 0.84505,0.2375 0.91628,0.59456 0.15946,0.79941 -1.27266,1.48463 -0.45859,2.40209 -0.70281,0.75107 -2.11627,1.22464 -2.10843,2.25323 0.027,3.53852 5.53042,-3.86509 0.87871,2.07078 -0.73188,0.93392 -3.50631,2.521 -2.33181,2.68947 4.62982,0.66407 18.02038,-9.23088 -2.55995,4.0518 0.41314,0.23165 0.79809,0.52297 1.23941,0.69496 0.55373,0.2158 1.74031,-0.13462 1.72268,0.45941 -0.03,1.01163 -1.4099,1.50852 -1.80872,2.4387 -0.19008,0.44335 0.29109,0.9798 0.14581,1.43979 -0.89241,2.82552 -2.35877,1.39391 0.18581,2.80361 1.15576,-0.0399 2.31084,-0.1194 3.4673,-0.11973 0.91829,-2.6e-4 2.02747,-0.44581 2.75234,0.11795 1.24736,0.97013 -3.54168,3.86915 -2.35717,3.83563 5.02162,-0.14213 14.14556,-7.29822 -0.0472,2.40961 2.98489,1.23449 0.18921,-0.41624 -0.68824,2.82616 -0.60033,2.21837 4.83753,-1.87475 0.42281,2.20368 -5.8186,5.37536 -11.65517,6.41542 5.10315,0.15479 -0.74735,0.80324 -1.36046,1.75663 -2.24204,2.40972 -2.59356,1.92134 -5.97818,2.8581 -8.12378,5.26942 -0.94007,1.05648 3.87672,-2.20293 4.1629,-0.81801 0.32872,1.59077 -5.29356,3.22403 -3.66927,3.20683 9.34954,-0.099 9.14109,-6.9726 8.95016,-1.77383 -0.66494,0.80063 -1.18496,1.74821 -1.99481,2.40188 -2.30911,1.8638 -6.64809,2.20309 -7.2977,5.09856 -0.49714,2.2159 4.34211,-1.35479 6.56359,-1.82642 1.09779,-0.23306 4.03951,-1.16883 3.35516,-0.27937 -5.32352,6.9192 -9.26326,2.75287 -6.07651,5.70641 1.67414,2.04701 0.30461,0.84469 5.41887,-0.72561 0.11184,0.1144 -6.91012,5.41422 -6.9318,5.49711 -0.24616,0.94113 1.90254,-0.44718 2.87321,-0.5114 0.52816,-0.0349 1.04869,0.14483 1.57303,0.21724 -1.52161,1.1575 -3.3167,2.0243 -4.56482,3.47251 -0.44671,0.51832 2.2492,0.46712 1.74568,1.2988 -0.6216,1.02671 -3.07583,1.37328 -2.61071,2.47971 1.65123,3.92793 4.90473,-3.87333 2.6877,0.77006 -6.66072,5.56962 0.36798,-0.83336 4.28539,-0.27792 1.1079,0.15709 -5.53866,3.34557 -5.46553,4.78461 0.037,0.72743 2.02338,-0.83333 2.15168,0.38075 4.12173,0.99404 -0.0563,-0.29498 -6.22292,4.83789 -0.56224,0.46799 1.47621,-0.0627 2.178,-0.26914 1.64502,-0.48384 3.21416,-1.19582 4.82264,-1.78991 1.49947,-0.55383 4.24314,-3.23134 4.50121,-1.65384 0.27342,1.67132 -2.893,1.76151 -4.3395,2.64227 -1.95306,2.20485 -4.62765,2.88503 -7.18354,4.55102 -0.27259,0.17768 0.66562,-0.2445 0.96818,-0.12476 0.19028,0.0753 0.12726,0.38899 0.19089,0.58349 1.80537,-0.61886 3.61073,-1.23772 5.4161,-1.85658 -0.84041,0.72567 -1.82938,1.30856 -2.52124,2.17702 -0.1752,0.21991 0.033,0.56791 0.12198,0.83464 0.96367,2.88967 4.76481,-1.02093 4.14926,2.23243 -2.60527,1.74858 -5.40667,3.23551 -7.81581,5.24574 -0.25305,0.21115 0.40301,0.70659 0.73015,0.66667 1.61028,-0.1965 3.11405,-0.91185 4.68222,-1.32713 5.53619,-1.46608 4.75726,-1.80858 1.75589,1.30793 -0.67715,0.70314 -1.3174,1.4409 -1.97611,2.16135 0.34892,0.13924 1.05014,0.0421 1.04674,0.41771 -0.003,0.31093 -2.48306,3.33637 -2.38036,3.44466 1.97496,2.08253 2.7307,-1.87751 3.63602,0.61954 0.13879,0.38282 -0.0124,0.81431 -0.0186,1.22147 -0.72121,0.73268 -1.83167,1.22502 -2.16363,2.19804 -0.14402,0.42214 0.92361,0.15463 1.26175,0.44549 0.23821,0.2049 0.0682,0.67886 0.29638,0.89481 1.89225,1.79045 3.97497,-3.22614 -2.61618,3.25198 -0.37523,0.3688 -0.44084,0.95545 -0.66126,1.43318 -0.74546,2.83605 -0.90252,1.57357 3.71736,0.86489 0.008,0.42841 -6.88909,5.34977 -7.0881,6.42457 -0.14836,0.80124 1.73679,-0.14013 2.42695,0.2931 0.35611,0.22354 0.032,0.84032 0.048,1.26048 0,0 0.60987,-0.29215 0.60987,-0.29215 z"
550+ inkscape:connector-curvature="0" />
551+ <path
552+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
553+ id="path5274"
554+ d="m 235.10087,494.853 c -1.72477,0.26384 -3.45101,1.06486 -5.1743,0.79153 -0.6595,-0.1046 -1.35563,-1.23186 -0.95104,-1.76306 1.85598,-2.43682 6.54502,-3.01774 6.90875,-6.05919 0.24847,-2.0776 -4.22104,1.06972 -6.25171,0.56521 -0.73743,-0.18322 -0.89283,-1.60823 -0.45989,-2.23268 1.45131,-2.09329 3.73816,-3.46105 5.60724,-5.19158 -1.30165,-0.0397 -3.61704,1.15104 -3.90497,-0.11899 -0.346,-1.52615 2.44277,-2.11658 3.07345,-3.54874 0.21772,-0.49443 -0.40056,-1.19952 -0.92452,-1.33116 -2.9824,-0.74933 -3.35447,1.04728 -6.00663,-0.1243 -0.44177,-0.19515 -0.59317,-0.76231 -0.88975,-1.14347 -1.62467,-2.19337 7.01252,-3.6846 6.03097,-6.4629 -0.35181,-0.99579 -2.82628,0.69214 -3.15292,-0.31219 -0.38064,-1.1704 1.77768,-1.8691 2.05342,-3.06855 0.13841,-0.60205 -0.6583,-1.04553 -0.98745,-1.56829 0.30475,-0.89097 0.60949,-1.78193 0.91424,-2.67289 -7.53264,-2.23618 2.38396,1.59728 1.54227,-5.15805 -0.19963,-1.60228 -4.7333,1.91176 -4.83467,0.30028 -0.13257,-2.10718 3.96145,-2.35795 4.5782,-4.3772 0.30919,-1.0123 -2.1105,-0.1648 -3.16575,-0.24721 1.05904,-1.13455 2.11809,-2.26911 3.17714,-3.40366 -0.0107,-0.005 -2.87769,-1.00308 -2.64038,-1.81722 0.28175,-0.96659 2.02632,-1.34668 1.90262,-2.34586 -0.0907,-0.7326 -2.66456,0.44301 -2.21022,-0.13879 4.30542,-5.51324 7.1961,-2.12072 4.55559,-4.73091 -10.33839,2.77071 -4.13255,1.83896 0.36955,-2.9086 0.29783,-0.31407 -0.57517,-0.86256 -1.00599,-0.821 -1.92044,0.18526 -4.33153,2.91099 -5.60131,1.45839 4.33291,-3.89435 5.22082,-2.41149 2.30678,-4.64367 -0.95622,-0.209 -2.72732,0.34155 -2.86867,-0.62699 -0.18982,-1.30072 1.45602,-2.19689 2.31084,-3.19549 1.82796,-2.13542 4.60067,-3.91377 5.39003,-6.85628 0.1612,-0.60094 0.29581,-1.49754 -0.21396,-1.85426 -0.8024,-0.56149 -1.9457,-0.22517 -2.91855,-0.33776 13.2084,-10.64887 -4.15898,2.78139 1.32237,-6.03641 0.64361,-1.03536 2.37686,-1.74659 2.16556,-2.94724 -3.43454,-0.58468 -2.63967,0.45586 -3.04384,-2.48312 -1.56436,2.8e-4 -3.65465,1.17085 -4.69308,8.6e-4 -0.85123,-0.95908 0.86545,-2.44305 1.58105,-3.50717 1.18764,-1.76606 2.96115,-3.10066 4.08003,-4.91106 5.48924,-8.88184 -2.19873,1.44113 -2.88812,-4.43772 -0.21626,-1.84413 2.23606,-2.96484 3.35409,-4.44726 -0.43526,2.04932 -5.52153,-1.4114 -4.49527,-2.86108 1.54224,-2.17853 4.97434,-2.83829 6.00046,-5.30235 0.48638,-1.16795 -3.85006,1.27155 -3.79552,0.008 0.29225,-6.77243 6.68102,-1.96905 2.60362,-4.63478 -1.03744,0.0157 -3.37206,1.05159 -3.11233,0.0471 0.4629,-1.79031 2.90142,-2.33554 4.07379,-3.76559 0.6145,-0.74957 0.86383,-1.74051 1.18878,-2.65368 0.33006,-0.92755 1.7161,-2.57839 0.77018,-2.8514 -7.79155,-2.2488 -8.87812,5.07596 -6.27386,-2.9532 -0.19249,-5.38304 7.51347,-6.23842 8.35735,-10.67915 0.32218,-1.6954 -4.83804,1.06359 -5.13804,-0.63588 -0.37881,-2.14591 3.18898,-3.10151 4.13147,-5.06624 0.37314,-0.77786 -0.50218,-1.68058 -0.48124,-2.54306 0.0239,-0.98457 0.40879,-1.92683 0.61318,-2.89025 -13.08296,-3e-5 0.34211,1.32306 -2.48426,-3.42912 -0.72627,-1.22112 -2.99658,0.75682 -4.26095,0.10876 -0.63537,-0.32567 0.43448,-1.404 0.35062,-2.11303 -0.59735,-5.05055 -8.30722,4.38127 5.7127,-6.54936 -0.0182,-1.2e-4 -4.80445,0.71053 -5.00217,-1.19016 -0.0931,-0.89458 1.35621,-1.63297 1.05882,-2.48178 -0.26951,-0.7692 -2.36655,-0.1317 -2.25749,-0.93942 0.21104,-1.56284 1.93828,-2.48832 2.89013,-3.74569 0.93981,-1.24147 1.8564,-2.50034 2.7846,-3.75051 -2.76858,-2.34245 -1.68916,-0.49431 -0.42062,-4.50395 0.27132,-0.8576 0.13894,-1.83132 0.51325,-2.64924 0.0839,-0.18345 4.09283,-3.81873 1.03007,-4.56276 -1.17187,-0.28469 -2.40782,0.1402 -3.61173,0.2103 0.44344,-0.69241 1.62548,-1.3098 1.33032,-2.07723 -0.27912,-0.72573 -1.74502,0.12468 -2.29333,-0.42664 -2.14536,-2.1571 2.94322,-3.721 4.63358,-7.16098 0.43562,-0.88652 -0.5755,-1.88984 -0.86326,-2.83476 1.38204,-1.92046 2.76407,-3.84091 4.1461,-5.76136 -1.12321,-2.72543 -5.51508,-1.70553 -6.87835,-3.97595 -0.64832,-1.07972 0.6824,-2.56943 0.29228,-3.76689 -0.32662,-1.00257 -2.37342,-1.18153 -2.24537,-2.22816 0.2216,-1.8113 4.5249,-3.42652 3.07232,-4.53103 -2.03388,-1.54653 -5.05737,0.73258 -7.58605,1.09887 3.45501,-4.83392 2.38586,-3.59475 6.51263,-8.25392 1.30105,-1.46891 3.68816,-2.41085 3.96198,-4.3539 0.1544,-1.09559 -3.26799,0.49128 -3.26173,-0.61512 0.0295,-5.20775 9.08516,-4.03102 -1.61053,-3.94352 1.95939,-1.79801 4.27984,-3.26861 5.87816,-5.39403 0.5383,-0.71582 1.049,-2.4148 0.1935,-2.67992 -2.17746,-0.6748 -4.83656,1.77094 -6.80983,0.62951 -1.14404,-0.66177 0.90074,-2.53951 1.7331,-3.56613 5.14504,-6.34577 8.38621,-4.56052 2.51601,-6.13981 -0.85199,-0.32077 -2.4474,-0.0584 -2.55599,-0.96231 -0.15992,-1.33111 1.81714,-2.15158 2.02274,-3.47641 0.10406,-0.6705 -0.95976,-0.99901 -1.23172,-1.62066 -0.25041,-0.57238 0.0184,-1.30737 -0.28754,-1.8521 -0.28092,-0.5002 -1.61971,-0.55889 -1.35127,-1.06589 0.82116,-1.55092 2.55255,-2.40897 3.82883,-3.61345 0.68857,-0.86315 3.1386,-2.32855 2.06571,-2.58945 -10.65886,-2.59195 -5.5639,9.83091 1.19302,-3.59935 0.81945,-1.62876 -6.02054,2.91187 -5.33113,1.22396 2.10963,-5.16508 13.61278,-6.44046 1.79364,-4.30704 0.21403,-0.73136 0.91552,-1.4828 0.64208,-2.1941 -0.26442,-0.68781 -1.27472,-0.73968 -1.91207,-1.10953 1.76068,-1.5448 5.50805,-2.30302 5.28205,-4.63441 -0.18401,-1.89836 -4.58115,2.58145 -5.63515,0.99188 -2.44934,-3.69394 14.48858,-5.96488 -2.32442,-2.54757 -1.7179,-1.85348 6.47741,-4.03448 5.12419,-6.41256 -0.5953,-1.04614 -2.55849,0.49122 -3.60971,-0.095 -0.53027,-0.29573 -0.24018,-1.38425 0.19132,-1.81139 2.01047,-1.99013 4.52431,-3.3972 6.78646,-5.0958 0,0 0.68933,-0.35123 0.68933,-0.35123 l 0,0 c -1.6505,1.38695 -3.301,2.7739 -4.9515,4.16084 1.18084,-0.0304 3.93897,-1.20381 3.54252,-0.0911 -0.76705,2.15293 -7.26975,3.74376 -5.1306,4.54845 4.99319,1.8783 18.98607,-10.22151 2.1677,2.69716 4.46028,0.74689 2.96356,-0.31341 -0.0889,3.77723 -0.63494,0.85088 -2.69281,2.50424 -1.65372,2.72204 2.33328,0.48909 4.58505,-1.63653 6.96887,-1.60791 0.61976,0.007 1.18756,1.25976 0.75189,1.70062 -2.49496,2.5247 -5.74404,4.17153 -8.61606,6.2573 12.12981,-2.41278 3.30013,-1.95214 3.56234,1.57757 0.054,0.72705 2.55848,-0.20768 2.15087,0.39678 -1.23294,1.82837 -6.14542,2.11983 -5.18729,4.10604 2.15892,4.47548 10.45489,-6.74524 4.2017,0.92413 2.15257,3.5125 1.16422,0.95894 -5.41535,6.17669 1.26621,2.35461 6.76352,-4.43669 7.77661,-1.96259 8.28333,20.22899 -28.85471,-8.50838 3.09217,3.12802 1.84928,0.67359 -2.68677,2.87671 -4.03016,4.31506 -0.92921,1.24117 -1.85842,2.48233 -2.78763,3.7235 0.58132,0.56011 1.79929,0.87498 1.74397,1.68034 -0.0987,1.43783 -1.97261,2.33537 -2.11827,3.76921 -0.0857,0.8441 1.28221,1.22332 1.52863,2.03519 1.49261,4.91758 -5.97031,3.63582 7.4952,1.34569 -0.55001,0.45931 -10.77994,8.64592 -11.73418,10.56561 -0.32217,0.64811 2.10161,0.84706 1.64937,1.41214 -2.09388,2.61633 -7.66175,2.96102 -7.82786,6.30795 -0.16991,3.4234 8.56273,-4.46712 10.27927,-0.27127 -1.76159,1.81971 -3.52318,3.63942 -5.28477,5.45913 0.76622,0.59813 2.4346,0.8319 2.29867,1.79438 -0.27968,1.9803 -3.80714,3.11034 -3.26317,5.0349 0.46282,1.63743 4.21131,-0.43752 4.99021,1.07532 3.93398,7.64091 -10.83273,3.14159 3.44758,5.72402 -0.9151,1.68254 -1.83021,3.36508 -2.74532,5.04762 -0.11662,1.15096 0.18214,2.42561 -0.34986,3.45288 -0.92725,1.79049 -2.95622,2.85717 -3.85317,4.66303 -1.3412,2.70028 4.81416,2.32838 5.47911,2.30305 -0.3941,0.74413 -1.64975,1.53203 -1.18229,2.23241 0.55364,0.82952 3.61797,-0.31799 2.96056,0.43196 -2.55013,2.90915 -6.77897,3.98142 -9.35743,6.86548 -0.53751,0.60121 1.34054,0.89766 1.99237,1.3725 3.00597,2.18979 0.67181,0.85532 4.1958,2.58013 0.97743,0.27897 3.12712,-0.1607 2.93229,0.83692 -0.37108,1.90014 -2.58726,2.88188 -3.93077,4.27586 -1.41178,1.46482 -5.0521,2.39525 -4.3329,4.29829 0.66993,1.77268 4.05322,-1.99686 5.61067,-0.91724 2.00902,1.39265 -6.05121,4.28837 -4.30844,5.93449 8.79644,-0.97427 2.46458,-1.29009 1.54772,2.85609 -0.55951,2.53017 5.57675,-0.6921 4.65189,1.63694 -4.43479,11.16791 -5.60958,0.57178 -4.52068,6.97527 -1.01107,1.36804 -2.96739,2.40428 -3.03319,4.10412 -0.0404,1.04402 1.88097,0.91014 2.82145,1.36521 0.80303,0.59941 2.46039,0.79749 2.40909,1.79825 -0.0133,0.25895 -5.43921,8.7383 -5.29443,9.14273 0.52067,1.4545 3.02467,0.63085 4.53701,0.94627 -0.64141,1.38413 -0.99081,2.94575 -1.92422,4.15238 -1.68564,2.17903 -8.12235,4.34484 -5.82411,5.86392 2.99789,1.98153 6.81296,-3.05371 10.40013,-2.83928 1.37703,0.0823 -2.60327,1.93867 -2.48051,3.31268 0.0835,0.9342 1.86713,0.1807 2.80069,0.27105 -2.14561,1.60336 -4.29121,3.20672 -6.43682,4.81008 3.30783,4.57067 -4.3191,-6.77248 -4.22041,5.45487 0.0138,1.70811 3.38667,-0.86784 5.08198,-0.6587 2.45197,0.30248 2.15812,5.02735 1.95842,6.42304 -3.19404,2.52625 -8.52248,3.64671 -9.58211,7.57875 -0.68106,2.52726 6.18106,-3.00764 7.79494,-0.94699 1.4073,1.79689 -3.96866,2.95153 -4.48356,5.17508 -0.24512,1.05851 2.84501,0.24565 2.9786,1.32394 0.65218,5.26442 -11.20032,5.62551 5.28419,2.39953 -0.67702,1.06264 -1.40702,2.09332 -2.03106,3.18792 -0.45821,0.80372 -1.7507,1.74672 -1.21035,2.49769 0.61904,0.86033 2.63525,-0.68635 3.17151,0.22788 0.50566,0.86208 -1.0545,1.6999 -1.52286,2.58281 -2.38314,4.49245 0.66315,0.65249 -5.01938,6.11325 2.51835,3.02589 0.23004,-0.58326 -2.18819,4.90305 -0.24233,0.54978 0.86402,0.916 0.99629,1.50207 0.16072,0.71214 -0.80005,1.82496 -0.16437,2.18397 1.16036,0.65534 2.71959,-0.39573 3.99784,-0.0189 0.60248,0.17759 0.73877,1.01601 1.10815,1.52402 -1.36074,1.34979 -3.20063,2.34749 -4.08221,4.04936 -0.27139,0.52391 1.05543,0.7318 1.64139,0.66261 1.97114,-0.23275 3.95028,-2.36609 5.75307,-1.53572 1.23577,0.5692 -2.05672,1.79651 -2.93984,2.8315 -0.48863,0.57266 -0.71854,1.32576 -1.13976,1.94967 -0.51361,0.76074 -1.1066,1.46475 -1.6599,2.19713 0.33873,0.23814 1.19514,0.34101 1.01619,0.71442 -0.51118,1.06668 -1.77446,1.57558 -2.54408,2.4738 -3.23585,3.77652 -0.50318,0.34945 -0.55933,2.92637 -0.0502,2.30217 -3.339,4.40022 -4.37743,5.39298 18.27135,-5.79195 7.34887,-3.82125 5.07084,1.06134 -0.30011,0.64325 1.3829,0.55521 1.80963,1.12243 0.39639,0.52689 0.0574,1.41202 0.47866,1.91923 2.55384,3.07493 4.72612,-2.11248 -1.30905,5.03921 0.59055,0.43479 1.11486,0.97814 1.77163,1.30437 0.77872,0.38681 2.79133,-0.004 2.4803,0.80765 -0.73427,1.91687 -3.09181,2.72119 -4.42871,4.27883 -3.95484,4.60782 -1.14208,11.46892 8.83243,0.8083 -2.78274,1.95014 -5.79944,3.60312 -8.34822,5.85044 -0.32391,0.28561 -0.30561,2.75922 0.50582,2.9271 1.34444,0.27816 4.07556,-1.66363 4.10843,-0.29112 0.0607,2.53601 -5.62402,2.95619 -4.29713,6.28092 2.63484,-0.56434 7.72239,-4.38145 7.90451,-1.69301 0.22273,3.28781 -5.39696,3.87479 -7.59009,6.33435 -0.4392,0.49256 -1.07168,4.84516 -0.91018,5.62296 0.13638,0.65682 1.15889,1.0274 1.08607,1.69426 -0.11092,1.01582 -1.03951,1.7596 -1.55927,2.6394 0,0 1.72998,-0.88147 1.72998,-0.88147 z"
555+ inkscape:connector-curvature="0" />
556+ <path
557+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
558+ id="path5276"
559+ d="m 271.40967,500.0231 c 0.84361,-1.11502 2.99222,-2.02518 2.53083,-3.34505 -0.4382,-1.25354 -3.99371,0.67052 -3.92942,-0.65585 0.10731,-2.21366 3.58902,-3.01826 4.21063,-5.14555 0.29949,-1.02491 -2.43266,-1.56527 -1.96867,-2.52695 1.35708,-2.81273 4.53987,-4.32256 6.46127,-6.78454 1.08733,-1.39324 1.65843,-3.12142 2.48765,-4.68213 -7.46636,-0.79001 -5.08221,1.28307 -4.90812,-4.06826 0.0316,-0.97271 -0.27942,-1.93215 -0.28908,-2.90532 -0.01,-0.98605 1.13592,-2.55748 0.23099,-2.94925 -1.80824,-0.78284 -6.00039,2.46658 -5.89015,0.49924 0.15148,-2.70327 4.10594,-3.5304 6.15891,-5.2956 0.0863,-0.32458 1.27486,-3.51563 0.28596,-4.10661 -1.01075,-0.60403 -5.10035,0.971 -2.93296,-2.24553 0.98758,-1.46562 4.18347,-2.1455 3.66196,-3.8341 -0.47465,-1.53684 -3.21662,0.0457 -4.82493,0.0686 2.53225,-3.24957 8.12982,-9.92119 3.34362,-13.96697 -0.84971,-0.71826 -2.89316,-0.3958 -2.97925,-1.50507 -0.13245,-1.70646 1.67665,-2.98445 2.51498,-4.47667 -0.58297,-0.66933 -1.16594,-1.33865 -1.74891,-2.00798 0.6124,-1.11344 2.66426,-2.37557 1.8372,-3.34034 -0.96917,-1.13053 -3.04522,0.75237 -4.45833,0.28279 -0.57763,-0.19194 -0.54641,-1.34538 -0.16369,-1.81869 1.64069,-2.02908 4.24365,-3.14716 5.84497,-5.20745 0.62197,-0.80025 0.35302,-1.99609 0.52954,-2.99413 1.18656,-1.71414 5.51829,-4.42825 3.55968,-5.14242 -3.68312,-1.34296 -7.74476,3.18213 -11.54765,2.2298 -1.60687,-0.4024 0.40917,-3.40618 1.31691,-4.79181 2.25208,-3.43772 11.80529,-7.67655 4.00813,-10.58092 -2.01538,-0.75071 -4.26368,-0.56779 -6.39552,-0.85169 18.31556,-19.35763 6.94798,-2.12539 4.25278,-10.10937 -0.45617,-1.35131 1.87569,-2.93051 1.10826,-4.13267 -0.81412,-1.27531 -3.9706,0.39684 -4.41614,-1.04908 3.1689,-5.21567 3.70236,-3.03245 1.23334,-6.62861 -8.18188,-0.007 -4.8163,1.36305 -2.0755,-4.06271 0.36854,-0.72956 0.7691,-1.82926 0.22758,-2.4415 -0.65309,-0.73837 -3.47767,0.21889 -2.89987,-0.57977 6.09343,-8.42267 10.79235,-3.56777 5.91985,-7.57855 -12.25386,-1.1019 4.27779,1.54129 1.65438,-6.69263 -0.80029,-2.51183 -5.25694,0.40437 -7.88541,0.60655 4.24469,-3.40941 9.75453,-5.67149 12.73406,-10.22823 0.98096,-1.50023 -4.44943,0.36646 -5.23091,-1.24669 -0.79604,-1.6432 1.46396,-3.34544 2.19595,-5.01817 -0.44562,-1.0569 -0.89124,-2.11379 -1.33686,-3.17069 1.21842,-1.89661 4.4227,-3.57023 3.65526,-5.68983 -0.096,-0.26526 -11.48,-0.72332 -11.66559,-0.80844 -0.84802,-0.38896 -1.35245,-1.93441 -0.79604,-2.68331 2.10967,-2.83952 5.32265,-4.66087 7.98397,-6.99131 2.34226,-3.02177 -2.3457,-2.92502 -2.13068,-5.33277 0.0676,-0.75729 1.50987,-1.53029 0.96915,-2.06479 -2.53936,-2.51012 -10.63271,6.31581 2.37228,-4.61883 -6.19474,1.66146 -9.1703,5.00092 -7.46109,-0.21769 0.33443,-1.02109 0.72495,-2.03797 1.27773,-2.95934 0.83764,-1.39618 2.56915,-2.35267 2.86929,-3.95293 0.18473,-0.98495 -4.34465,-3.06502 -4.46788,-3.13006 -0.99349,-2.65488 2.28337,-4.28846 2.23274,-6.61519 -0.008,-0.38838 -3.79592,-3.07607 -3.34144,-3.91339 1.29663,-2.38888 5.23411,-3.12425 5.73612,-5.79557 0.28158,-1.49837 -3.02289,-0.39958 -4.53434,-0.59936 -0.73879,-0.56327 -2.69562,-0.89392 -2.21638,-1.6898 1.57771,-2.62017 4.86309,-3.77022 6.8856,-6.06454 0.60727,-0.68889 1.19228,-1.81559 0.7877,-2.64001 -0.39219,-0.79916 -1.88839,-0.25903 -2.51884,-0.88752 -3.52309,-3.51214 5.42905,-1.06978 -3.64123,-2.45646 -1.28883,-1.47023 4.42724,-4.22495 3.00729,-5.92455 -0.87349,-1.04551 -4.62824,0.79539 -4.06292,-0.44416 1.2864,-2.82067 5.3809,-3.45971 7.0382,-6.07971 0.48077,-0.76003 -2.25924,-0.53635 -2.2845,-1.43532 -0.0391,-1.39345 1.42887,-2.394 2.14331,-3.591 0.57897,-1.13977 2.39829,-2.3253 1.7369,-3.4193 -0.70437,-1.16508 -2.77891,0.0285 -4.06202,-0.42667 -4.36529,-1.54853 1.70005,-4.13301 2.00734,-5.56729 0.1857,-0.86677 -1.69451,-0.52131 -2.54176,-0.78196 1.09797,-1.11213 2.19594,-2.22425 3.29391,-3.33638 -7.62803,0.34657 -0.62323,0.76416 0.31659,-3.27246 0.22707,-0.97527 -1.99998,0.10431 -2.99997,0.15647 1.88627,-1.35807 4.42344,-2.10539 5.65881,-4.0742 0.6094,-0.9712 -2.25983,0.52665 -3.40556,0.48318 -0.49695,-0.0188 -0.89097,-0.44207 -1.33646,-0.6631 -5.22674,-0.89819 -1.26314,0.31771 4.36293,-5.17642 0.40557,-0.39606 0.69638,-1.64645 0.13163,-1.69553 -1.86658,-0.16222 -5.82482,3.07279 -5.48453,1.23034 0.86459,-4.68123 15.40434,-8.00968 -0.85374,-3.14014 -0.53032,-0.34481 -1.68223,-0.40848 -1.59097,-1.03442 0.19075,-1.30836 3.0586,-2.26352 2.22094,-3.2865 -0.93884,-1.14654 -2.98163,1.04666 -4.40173,0.62324 -0.64237,-0.19154 0.3747,-1.67926 -0.21416,-1.99954 -5.42716,-2.95183 -2.51001,2.85865 -4.39836,-2.21361 -1.0113,-2.24228 -4.57355,0.1204 -5.66196,-1.50056 -0.59485,-0.88591 1.78009,-2.58036 0.84181,-3.08859 -1.49807,-0.81144 -3.4198,1.31804 -5.04636,0.81115 -0.75563,-0.23548 0.0205,-1.5828 0.0307,-2.37421 -0.78514,-0.23306 -1.57029,-0.46613 -2.35544,-0.6992 -18.82244,6.26303 -1.07068,2.06578 -7.70098,1.1906 -1.1255,-0.14856 -3.19802,1.33749 -3.39866,0.22011 -0.0112,-0.0625 5.46172,-7.20216 5.52409,-7.28374 0,0 -1.31152,0.82398 -1.31152,0.82398 l 0,0 c 0.10548,0.55469 -0.0164,1.20797 0.31643,1.66406 1.996,2.73517 7.61323,-1.59563 8.31169,-1.60148 0.86632,-0.007 -0.60768,1.62267 -0.91151,2.43401 1.32261,-0.19648 2.64522,-0.39296 3.96783,-0.58944 0.98298,4.11711 -0.6215,0.21591 4.5331,1.56511 3.1779,0.83181 -0.81124,5.16423 -0.63379,5.49385 1.18549,2.20203 6.22576,-1.97788 7.49509,0.33556 0.0419,3.06555 -0.51194,2.13147 1.3249,4.21502 0.41138,0.46665 0.67506,1.34969 1.2971,1.34182 1.97297,-0.025 4.46542,-2.93396 5.74383,-1.43099 1.16055,1.36441 -4.40317,2.5692 -3.47341,4.10022 0.93565,1.54072 3.52951,-0.73456 5.29427,-1.10184 0.24843,0.52021 0.45786,1.06092 0.74531,1.56063 0.28443,0.49446 1.19772,0.89213 0.96528,1.41305 -0.6129,1.37359 -2.07256,2.18119 -3.071,3.30614 -0.81192,0.9148 -3.20997,1.93322 -2.35835,2.81118 1.02778,1.05958 2.91649,-1.02103 4.36594,-0.74149 0.51104,0.0986 -0.10293,1.03582 -0.15439,1.55373 -0.90402,0.90742 -2.11634,1.58835 -2.71205,2.72228 -0.18254,0.34745 0.46747,0.83858 0.85858,0.80575 1.71116,-0.14364 3.53033,-2.2076 4.98576,-1.29633 1.08463,0.6791 -1.95527,1.68725 -2.70466,2.72456 -0.3822,0.52905 -0.40546,1.24075 -0.6082,1.86113 -0.73879,0.89107 -1.47759,1.78213 -2.21639,2.6732 -2.6156,4.34603 -1.72804,1.25734 2.28963,2.6141 0.6603,0.22298 1.61939,0.89052 1.4007,1.55225 -0.52966,1.60271 -3.05305,2.23698 -3.20843,3.91777 -0.0904,0.97838 1.83173,0.71158 2.7476,1.06737 -0.14246,0.9235 -0.28492,1.84701 -0.42738,2.77052 -17.69737,16.4527 -0.29839,-1.93789 0.46659,4.845 0.29603,2.62482 -4.02236,3.45288 -5.75948,5.44278 -2.96193,3.39292 -2.80901,4.63463 0.58667,6.56553 -0.71195,3.37109 -1.32723,2.23656 2.17358,3.20427 -0.25398,1.70167 -3.37311,2.17554 -3.38566,3.89601 -0.009,1.22027 7.05377,-0.32166 6.38009,1.54413 -0.87889,2.4341 -3.60347,3.71541 -5.4052,5.57312 1.47272,0.21953 3.73062,-0.66216 4.41814,0.6586 1.2021,2.30926 -4.75905,5.20232 -5.26915,8.38805 -0.29809,1.8617 8.33922,2.21527 5.25308,5.58725 -2.34657,2.11623 -4.82208,4.0977 -7.03972,6.34868 -1.35667,1.37707 -4.99171,3.11027 -3.65263,4.50445 1.62643,1.69336 4.64369,-1.4155 6.96405,-1.05677 0.72132,0.11151 -0.0472,1.51283 0.26216,2.17392 1.59106,3.40017 3.61933,-1.30155 -1.31458,4.69517 1.74643,5.18425 -1.91013,-4.03341 4.14083,1.28591 0.65697,0.57753 -0.7704,1.59325 -1.34315,2.25439 -1.20653,1.39272 -3.31939,2.13238 -3.95537,3.86181 -0.27479,0.74726 1.97176,0.24327 2.16143,1.01653 0.26163,1.06662 -0.77015,2.05703 -1.15523,3.08554 0.46488,0.70316 1.66986,1.31273 1.39465,2.10948 -0.70049,2.02806 -5.77724,4.09808 -3.89369,5.12569 2.954,1.6116 6.67212,-2.6877 9.92708,-1.83407 1.38407,0.36298 -0.26721,3.00285 -1.1364,4.13947 -7.69167,10.05825 -11.30113,4.9757 -5.50453,10.47003 12.1809,2.58454 0.68305,-1.60595 1.50332,7.04834 0.15075,1.59053 3.7325,0.0369 4.58818,1.38612 0.74593,1.17615 -1.03929,2.62565 -1.16411,4.01279 -0.0314,0.34944 0.93404,4.71611 1.05781,5.28963 -1.3802,1.51721 -4.84437,2.62508 -4.14062,4.55164 0.63712,1.74412 3.69816,-1.18051 5.51394,-0.79214 0.65481,0.14005 0.6238,1.53115 0.149,2.00335 -2.81355,2.79816 -8.04318,3.53205 -9.44682,7.2436 -0.82141,2.17201 11.13635,-5.11164 6.76838,1.64927 0.091,0.92514 -0.0854,1.91766 0.27296,2.77541 0.35755,0.85586 1.9948,1.23647 1.78003,2.13881 -0.48142,2.02267 -3.23036,3.09296 -3.51545,5.15249 -0.15382,1.11121 1.86813,1.33099 2.48861,2.26557 2.10332,3.16805 0.92404,4.31695 0.06,7.89324 -10.57418,17.09201 -0.63343,-3.00266 3.38288,6.60571 0.99622,2.38331 -2.8287,4.32307 -4.24305,6.48461 -0.25211,1.50129 -1.38497,3.11742 -0.75634,4.50387 0.56327,1.24229 2.56202,1.0317 3.64324,1.86327 0.79588,0.6121 1.24,1.57949 1.86,2.36924 -8.39878,9.58005 -3.85858,2.27949 2.00132,3.93387 0.86536,0.24431 -0.51486,1.78055 -1.10053,2.46285 -1.42711,1.6626 -4.03733,2.4121 -4.83549,4.45265 -0.34986,0.89445 1.79295,0.68927 2.68943,1.03391 -1.31854,1.63819 -2.63708,3.27639 -3.95562,4.91458 0.66656,0.72544 1.92921,1.19368 1.99967,2.17633 0.0985,1.37395 -1.25826,2.48092 -1.56948,3.82278 -0.31391,1.35345 0.11788,4.84884 0.11683,6.22575 -8e-4,1.05328 -0.46388,2.14171 -0.17615,3.15493 0.24138,0.85002 1.10612,1.3783 1.65918,2.06746 7.82248,2.82758 -3.54352,-2.1669 -3.75729,6.27252 -0.0553,2.18496 4.23975,-1.72368 6.40311,-1.4122 0.77116,0.11103 -0.28142,1.56687 -0.68022,2.23618 -0.55535,0.93205 -4.91731,6.04302 -5.54138,6.78439 1.57917,-0.0855 3.18481,-0.55678 4.73749,-0.2564 0.79773,0.15433 2.07487,0.7473 1.89058,1.53864 -0.42269,1.81501 -2.92768,2.68693 -3.29992,4.51295 -0.18906,0.92743 1.54302,1.20422 1.96139,2.05324 1.93613,3.92917 -1.20747,5.21438 -3.71401,8.46144 0.47992,0.89368 0.58071,2.14155 1.43974,2.68104 1.34606,0.84534 4.13572,-0.37781 4.63211,1.13218 0.93525,2.84501 -5.60155,6.20062 -3.45815,8.29218 1.10289,1.07621 3.61454,-0.86631 4.61266,0.30772 0.75374,0.88657 -1.21106,1.98741 -1.81659,2.98112 0,0 -9.14493,4.65958 -9.14493,4.65958 z"
560+ inkscape:connector-curvature="0" />
561+ <path
562+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
563+ id="path5278"
564+ d="m 581.73672,201.15421 c -5.84049,0.0226 -12.46977,3.74174 -18.23522,1.43917 -0.92495,-0.3694 -1.5343,-1.27727 -2.36778,-1.82253 -0.87794,-0.57434 -1.83477,-1.01794 -2.75215,-1.52691 -7.8597,-0.6834 -0.70651,-0.5885 -11.70003,2.01168 -1.54963,0.36652 -3.24586,1.07687 -4.74618,0.54324 -1.59834,-0.5685 0.85485,-5.45684 0.64497,-5.42188 -3.26191,0.54332 -5.93446,2.91946 -8.90169,4.37918 -1.07703,0.12394 -2.49605,1.16872 -3.23111,0.37181 -0.67458,-0.73134 1.68867,-2.13419 0.97042,-2.82271 -0.88828,-0.85153 -2.51436,-0.15881 -3.68494,0.22051 -3.53501,1.14552 -6.56729,4.25069 -10.28286,4.30575 -1.41951,0.021 0.58867,-2.77764 0.88301,-4.16646 -2.91053,0.68366 -5.82106,1.36733 -8.73159,2.05099 -8.55726,-7.49791 7.12666,4.81414 -14.40115,1.39085 -1.87303,-0.29784 3.21179,-4.26845 1.70996,-5.42666 -1.91086,-1.47365 -4.74198,0.90779 -7.13129,1.24574 -10.05799,1.42263 -1.29042,-0.848 -12.92141,2.92188 5.75912,-7.83975 5.57265,-5.71046 -3.55664,-2.09752 -2.02766,0.80245 -4.06024,1.61861 -6.16523,2.18815 -1.4706,0.39791 -3.76794,2.02901 -4.51623,0.70196 -7.88504,-13.98369 9.26915,0.86687 0.0352,-6.49718 -3.10338,1.17026 -6.20676,2.34052 -9.31014,3.51078 -0.57567,-0.69237 -0.83006,-2.15625 -1.72699,-2.0771 -3.09746,0.27333 -5.72754,2.79919 -8.82997,3.00868 -0.91247,0.0616 -0.59156,-2.08023 -1.47605,-2.31278 -1.66146,-0.43682 -4.08937,1.95174 -5.12124,0.57823 -0.99973,-1.33072 1.45413,-2.99444 2.18119,-4.49166 -5.27131,3.01667 -10.21343,6.70024 -15.81393,9.05001 -0.70494,0.29576 0.0899,-1.64484 -0.3563,-2.26558 -0.50641,-0.70446 -1.36894,-1.2616 -2.23327,-1.33677 -1.86367,-0.16207 -3.74223,1.04035 -5.57342,0.65787 -1.06648,-0.22276 -1.54432,-1.53723 -2.31648,-2.30585 -2.11262,0.0407 -4.22524,0.0813 -6.33786,0.12201 -12.42253,2.1242 -6.53573,2.91676 -15.18382,-0.38034 -1.73617,-0.66191 -3.38695,-1.91563 -5.24481,-1.8878 -3.53288,0.0529 -6.89889,2.69571 -10.38526,2.12195 -1.37022,-0.2255 -0.74241,-2.67624 -1.11361,-4.01436 -1.88368,-0.006 -3.76736,-0.012 -5.65104,-0.018 -24.28983,7.62107 -2.5727,3.42937 -7.44036,-1.19304 -1.17426,-1.1151 -3.36042,0.76142 -4.85605,0.14059 -0.9471,-0.39314 -0.37753,-2.78281 -1.40202,-2.73831 -17.07323,0.74169 -4.68269,5.70713 -13.15884,1.68512 -13.47711,1.23798 -0.53507,0.38388 -11.61335,0.31509 -1.85578,-0.0115 -3.70698,0.21371 -5.56273,0.2289 -1.65286,0.0135 -3.33144,-0.4439 -4.95669,-0.14277 -2.69057,0.49853 -5.20719,1.68414 -7.81614,2.50939 -2.13831,0.67638 -4.28365,1.33032 -6.42547,1.99548 -0.9837,-2.83624 -11.06653,5.91354 -9.75378,2.79645 1.53204,-3.63777 6.99404,-4.59873 8.4956,-8.24918 0.86063,-2.09228 -4.2097,1.67823 -6.21979,2.71644 -3.72203,1.92244 -6.92828,5.00691 -10.95697,6.15539 -1.36693,0.38967 1.46202,-2.43801 2.19303,-3.65701 -1.17023,0.0909 -2.53198,0.92049 -3.51069,0.27257 -0.68695,-0.45477 0.96331,-2.14961 0.20152,-2.46328 -4.37993,-1.80347 -8.71503,6.31425 -13.28098,5.05394 -9.4835,-2.61768 17.38499,-9.14192 -0.38289,-4.16034 -0.53601,-0.28526 -1.00462,-0.92342 -1.60803,-0.85578 -1.6592,0.18598 -3.16377,1.06881 -4.75661,1.56922 -1.28969,0.40517 -2.59751,0.75066 -3.88989,1.14719 -1.27067,0.38987 -2.49478,0.95467 -3.7988,1.21188 -0.57067,0.11256 -1.73101,0.46238 -1.74093,-0.1192 -0.0174,-1.01834 1.13036,-1.69456 1.69554,-2.54183 1.13752,-1.49935 -4.63996,2.07583 -4.03582,0.84154 0.66217,-1.35285 4.65116,-3.58371 3.19622,-3.19408 -2.64341,0.70788 -4.51559,3.1669 -7.03997,4.22341 -0.68424,0.28637 0.85778,-1.21035 1.28667,-1.81553 -1.25078,0.65196 -2.50155,1.30391 -3.75232,1.95586 9.21621,-7.98762 6.18858,-4.83213 -0.87423,-0.87619 -0.72387,0.40544 -1.54218,0.6125 -2.31326,0.91875 0,0 2.61292,-1.89841 2.61292,-1.89841 l 0,0 c 1.14285,-0.57635 2.16869,-1.50316 3.42856,-1.72905 0.47365,-0.0849 -0.48687,0.83017 -0.73031,1.24525 0.72034,-0.25777 1.44068,-0.51554 2.16102,-0.77331 -0.0129,0.26612 0.11092,0.57784 -0.0386,0.79836 -0.61275,0.9037 -3.19334,2.83016 -2.16774,2.45563 2.58273,-0.94316 4.57145,-3.1023 7.07165,-4.24644 0.52365,-0.23963 -1.37048,1.87365 -0.20579,1.9951 0.74974,0.0782 1.47219,-0.32486 2.20828,-0.48729 1.12167,-0.37799 2.86257,-2.20567 3.36502,-1.13396 0.5616,1.19787 -1.995,1.89173 -2.42282,3.14363 -0.14298,0.41839 0.66423,0.8732 1.09039,0.75536 2.14236,-0.59239 4.04954,-1.83414 6.08018,-2.73811 7.30472,-3.2518 6.963,-5.26487 6.23711,-0.62098 2.93608,2.63023 -0.0453,0.63795 6.49918,-0.28738 0.87818,-0.12417 1.74981,0.3045 2.63356,0.3793 1.00939,0.0854 2.02969,0.20481 3.03732,0.10065 1.47374,-0.15233 3.07086,-1.5959 4.35975,-0.86523 0.8671,0.49155 -0.53804,1.91949 -0.80707,2.87923 1.97363,-0.76926 3.95449,-1.52021 5.92088,-2.30778 1.9543,-0.78272 3.79068,-1.92385 5.84067,-2.40289 0.58192,-0.13598 1.89487,0.0929 1.67184,0.64736 -0.76788,1.90882 -4.79687,2.97497 -3.86747,4.81058 1.48135,2.92575 7.32339,-5.50614 9.48936,-2.59649 1.42331,-0.13101 2.86231,-0.14485 4.26993,-0.39303 2.16768,-0.38218 4.23629,-1.34177 6.43214,-1.49385 1.38899,-0.0962 2.73015,0.97425 4.10649,0.76394 3.11854,-0.47655 5.9479,-3.356 9.04853,-2.77417 1.44152,0.2705 -2.0691,4.5208 -0.61155,4.35735 4.97883,-0.55833 9.23228,-3.89449 13.84842,-5.84174 2.70996,5.53973 -0.455,1.31529 7.32522,2.53772 1.08502,0.17048 1.78761,1.32193 2.81267,1.71639 3.87458,1.49101 7.92222,0.0734 11.83057,1.37936 2.68235,-0.56484 5.31262,-1.88683 8.04704,-1.69452 1.35852,0.0955 2.16027,1.65891 3.24041,2.48837 2.61456,-0.0998 5.22912,-0.19951 7.84368,-0.29927 4.64473,-0.95021 8.88553,-3.87351 13.62142,-4.09202 1.60186,-0.0739 2.33226,2.20143 3.49839,3.30214 2.94984,-0.29944 5.89968,-0.59888 8.84953,-0.89833 1.33758,0.50801 2.58403,1.44687 4.01276,1.52403 2.28088,0.12319 5.73312,-2.96689 6.78773,-0.94072 1.14605,2.20185 -6.4441,5.77702 -4.01202,6.27356 3.87161,0.79045 6.92622,-4.10745 10.7999,-4.88771 0.91737,-0.18479 -1.29282,2.74099 -0.35803,2.78445 2.40459,0.11179 4.52016,-1.65725 6.78024,-2.48588 1.31945,0.0101 2.75475,-0.51033 3.95836,0.0304 0.78176,0.35121 0.32942,2.27564 1.18643,2.28098 2.67859,0.0167 5.04481,-2.12584 7.72159,-2.22554 0.94208,-0.0351 1.26601,1.48553 2.13182,1.8585 3.60961,1.55491 6.53653,0.002 10.21237,-0.83478 20.52329,-6.74084 1.89226,-2.51045 4.15948,3.45256 0.73899,1.94361 4.01445,-1.09539 5.98399,-1.76219 2.23019,-0.75505 4.26124,-2.7115 6.60629,-2.50033 1.06227,0.0957 -1.16158,1.92736 -1.13013,2.99346 0.0199,0.67426 0.84334,1.05302 1.26501,1.57952 4.12862,-2.00822 7.89397,-5.07526 12.38586,-6.02468 1.10106,-0.23272 -0.91579,3.05361 0.16356,3.37219 2.3053,0.68043 4.7056,-1.09801 7.10578,-1.22658 1.70439,-0.0913 3.40092,0.83038 5.0883,0.57341 3.51787,-0.53576 6.7499,-3.43107 10.27034,-2.91244 1.4619,0.21537 0.033,2.95518 0.0495,4.43277 0.30181,1.19077 -0.20039,3.03734 0.90543,3.57231 1.66477,0.80539 3.68366,-0.33775 5.52901,-0.45944 5.57141,-0.36741 4.65262,0.33142 10.53807,-2.04259 2.8186,-1.13693 5.21399,-3.79318 8.25207,-3.87782 1.47239,-0.041 -1.59556,2.4764 -2.39334,3.7146 0.73439,-0.24086 6.01373,-2.85719 7.87461,-1.29776 0.0775,0.0649 0.89268,4.62211 1.49732,4.43526 14.78199,-4.56792 12.68988,-12.15571 13.93555,-3.63588 2.66352,6.81078 -1.20811,-0.39314 10.26637,-0.38966 1.05436,3.1e-4 -0.57443,2.7161 0.39151,3.13875 2.9451,1.28864 13.55718,-4.08658 14.2252,-4.14987 0.94238,-0.0893 -0.4954,1.82723 -0.7431,2.74084 0,0 -1.4256,1.03576 -1.4256,1.03576 z"
565+ inkscape:connector-curvature="0" />
566+ <path
567+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
568+ id="path5280"
569+ d="m 437.91913,154.10856 c 1.23619,-0.0682 2.47239,-0.13636 3.70859,-0.20454 1.58967,0.0869 3.17891,0.33934 4.76902,0.2608 2.60001,-0.1284 5.26923,-1.63681 7.75984,-0.87957 1.37606,0.41837 0.54192,3.9983 1.97124,3.83817 35.88963,-4.02092 2.82269,-7.22874 20.51937,-5.03347 3.69136,2.35282 7.48018,-1.04865 11.12799,-0.36496 0.89607,0.16795 0.44139,2.40963 1.35248,2.3772 3.04143,-0.10825 5.79477,-1.86385 8.71066,-2.73538 3.21228,-0.96013 6.53974,-3.95409 9.67225,-2.75912 1.88961,0.72083 -0.98785,3.92236 -1.48177,5.88355 7.4778,-2.6997 15.0486,-0.18493 22.64137,-0.87945 2.98582,-0.27312 5.86817,-1.42005 8.86462,-1.52519 0.64451,-0.0226 7.36205,1.74657 8.38695,2.01041 22.66233,-6.2164 -5.59406,-0.006 1.79508,4.88026 3.01566,1.99414 6.60785,-3.07778 10.1398,-3.84989 1.21885,-0.26645 2.41213,0.66777 3.64842,0.83569 1.51909,0.20634 3.07181,0.49347 4.59038,0.28337 2.64393,-0.36579 5.17049,-1.32765 7.75573,-1.99148 4.22728,5.71577 -1.93151,-1.13286 8.0372,1.02932 0.90762,0.19686 0.25793,1.83945 0.38689,2.75918 3.89585,-1.98477 7.40915,-5.05301 11.68754,-5.95432 1.46572,-0.30877 -2.98474,2.70083 -2.15115,3.94534 0.81315,1.21397 2.88525,-0.46374 4.32788,-0.69561 -0.19386,1.02948 -1.62892,3.11096 -0.58158,3.08846 3.16055,-0.0679 5.77676,-2.79974 8.90328,-3.26728 1.17277,-0.17538 2.07855,1.64352 3.24848,1.4501 3.6131,-0.59735 6.7766,-2.87648 10.3455,-3.69758 2.04298,-0.47003 7.74632,0.36486 9.92961,0.62392 1.54872,0.10745 3.32447,-0.49201 4.64618,0.32236 0.83502,0.51449 -0.41383,2.96447 0.56398,2.88785 3.16131,-0.24772 5.68039,-3.03528 8.79677,-3.62136 0.63863,-0.1201 -0.0278,2.00105 0.60497,1.85324 13.56341,-3.16818 3.77193,-4.72057 10.23057,-3.37752 1.24491,-0.0989 2.70181,-0.99864 3.73472,-0.29674 0.77133,0.52414 -0.93495,2.48789 -0.0552,2.79714 1.80758,0.63537 3.81902,-0.40231 5.6897,-0.81658 2.85096,-0.63135 5.54036,-2.05462 8.44881,-2.31443 1.41531,-0.12642 2.67478,1.09344 4.09215,1.19417 2.08296,0.14804 4.16519,-0.30621 6.24779,-0.45931 3.35928,-1.08463 6.71857,-2.16926 10.07785,-3.25389 0.55553,0.85196 0.64963,2.54065 1.6666,2.55589 2.67042,-0.80363 5.34085,-1.60725 8.01128,-2.41088 0.47122,0.60211 0.67697,1.60175 1.41368,1.80633 1.24445,0.34558 4.10822,-1.60936 3.85951,-0.34198 -0.48472,2.4701 -3.27807,3.82095 -4.91711,5.73142 5.70103,-1.33877 10.12612,-5.88484 15.70457,-7.45129 0.58355,-0.16386 -0.0962,1.23344 0.0671,1.81715 0.12902,0.46112 0.31209,1.10095 0.77906,1.20689 3.35426,0.76105 3.63331,-2.23898 2.23264,1.86594 4.56137,3.27917 -3.43228,-1.96059 10.56193,-4.51534 1.97581,-0.3607 -3.49921,2.45408 -4.17344,4.34599 -0.32903,0.92328 1.91845,-0.40858 2.86324,-0.66949 3.86803,-1.06818 2.71345,-2.07285 4.06916,-0.29377 0.65874,-0.0329 2.02115,-0.75681 1.97621,-0.0988 -0.0787,1.1527 -3.30667,2.8058 -2.15487,2.7149 2.30435,-0.18187 4.05842,-2.25648 6.23422,-3.03687 0.64315,-0.23067 1.38324,0.22087 2.04874,0.0661 5.51896,-1.28379 3.78688,-3.25824 5.50883,-0.19721 -1.46583,1.71812 -4.57212,2.90268 -4.39748,5.15437 0.0156,0.20143 8.67506,-2.31725 8.69122,-1.51312 0.0418,2.08276 -5.97008,4.93862 -3.88739,4.89336 7.80888,-0.16971 16.70616,-11.4037 6.31714,0.11757 0.90308,0.0651 1.80616,0.1302 2.70924,0.1953 0.27814,0.42273 0.34662,1.1336 0.83442,1.26817 0.95893,0.26455 2.42441,-1.04441 2.97636,-0.21683 0.53135,0.79669 -2.30924,1.98837 -1.47786,2.46364 3.33256,1.90507 5.5039,-3.90126 3.07872,1.08059 4.23139,0.93002 -3.61451,3.89543 -2.08482,4.59396 2.0287,0.9264 4.18748,-2.97245 6.29916,-2.25506 1.45192,0.49325 -4.18448,2.13801 -3.19514,3.30956 1.96861,2.33118 11.74376,-7.47434 0.389,2.57794 -0.0375,0.53551 -0.64336,1.68584 -0.11242,1.60654 9.29633,-1.38852 9.7283,-6.25852 2.73533,1.82238 -0.71864,0.83044 -2.31993,2.03153 -1.61359,2.87246 4.45015,-0.20547 3.30627,-1.34009 4.63554,1.36899 6.1752,-0.54264 -0.212,2.16231 3.94226,3.83059 0.70833,0.28445 2.2706,-0.38153 2.25791,0.38168 -0.0534,3.21284 -8.33635,4.79542 2.76754,1.28484 0.83075,1.91899 -2.55661,2.61469 -2.23156,4.1519 0.16395,0.77534 1.75121,-0.54192 2.37683,-0.0555 0.43233,0.33616 -0.62952,1.09756 -0.39821,1.59396 0.32296,0.69309 3.34908,0.74132 3.53405,0.75722 -6.37567,7.34986 -1.26749,0.14391 -0.0687,2.59487 0.35848,0.73292 -0.86775,1.38193 -1.30163,2.07289 0,0 2.83108,-1.71491 2.83108,-1.71491 l 0,0 c -1.62573,0.55271 -3.171,1.46474 -4.87719,1.65811 -1.86292,0.21113 3.92214,-13.77688 3.30994,-4.54751 -0.43697,-0.22138 -0.82145,-0.64501 -1.31093,-0.66414 -1.08331,-0.0423 -2.45436,1.25905 -3.21571,0.48724 -0.62898,-0.63763 1.2009,-1.38762 1.49202,-2.23463 0.1294,-0.37651 0.0634,-1.08308 -0.32938,-1.14805 -1.07257,-0.17743 -2.83327,1.58094 -3.21263,0.56213 -0.4318,-1.15963 1.56848,-1.91432 2.35272,-2.87148 -2.08529,0.77586 -4.04044,2.122 -6.25586,2.3276 -1.2483,0.11584 1.87343,-2.85833 0.13022,-3.36272 -1.25443,-0.36296 -2.64297,0.7181 -3.9005,0.36606 -1.98283,-0.5551 0.33136,-8.6675 2.44939,-5.67082 -0.11377,-0.59785 0.0423,-1.32107 -0.34132,-1.79354 -0.44536,-0.54858 -1.18502,-0.96523 -1.89156,-0.95682 -1.75728,0.0209 -4.10153,2.43729 -5.16845,1.04082 -0.97742,-1.27933 2.58059,-2.13226 3.19884,-3.6188 0.35667,-0.85759 -3.97088,-0.70327 -0.94285,-2.62205 -0.30912,-0.2909 -1.07662,-0.47532 -0.92737,-0.87269 0.41542,-1.10604 3.15171,-1.92448 2.27577,-2.71734 -1.06356,-0.9627 -2.76722,1.17347 -4.18937,0.98523 -0.50454,-0.0668 0.67442,-1.53777 0.16587,-1.5178 -2.00217,0.0786 -4.22172,3.02759 -5.75464,1.73723 -1.28523,-1.08186 2.27523,-2.47231 3.41284,-3.70846 1.69334,-3.79201 1.02434,-0.89294 -2.96887,-1.10444 -0.43164,-0.0229 -0.70644,-0.64888 -0.71619,-1.08101 -0.0159,-0.70575 1.25738,-1.66328 0.64632,-2.01675 -3.18866,-1.84449 -5.78111,4.03437 -1.35578,-2.85334 -2.21776,-1.64947 -1.21018,-1.37236 -4.79348,-1.13862 -1.39043,0.0907 -2.84666,0.9726 -4.15193,0.48495 -0.65871,-0.2461 0.41497,-1.72217 -0.17671,-2.10214 -1.42729,-0.91658 -3.70494,0.93947 -5.03484,-0.73883 -0.60299,-0.21393 -1.16997,-0.67416 -1.80895,-0.64178 -1.28889,0.0653 -2.74506,1.72143 -3.75975,0.924 -0.80647,-0.63381 2.20098,-1.84945 1.56159,-2.65151 -0.65242,-0.81839 -2.00917,1.00735 -3.03556,0.80256 -0.45187,-0.0902 0.14067,-0.91075 0.21101,-1.36613 -1.21839,0.43181 -2.43678,0.86363 -3.65517,1.29544 -0.25354,-0.22264 -0.62939,-0.35707 -0.76062,-0.66793 -0.17042,-0.40368 0.34819,-1.20015 -0.0754,-1.31238 -1.8936,-0.50174 -8.19894,3.49373 -8.31461,3.19586 -0.81569,-2.10049 4.56795,-2.57491 4.73782,-4.82182 0.12012,-1.58882 -2.99734,1.0822 -4.49601,1.6233 -2.08036,0.93553 -4.14334,1.9107 -6.24108,2.80658 -1.80069,0.76903 -4.53985,3.91984 -5.45174,2.18712 -0.97455,-1.85178 3.0537,-2.94119 4.10168,-4.75242 0.26628,-0.46021 0.0615,-1.507 -0.46996,-1.52428 -1.58559,-0.0515 -2.99345,1.11425 -4.56062,1.36077 -0.79363,0.12484 -1.59692,-0.17771 -2.39537,-0.26657 -4.2548,2.37378 -7.90174,6.81699 -12.7644,7.12133 -2.94666,0.18442 3.87288,-4.45736 5.80931,-6.68603 -0.48591,-0.59114 -0.69261,-1.78416 -1.45775,-1.77341 -2.51475,0.0353 -4.81733,1.51195 -7.30276,1.89646 -5.06126,0.783 -4.67311,0.33438 -8.50322,-1.66221 -3.47983,1.1729 -6.95966,2.3458 -10.43949,3.5187 -0.30549,-1.1707 0.29338,-3.52556 -0.91645,-3.51209 -3.62555,0.0404 -6.62575,3.65582 -10.25148,3.63642 -1.23853,-0.007 1.66672,-3.89226 0.44498,-3.68891 -4.09903,0.68224 -7.13798,4.59273 -11.19787,5.47861 -1.12856,0.24626 0.7464,-2.18633 1.1196,-3.2795 -2.70903,1.27901 -5.1708,4.32182 -8.12711,3.83702 -1.90269,-0.31201 3.59746,-2.66683 3.5158,-4.59321 -0.0525,-1.23781 -2.45976,0.30037 -3.69255,0.4234 -1.46662,0.14638 -2.9419,0.20626 -4.40496,0.38477 -10.62024,1.29579 -0.63827,0.54815 -11.1481,1.16065 -2.80818,0.75238 -5.61635,1.50477 -8.42453,2.25716 0.42608,-1.63775 2.96629,-5.03264 1.27824,-4.91325 -4.77994,0.33808 -8.7824,3.8364 -13.1736,5.7546 0.15542,-1.38489 1.85968,-4.17566 0.46625,-4.15467 -13.75571,0.20727 -7.80115,8.29755 -11.47942,2.48146 -2.33385,0.88589 -4.51172,2.47751 -7.00154,2.65765 -0.71542,0.0518 0.18093,-1.61481 -0.32031,-2.12791 -1.69982,-1.74004 -6.36894,-4.9e-4 -7.57099,-0.11103 -0.99157,-0.0912 -1.76798,-0.91671 -2.65197,-1.37507 -2.09073,0.3287 -4.18146,0.65741 -6.27219,0.98612 -4.18788,-0.43557 -4.06426,-0.009 -7.55836,-1.74909 -0.90521,-0.4509 -1.58091,-1.59702 -2.59199,-1.5767 -2.46011,0.0494 -4.7061,1.70603 -7.1658,1.77273 -0.85835,0.0233 -1.38548,-1.01473 -2.07822,-1.52209 -1.71767,0.15619 -3.43533,0.31238 -5.153,0.46857 -12.16079,-1.35046 6.81819,0.27799 -13.25426,1.47955 -1.76161,0.10546 -3.40987,-1.20637 -5.17287,-1.12741 -3.27207,0.14656 -6.39822,1.50844 -9.66129,1.79185 -5.95391,0.51713 -6.23671,-0.2208 -11.03836,-2.32483 -2.57098,0.27277 -5.14197,0.54555 -7.71295,0.81832 -0.91766,-0.76156 -1.56555,-2.3947 -2.75298,-2.28469 -3.40416,0.31538 -6.37271,2.50981 -9.66387,3.43505 -1.89983,0.5341 -3.88985,0.6772 -5.84762,0.9257 -1.86103,0.23622 -4.98041,2.29736 -5.60307,0.52776 -0.83546,-2.3744 2.2799,-4.48833 3.41985,-6.7325 -6.30568,3.02597 -12.28992,6.842 -18.91706,9.07792 -2.01786,0.6808 -5.02029,1.10316 -6.3653,-0.54796 -1.28614,-1.57885 3.01586,-5.66819 1.01096,-6.02496 -4.18293,-0.74434 -7.97301,2.98121 -12.08383,4.05456 -18.47256,4.82328 8.73927,-5.16607 -13.65645,3.68169 0,0 8.30503,-6.03396 8.30503,-6.03396 z"
570+ inkscape:connector-curvature="0" />
571+ <path
572+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
573+ id="path5282"
574+ d="m 439.33369,237.08082 c -5.47439,-2.96028 -3.28562,5.72694 -1.53636,5.2325 3.44066,-0.97252 6.23707,-3.74491 9.72747,-4.52016 0.75774,-0.16831 0.0633,2.07164 0.83265,2.17464 2.67303,0.35787 13.46994,-4.64024 14.14779,-4.92121 2.04586,-0.54266 4.02194,-1.56426 6.13758,-1.62798 8.26835,-0.24902 -5.3663,4.92975 0.63623,6.00394 3.33252,0.59637 6.26374,-2.88469 9.6326,-3.21942 1.2112,-0.12034 2.05563,1.30397 3.08344,1.95596 4.62785,-1.95699 9.2557,-3.91398 13.88356,-5.87097 1.74763,0.52552 7.10975,2.28627 8.91991,2.20657 4.91708,-0.21649 9.55324,-2.7245 14.4705,-2.93685 0.96534,-0.0417 2.29151,0.18143 2.6989,1.05758 0.48206,1.03674 -0.42687,2.24646 -0.6403,3.36969 0.42808,-0.18104 13.87161,-6.49543 16.43017,-5.66676 1.26258,0.40892 -0.86067,3.47065 0.37133,3.96409 2.45886,0.98481 5.28458,-0.37938 7.92014,-0.64321 15.4825,-1.54984 2.47234,-0.73607 17.32767,-1.44683 20.16342,-0.68595 -0.61738,-1.19219 12.25354,1.71416 1.88649,0.42598 3.86273,0.35948 5.79515,0.2814 2.08666,-0.0843 4.32689,-1.6528 6.21799,-0.76679 1.05283,0.49326 -0.32071,2.30308 -0.48106,3.45461 1.74185,-0.3169 3.4837,-0.6338 5.22556,-0.9507 6.48171,0.58028 11.31801,-5.02464 17.37162,-5.86508 1.21646,-0.16889 0.92241,2.46374 1.96105,3.11914 1.64086,1.0354 3.65432,1.44253 5.58426,1.64206 12.40919,-0.10851 6.3507,-0.44262 18.18031,0.91002 5.2457,-0.519 10.31623,3.10438 15.5601,2.66514 4.34908,-0.36429 8.39276,-3.24534 12.74907,-2.98106 1.44232,0.0875 0.87592,2.90366 1.94781,3.87268 5.51735,4.98789 4.48486,-2.50811 3.9798,6.33873 5.76444,2.19985 12.43819,-6.60952 18.41769,-4.55109 1.44645,0.49794 -1.68101,4.66965 -0.15351,4.58671 4.81321,-0.26138 8.63125,-5.36947 13.45116,-5.30882 1.86087,0.0234 -0.76563,3.64243 -1.14845,5.46364 2.80475,-0.55712 5.6095,-1.11423 8.41425,-1.67135 4.05645,-1.59233 7.85344,-4.17428 12.16934,-4.777 1.04286,-0.14563 0.82904,1.93593 1.24356,2.90389 1.42922,0.0469 2.87497,0.36245 4.28766,0.14068 2.12504,-0.33359 4.1136,-1.26683 6.20077,-1.78717 8.61601,-2.14799 -0.68024,1.02577 9.22492,-2.7096 0.72398,-0.0381 1.46437,-0.27231 2.17195,-0.11441 0.35929,0.0802 0.5247,0.51649 0.78706,0.77473 2.09582,-1.26276 4.09681,-2.69827 6.28745,-3.78827 0.18826,-0.0937 0.30755,0.28684 0.46132,0.43026 0.20897,0.0476 0.43874,0.24546 0.62691,0.14284 1.47903,-0.80655 2.81173,-1.85645 4.21759,-2.78467 10.09216,-6.69652 -1.79879,0.91257 -0.29335,1.23767 1.96082,0.42344 3.21705,-2.44369 5.02727,-3.30811 1.21936,-0.58227 -3.94065,3.52558 -2.67863,3.04268 1.34115,-0.51318 2.40022,-1.57706 3.60033,-2.36559 3.58809,-1.94003 -0.10481,0.10832 -3.22044,1.2596 -1.18007,0.43606 2.73229,-0.76526 2.43591,-1.6751 -0.28452,-0.87342 -1.83157,-0.28546 -2.74741,-0.21451 24.89401,-13.44905 10.98443,-4.78107 6.10519,-4.80799 -0.5874,-0.003 0.47601,-1.09001 0.84739,-1.54512 0.68406,-0.83828 1.57306,-1.49001 2.29035,-2.30005 0.57577,-0.65021 1.0537,-1.38089 1.58056,-2.07134 0.79967,-0.87436 1.59935,-1.74871 2.39902,-2.62307 0.20201,-0.60032 1.14138,-1.46243 0.60604,-1.80097 -0.9444,-0.59722 -2.26102,0.30833 -3.35154,0.0648 -0.4229,-0.0944 -1.12945,-0.68431 -0.83167,-0.9991 2.87301,-3.03713 7.29695,-4.22652 10.35349,-7.07887 0.23764,-0.22177 -0.41926,-0.49684 -0.62888,-0.74526 -1.4682,-0.57082 -1.26023,-0.042 -1.06626,-1.32997 0,0 3.02138,-1.27939 3.02138,-1.27939 l 0,0 c -7.61861,4.36787 -7.57924,3.76889 1.37583,1.16192 -2.79852,1.96211 -7.60545,5.05804 -9.695,7.79492 -0.10971,0.14368 -1.22351,2.57933 -0.59404,3.03807 0.67307,0.49052 1.63956,0.29393 2.45934,0.4409 -10.69683,7.76533 -6.46048,2.28777 -3.95825,4.66925 0.28022,0.26669 0.34086,0.73833 0.2949,1.12243 -0.0613,0.51213 -1.11159,1.36151 -0.59991,1.42631 3.44049,0.43571 7.18169,-3.194 1.54058,1.27161 0.51455,0.0832 1.85256,-0.17022 1.54366,0.24963 -0.42993,0.58435 -5.88925,3.82593 -6.64539,4.28618 -2.25283,1.37126 -4.25988,3.13349 -6.56145,4.42126 -0.57965,0.32433 0.91585,-0.96229 1.37377,-1.44343 -0.97832,0.61888 -1.77788,1.82089 -2.93498,1.85665 -0.84725,0.0262 1.25052,-1.15506 1.77523,-1.8208 0.14614,-0.18543 0.39428,-0.8091 0.19623,-0.68058 -2.95533,1.9178 -5.47115,4.52716 -8.60483,6.13714 -1.41463,0.7268 7.50902,-5.73994 3.22744,-3.51403 -1.94695,1.35889 -3.46817,4.16386 -5.84085,4.07668 -2.00788,-0.0738 5.80425,-2.70226 4.39051,-4.12997 -1.44757,-1.46188 -3.44965,2.24521 -5.22143,3.29084 -1.58833,0.93737 -3.17327,1.89624 -4.85411,2.65534 -3.03629,1.37125 -2.45211,1.51547 -3.62353,-0.19057 -1.2459,0.29401 -2.4918,0.58801 -3.7377,0.88202 -0.94895,-0.0594 -1.91563,-0.37033 -2.84684,-0.17826 -0.90464,0.18658 -13.69919,4.55903 -14.20983,4.39414 -1.42497,-0.46012 2.23239,-3.95305 0.80923,-4.41875 -2.71665,-0.88896 -5.56686,1.30075 -8.35029,1.95112 -18.56769,0.0793 13.70185,-0.82956 -20.27406,5.77609 -1.24209,0.24149 -1.79361,-1.8371 -2.90263,-2.44636 -1.29963,-0.71396 -2.72775,-1.30777 -4.20303,-1.45723 -7.88112,-0.79842 -5.93835,3.28036 -6.95179,-3.55661 -4.15903,1.41034 -8.31806,2.82068 -12.47709,4.23101 0.13929,-1.64904 2.02947,-4.5709 0.41789,-4.94714 -3.68032,-0.8592 -7.29587,2.22135 -11.06755,2.46089 -19.20033,1.21941 3.42849,-1.74287 -8.44928,-6.51391 -3.19359,-1.2828 -6.88148,0.1533 -10.32222,0.22994 -4.01012,0.4663 -8.02025,0.93261 -12.03037,1.39891 -1.70721,-1.01184 -3.14481,-2.86055 -5.12161,-3.03552 -3.42478,-0.30314 -6.76114,2.07637 -10.17655,1.68141 -1.21543,-0.14056 -0.76145,-3.11341 -1.98472,-3.08777 -3.40485,0.0714 -6.28097,2.89341 -9.65823,3.33189 -0.82541,0.10716 -1.10923,-1.24127 -1.66384,-1.86191 -1.46833,0.1182 -2.93216,0.38115 -4.405,0.3546 -1.20724,-0.0218 -2.40792,-0.75508 -3.5878,-0.49866 -3.40511,0.74001 -6.46807,4.14784 -9.87684,3.42487 -1.76071,-0.37343 2.8545,-4.90599 1.09548,-5.28732 -6.30413,-1.36664 -12.36869,6.47925 -19.07787,3.24381 -2.81278,-1.15043 -8.0235,-3.73838 -11.21616,-2.99339 -5.30446,1.23778 -9.81702,5.25422 -15.21947,5.94913 -1.30931,0.16841 0.63002,-3.1138 -0.3995,-3.94008 -0.96711,-0.77619 -8.78805,0.52726 -9.49794,0.62362 -2.43958,0.81492 -4.75139,2.60053 -7.31876,2.44474 -3.96541,-0.24062 4.43728,-6.06793 -2.4985,-4.79696 -4.07548,0.74683 -7.54039,3.65131 -11.60589,4.45071 -3.28335,0.64561 -6.23377,-5.00667 -9.93337,-1.45023 -2.69658,0.53612 -5.34039,1.61546 -8.08974,1.60836 -1.29308,-0.003 -2.27044,-1.2921 -3.5163,-1.63834 -1.44221,-0.40081 -2.95661,-0.56492 -4.45344,-0.57657 -7.07426,-0.0551 -4.52877,1.11419 -12.43282,2.68962 -1.29287,0.25769 -2.63653,0.0199 -3.9548,0.0298 -0.74509,-0.3974 -1.39082,-1.19305 -2.23527,-1.19222 -8.48298,0.008 -5.99334,5.38203 -16.7296,6.92971 -1.63516,0.23571 1.50781,-2.94003 2.26172,-4.41004 -1.14109,0.0362 -2.31128,-0.14996 -3.42328,0.10862 -1.6719,0.38879 -3.21432,1.20574 -4.82148,1.80861 0,0 6.59652,-4.79266 6.59652,-4.79266 z"
575+ inkscape:connector-curvature="0" />
576+ <path
577+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:1;stroke-opacity:1;stroke-dasharray:none"
578+ id="path5288"
579+ d="m 427.09099,608.80296 c -0.54536,0.88302 -2.59373,3.0492 -1.6361,2.64907 3.94655,-2.30416 7.89309,-4.60832 11.83964,-6.91247 -0.48068,0.95507 -2.45697,2.52886 -1.44204,2.86521 1.68661,0.55895 3.34268,-1.22337 5.06375,-1.66497 1.38771,-0.35607 2.85207,-1.08817 4.237,-0.72145 0.74384,0.19697 0.64163,1.39882 0.96244,2.09823 1.37197,0.0822 2.78072,0.57276 4.11592,0.24671 3.459,-0.84467 6.54045,-2.94826 10.02108,-3.69889 2.35754,-0.50843 6.11513,1.00569 8.24077,1.90515 1.08276,0.45817 2.01316,1.647 3.18215,1.5214 3.04137,-0.32679 5.82554,-1.86816 8.73831,-2.80223 0.0213,1.01023 -0.88531,2.68436 0.064,3.03068 1.6579,0.60487 3.51554,-0.518 5.17469,-1.11943 3.10954,-1.12717 5.90095,-3.06895 9.06232,-4.04132 1.11082,-0.34167 3.56487,-0.97615 3.48173,0.18305 -0.17139,2.38956 -5.60845,4.31354 -3.92661,6.01963 1.90254,1.92996 5.2515,-1.34136 7.87725,-2.01204 5.57129,2.09339 10.88855,-4.38851 16.19806,-2.9901 1.5161,0.39931 -1.91863,4.4988 -0.36246,4.68943 3.61195,0.44246 6.80584,-2.64975 10.35214,-3.4657 4.75582,-1.09424 4.54067,-0.5378 7.56802,1.586 2.7685,-1.48955 4.99284,1.99196 7.53405,1.61787 3.37273,-0.49649 6.43938,-2.27591 9.75173,-3.08218 2.40251,-0.5848 4.93371,-0.50568 7.33922,-1.078 3.43034,-0.81613 6.59584,-3.15152 10.12112,-3.07612 1.16193,0.0249 0.71886,2.21042 1.07829,3.31564 1.42789,0.26396 2.89169,0.37849 4.28369,0.79187 4.40179,1.3072 2.47933,2.85169 8.61077,1.10238 2.82423,-0.80575 18.5379,-8.0104 22.90225,-7.58044 1.38563,0.1365 1.07795,2.56757 1.61692,3.85135 6.71637,11.01182 -3.68387,-3.37038 21.00188,-5.1411 2.86377,-0.20542 -5.40592,5.18669 -3.94363,7.65753 1.31649,2.22446 5.10447,-0.84577 7.6179,-1.44914 2.83592,-0.68079 5.59821,-1.63981 8.41404,-2.39945 2.34076,-0.63148 4.70255,-1.18227 7.05383,-1.7734 1.89507,-0.27942 3.83635,-1.33931 5.68522,-0.83825 0.91429,0.24778 0.67699,1.76946 1.01548,2.65418 2.87065,-0.87358 5.62729,-2.92999 8.61194,-2.62073 1.19443,0.12376 -0.64239,3.23232 0.51114,3.56602 2.02002,0.58437 15.38587,-3.38686 16.41366,-3.66716 -0.15304,1.37971 -1.82071,3.86876 -0.45913,4.13912 2.88522,0.57288 5.56173,-1.98169 8.44725,-2.55301 1.49987,-0.29697 3.07716,0.21428 4.58688,-0.0277 2.14755,-0.34421 4.12553,-1.5628 6.29505,-1.71648 0.71473,-0.0506 1.11339,1.25168 1.8214,1.14155 2.30654,-0.35876 4.37463,-1.63033 6.56195,-2.4455 -0.44888,1.15735 -2.31073,2.69008 -1.34663,3.47204 1.24004,1.00577 3.13873,-0.68571 4.73492,-0.72386 1.27427,-0.0305 5.97531,0.93232 7.19325,1.37508 1.00412,0.36504 1.79765,1.62936 2.85276,1.46128 3.30399,-0.52634 6.29112,-2.27935 9.43668,-3.41903 2.16754,7.29671 -1.34009,-1.02626 10.2382,-0.42077 1.26673,0.0662 -0.89534,2.46826 -0.75751,3.7292 0.0842,0.77019 0.82031,1.33174 1.37033,1.87741 3.25333,3.22757 4.14722,0.43976 9.52911,-1.62234 -5.67229,9.9542 2.19189,-5.35859 6.0963,-0.33515 1.76843,2.27527 -6.50408,3.21284 -6.15274,6.07304 0.29151,2.37311 4.145,-2.92278 6.53572,-2.95529 -2.29854,3.66744 -2.94659,2.96902 0.93035,2.70267 23.98881,-12.56081 6.53151,-5.05582 6.71984,-1.35229 0.0562,1.10418 2.14953,-0.59987 3.2544,-0.64052 0.59071,-0.0217 1.14215,0.30517 1.71323,0.45776 1.24821,-0.35158 2.46907,-0.82114 3.74464,-1.05472 0.6566,-0.12024 1.33503,0.002 2.00255,0.002 -7.56212,7.4632 -4.80566,3.75696 0.51507,2.0828 0.278,-0.0875 0.83108,0.29605 0.68233,0.54666 -0.69786,1.17578 -1.86061,2.00398 -2.79092,3.00597 0,0 -1.07469,0.77304 -1.07469,0.77304 l 0,0 c 0.29688,-0.55013 0.49344,-1.16768 0.89064,-1.65039 0.97731,-1.18769 4.73458,-3.44842 3.20163,-3.32283 -3.51078,0.28763 -5.88837,4.42533 -9.63666,4.33697 -0.15241,-0.37159 -0.34842,-0.72816 -0.45725,-1.11477 -0.29714,-1.0556 -0.24534,-2.79779 -0.8748,-2.62026 -2.57218,0.72544 -4.57165,3.54585 -7.24208,3.44002 -1.52804,-0.0605 1.88309,-2.41005 2.82464,-3.61508 -2.57134,1.22873 -5.14267,2.45745 -7.71401,3.68618 2.38555,-2.2552 5.77328,-3.78851 7.15663,-6.76561 0.72913,-1.56914 -3.24014,1.21524 -4.86007,1.82323 -1.6775,0.62961 -3.30811,1.40187 -5.03222,1.88956 -1.02074,0.28874 -3.10209,1.48551 -3.15373,0.42598 -0.0805,-1.65221 4.01255,-2.67402 2.98116,-3.96728 -1.08235,-1.35717 -3.33942,0.94959 -5.00913,1.42439 -20.13729,8.35134 -2.69796,2.67973 -3.51754,-2.53363 -0.30637,-1.94883 -3.85411,1.58588 -5.79195,1.21634 -0.87346,-0.16656 -0.0847,-2.23403 -0.93357,-2.49888 -11.84288,-3.69529 -1.16733,3.90973 -7.75074,-1.30702 -2.41838,0.61135 -4.85237,1.16396 -7.25514,1.83405 -2.78931,0.77789 -5.42146,2.48551 -8.31711,2.50877 -0.92506,0.007 -0.55731,-1.9355 -1.33837,-2.43121 -1.00483,-0.63771 -2.41681,-0.17922 -3.51128,-0.64664 -5.17393,-2.20964 4.51953,-3.02699 -6.76187,-0.29747 -1.8717,0.0522 -5.62004,0.0639 -7.3367,0.54341 -2.69541,0.75295 -5.09849,2.65578 -7.88857,2.87404 -0.88863,0.0695 -0.97674,-1.50932 -1.61597,-2.13053 -2.88746,-2.80607 -2.96741,-1.82144 -7.53922,-1.91209 -5.60568,-1.00025 -5.2968,-1.462 -12.0296,0.4822 -3.77819,1.09101 -7.17546,4.70648 -11.06137,4.10258 -1.96748,-0.30576 3.75004,-3.90954 2.47312,-5.43726 -3.21419,-3.84551 -12.45489,8.124 -14.52498,3.88536 0.16133,-1.24054 1.72133,-3.53728 0.484,-3.72163 -2.94633,-0.43896 -5.68485,1.79467 -8.57499,2.51619 -2.56637,0.64068 -5.14051,1.38245 -7.77923,1.56659 -1.88811,0.13176 -3.76204,-0.41995 -5.64306,-0.62993 0.13071,-1.79104 2.18522,-5.47173 0.39212,-5.37312 -5.55708,0.30559 -10.33401,4.13599 -15.50101,6.20399 -10.66645,-2.44406 0.68895,-0.62357 -14.71365,0.88811 -1.68946,0.16581 -3.35352,-0.60601 -5.05038,-0.65553 -1.88826,-0.0551 -3.83509,0.84291 -5.65682,0.34302 -0.93454,-0.25644 -0.73557,-2.23638 -1.69663,-2.36085 -2.36483,-0.30628 -4.69832,0.81892 -7.04748,1.22838 -2.97423,0.81182 -5.94845,1.62365 -8.92268,2.43547 -0.86665,-0.8126 -1.41221,-2.41128 -2.59994,-2.43779 -5.34668,-0.11933 -13.32985,5.97106 -18.52181,1.51644 -0.70103,-0.60147 -0.94151,-1.58947 -1.41227,-2.38421 -0.71473,0.22765 -15.04458,4.94998 -16.29418,4.75369 -1.16619,-0.18319 0.32544,-3.32233 -0.84936,-3.43812 -3.23005,-0.31834 -6.18554,2.3471 -9.4303,2.42499 -1.28344,0.0308 -2.14298,-1.4143 -3.21447,-2.12145 -4.57791,1.88547 -9.15581,3.77095 -13.73372,5.65642 -0.35422,-0.97953 -0.25778,-2.27745 -1.06266,-2.9386 -0.88974,-0.73085 -3.03618,0.38783 -3.38033,-0.71096 -0.44745,-1.42861 3.36536,-4.26694 1.8802,-4.07862 -3.91526,0.49646 -7.09459,3.45974 -10.64188,5.18961 0.003,-0.88328 0.88856,-2.56291 0.01,-2.64984 -2.39024,-0.23638 -4.62168,1.31014 -6.93253,1.9652 -21.18125,5.49534 1.18422,1.02595 -11.44132,0.93616 -2.29045,-0.0163 -4.51443,1.00604 -6.80442,0.95784 -1.30262,-0.0274 -2.42374,-1.16009 -3.72651,-1.17953 -2.1489,-0.0321 -4.24554,1.38622 -6.36478,1.02898 -0.87305,-0.14717 -0.86226,-1.54661 -1.29339,-2.31992 -2.244,0.83914 -8.96653,3.77305 -12.27692,3.87067 -0.63692,0.0188 -1.13222,-0.59394 -1.73219,-0.80855 -1.88608,-0.67465 -2.71984,-0.38168 -3.36282,-2.56718 0,0 -2.59014,1.88185 -2.59014,1.88185 z"
580+ inkscape:connector-curvature="0" />
581+ <text
582+ xml:space="preserve"
583+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier;-inkscape-font-specification:Courier"
584+ x="351"
585+ y="282"
586+ id="text4003"
587+ sodipodi:linespacing="125%"><tspan
588+ sodipodi:role="line"
589+ id="tspan4005"
590+ x="351"
591+ y="282"
592+ style="font-size:16px;font-style:italic;font-family:Arial;-inkscape-font-specification:Arial Italic">Endpoint</tspan></text>
593+ <text
594+ sodipodi:linespacing="125%"
595+ id="text4030"
596+ y="316"
597+ x="373"
598+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier;-inkscape-font-specification:Courier"
599+ xml:space="preserve"><tspan
600+ style="font-size:16px;font-style:italic;font-family:Arial;-inkscape-font-specification:Arial Italic"
601+ y="316"
602+ x="373"
603+ id="tspan4032"
604+ sodipodi:role="line">Transport</tspan></text>
605+ <text
606+ xml:space="preserve"
607+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier;-inkscape-font-specification:Courier"
608+ x="454.58578"
609+ y="292.34314"
610+ id="text4050"
611+ sodipodi:linespacing="125%"><tspan
612+ sodipodi:role="line"
613+ id="tspan4052"
614+ x="454.58578"
615+ y="292.34314"
616+ style="font-size:16px;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Italic">TCP connection</tspan><tspan
617+ sodipodi:role="line"
618+ x="454.58578"
619+ y="312.34314"
620+ style="font-size:16px;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Italic"
621+ id="tspan4068">(bi-directional)</tspan></text>
622+ <path
623+ style="fill:#b3b3b3;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:2,1;stroke-dashoffset:0"
624+ d="m 554,248 86,108"
625+ id="path4054"
626+ inkscape:connector-curvature="0" />
627+ <text
628+ sodipodi:linespacing="125%"
629+ id="text4056"
630+ y="376"
631+ x="594"
632+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier;-inkscape-font-specification:Courier"
633+ xml:space="preserve"><tspan
634+ style="font-size:16px;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Italic"
635+ y="376"
636+ x="594"
637+ id="tspan4058"
638+ sodipodi:role="line">Transport connection</tspan><tspan
639+ style="font-size:16px;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Italic"
640+ y="396"
641+ x="594"
642+ sodipodi:role="line"
643+ id="tspan4070">(uni-directional)</tspan></text>
644+ <text
645+ xml:space="preserve"
646+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Courier;-inkscape-font-specification:Courier"
647+ x="794"
648+ y="226"
649+ id="text4062"
650+ sodipodi:linespacing="125%"><tspan
651+ sodipodi:role="line"
652+ id="tspan4064"
653+ x="794"
654+ y="226"
655+ style="font-size:16px;font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial Italic">Chan</tspan></text>
656+ </g>
657+</svg>
doc/reconnect/blacklist.texnew file+525
@@ -0,0 +1,525 @@
1+\documentclass{article}
2+\usepackage[margin=2.5cm]{geometry}
3+\usepackage{amsmath, amssymb}
4+\usepackage{mathpazo, times}
5+
6+\newcommand{\sLam}[2]{\lambda {#1} \cdot {#2}}
7+\newcommand{\sApp}[2]{{#1} {#2}}
8+\newcommand{\sBind}[2]{{#1} \mathrel{\texttt{>}\!\!\texttt{>}\!\texttt{=}} {#2}}
9+\newcommand{\sReturn}{\mathtt{return}}
10+
11+\newcommand{\sExpect}{\mathtt{expect}}
12+\newcommand{\sSend}{\mathtt{send}}
13+\newcommand{\sSpawn}{\mathtt{spawn}}
14+\newcommand{\sLink}{\mathtt{link}}
15+\newcommand{\sThrow}{\mathtt{throw}}
16+\newcommand{\sReconnect}{\mathtt{reconnect}}
17+\newcommand{\sNewChan}{\mathtt{newChan}}
18+\newcommand{\sSendChan}{\mathtt{sendChan}}
19+\newcommand{\sReceiveChan}{\mathtt{receiveChan}}
20+\newcommand{\sMonitor}{\mathtt{monitor}}
21+
22+\DeclareMathOperator{\sNodeOf}{node}
23+\DeclareMathOperator{\sProcessOf}{process}
24+
25+\newcommand{\sSpawned}{\mathtt{spawned}}
26+
27+\newcommand{\sExtend}[1]{\mathrel{\triangleright_{#1}}}
28+
29+\newcommand{\sPar}{\mathrel{\parallel}}
30+\newcommand{\sProc}[2]{{#1}_{#2}}
31+
32+\newcommand{\sNid}{\ensuremath{\mathit{nid}}}
33+\newcommand{\sPid}{\ensuremath{\mathit{pid}}}
34+\newcommand{\sCid}{\ensuremath{\mathit{cid}}}
35+
36+\newcommand{\sId}{\ensuremath{\mathit{id}}}
37+\newcommand{\sRef}{\ensuremath{\mathit{ref}}}
38+
39+\newcommand{\sLinks}{\ensuremath{\mathit{links}}}
40+\newcommand{\sNode}[3]{\left[{#1} ; {#2}\right]_{#3}}
41+\newcommand{\sSystem}[4]{\left\langle #1 ; #2 ; #3 ; #4 \right\rangle}
42+\newcommand{\sNodes}{\mathcal{N}}
43+\newcommand{\sQueue}{\mathcal{Q}}
44+\newcommand{\sProcesses}{\mathcal{P}}
45+\newcommand{\sBlacklist}{\mathcal{B}}
46+\newcommand{\sMonitors}{\mathcal{M}}
47+
48+\newcommand{\sJust}[1]{\mathtt{Just} \; {#1}}
49+\newcommand{\sNothing}{\mathtt{Nothing}}
50+
51+\newcommand{\sCtxt}[1]{\mathbb{#1}}
52+
53+\newcommand{\sSenders}{\mathit{senders}}
54+
55+\newcommand{\OR}{\mathrel{|}}
56+
57+
58+\begin{document}
59+
60+\title{Cloud Haskell Semantics (DRAFT)}
61+\author{Edsko de Vries, Well-Typed LLP}
62+\date{\today}
63+
64+\maketitle
65+
66+\section{Introduction}
67+
68+This is a semantics for Cloud Haskell, based on the \textit{Unified Semantics
69+for a Future Erlang} (Hans Svensson, Lars-\AA{}ke Fredlund and Clara Benac
70+Earle, Erlang '10) but using the semantics style of \textit{Composable Memory
71+Transactions} (Tim Harris, Simon Marlow, Simon Peyton-Jones, Maurice Herlihy,
72+PPoPP '05). It differs from the former in that
73+%
74+\begin{enumerate}
75+\item We introduce an explicit notion of \textit{reconnecting} (with potential
76+message loss)
77+\item We simplify the semantics: we ``flatten'' sets of nodes of processes as
78+sets of processes (but assume a mapping from process identifiers to node
79+identifiers), do not have per-process mailboxes (but only the system queue or
80+``ether'') and do not have an explicit concept of node controllers
81+\end{enumerate}
82+%
83+It differs from the latter in that
84+%
85+\begin{enumerate}
86+\item We pretend that the Cloud Haskell \texttt{Process} monad is the top-level
87+monad, and do not consider the \texttt{IO} monad at all
88+\item We ignore exceptions
89+\end{enumerate}
90+%
91+Current imprecisions with respect to the ``real'' Cloud Haskell are
92+%
93+\begin{enumerate}
94+\item We ignore the issue of serializability, other than to say that the
95+semantics will get stuck when trying to send a non-serializable payload;
96+consequently, we do not formalize $\mathtt{static}$
97+\item We do not formalize all Cloud Haskell primitives (merging of ports,
98+``advanced messaging'', and others)
99+\item Some of the concepts that we do formalize are lower-level concepts; for
100+instance, the primitive $\sSpawn$ that we formalize is asynchronous (following
101+the Unified semantics); a synchronous construct can be derived.
102+\end{enumerate}
103+
104+\section{Preliminaries}
105+
106+\subsection{Terms}
107+
108+We assume the usual definitions of values and terms.
109+
110+\begin{equation*}
111+\begin{array}{ll@{\;::=\;}l}
112+\text{value} & V & \sLam{x}{M} \OR
113+ \sExpect \OR
114+ \sSend \; \sPid \; M \OR
115+ \sSpawn \; \sNid \; M \OR
116+ \sReturn \; M \OR
117+ \cdots
118+\\
119+\text{term} & M, N & \sApp{M}{N} \OR
120+ \sBind{M}{N}
121+\end{array}
122+\end{equation*}
123+
124+\subsection{Processes, Nodes, Systems}
125+
126+Processes run on \emph{nodes}. We assume that nodes run a special process
127+called the \emph{node controller} which is responsible for issuing monitor
128+messages, spawning new processes, etc. In the semantics messages to node
129+controllers (for instance, requests to spawn new processes) are recorded as
130+messages sent to the node itself.
131+
132+We assume disjoint countable sets $\mathtt{NodeId}$, $\mathtt{ProcessId}$, and
133+$\mathtt{ChannelId}$, changed over by \sNid, \sPid{} and \sCid{} respectively,
134+and representing process identifiers, node identifiers, and (typed) channel
135+identifiers. We assume the existence of total functions
136+%
137+\begin{equation*}
138+\begin{array}{r@{\;:\;}l}
139+\sNodeOf & (\mathtt{ProcessId} \uplus \mathtt{ChannelId}) \rightarrow \mathtt{NodeId} \\
140+\sProcessOf & \mathtt{ChannelId} \rightarrow \mathtt{ProcessId}
141+\end{array}
142+\end{equation*}
143+%
144+and define
145+$$\mathtt{Identifier} = \mathtt{NodeId} \uplus \mathtt{ProcessId} \uplus \mathtt{ChannelId}$$
146+and let $\sId$ range over $\mathtt{Identifier}$.
147+
148+We represent a process as a pair $\sProc{M}{\sPid}$ of a term $M$ and a process
149+ID $\sPid$. We will denote a set of processes as
150+%
151+ $$\sProc{M}{\sPid} \sPar \cdots \sPar \sProc{N}{\sPid'}$$
152+%
153+A \emph{system}
154+ $\sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}$
155+is a tuple containing a set $\sProcesses$ of processes, a \emph{system
156+queue} $\sQueue$, a \emph{blacklist} $\sBlacklist$, and a set of monitors
157+$\sMonitors$.
158+The set of monitors $\sMonitors$ is a set of tuples
159+ $(\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid, \sRef)$
160+which records that the node controller at \sNid{} knows that process
161+$\sPid_\mathit{fr}$ is monitoring $\sId_\mathit{to}$ ($\sRef$ is the monitor
162+reference).
163+The system queue is a set of triples $(\sId_\mathit{to}, \sId_\mathit{fr},
164+\mathit{message})$ of messages that have been sent but not yet processed. The
165+blacklist records disconnections and is represented as a of pairs
166+$(\sId_\mathit{to}, \sId_\mathit{fr})$.
167+
168+\section{Semantics}
169+
170+\subsection{Contexts}
171+
172+We define an evaluation context as in the \textit{Composable Memory
173+Transactions} paper, but note that we work with \emph{sets} of processes so the
174+definition of $\sCtxt{P}$ can be slightly simpler.
175+
176+\begin{align*}
177+\sCtxt{E} & ::= [] \OR \sBind{\sCtxt{E}}{M} \\
178+\sCtxt{P}_\sPid & ::= \sCtxt{E}[]_\sPid \sPar \sProcesses
179+\end{align*}
180+
181+\subsection{Disconnect}
182+
183+We assume that entire \emph{nodes} get disconnected from each other, not
184+individual processes. \emph{Reconnecting} however is on a per process basis. To
185+formalize this, let $\overline{\sNid}$ be the smallest set, given a set of
186+processes $\sProcesses$, such that
187+%
188+\begin{equation*}
189+\frac{
190+}{
191+\sNid \in \overline{\sNid}
192+}
193+%
194+\qquad
195+%
196+\frac{
197+\sPid \in \sProcesses \qquad
198+\sNodeOf(\sPid) = \sNid
199+}{
200+\sPid \in \overline{\sNid}
201+}
202+%
203+\qquad
204+%
205+\frac{
206+\sCid \in \sProcesses \qquad
207+\sNodeOf(\sCid) = \sNid
208+}{
209+\sCid \in \overline{\sNid}
210+}
211+\end{equation*}
212+
213+The following rule models random network disconnect between nodes $\sNid_1$ and
214+$\sNid_2$:
215+
216+\begin{equation*}
217+\frac{
218+}{
219+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
220+\rightarrow
221+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist \cup (\overline{\sNid_1} \times \overline{\sNid_2})}{\sMonitors}
222+} \textsc{Disconnect}
223+\end{equation*}
224+
225+Once a connection has been blacklisted, no further messages can be sent across
226+that connection (until an explicit or implicit reconnect). To model this we
227+introduce a function
228+%
229+\begin{equation*}
230+\begin{array}{l@{\;=\;}l@{\hspace{3em}}l}
231+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
232+&
233+ \sQueue, (\sId_\mathit{to}, \sId_\mathit{fr}, M)
234+&
235+ \text{if } (\sId_\mathit{to}, \sId_\mathit{fr}) \notin \sBlacklist
236+\\
237+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
238+&
239+ \sQueue
240+&
241+ \text{otherwise}
242+\end{array}
243+\end{equation*}
244+%
245+$(\sExtend{\sBlacklist})$ is only defined for serializable payloads.
246+
247+We provide a primitive to remove a connection from the blacklist:
248+
249+\begin{equation*}
250+\frac{
251+}{
252+ \sSystem{\sCtxt{P}[ \sReconnect \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
253+ {\sQueue}
254+ {\sBlacklist}
255+ {\sMonitors}
256+\rightarrow
257+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_\sPid}
258+ {\sQueue}
259+ {\sBlacklist \backslash (\sId_\mathit{to}, \sPid_\mathit{fr})}
260+ {\sMonitors}
261+} \textsc{Recon-Ex}
262+\end{equation*}
263+
264+Note that using this primitive means giving up on the reliability guarantee: a
265+call to $\sReconnect$ means that message loss is accepted.
266+
267+Connections to and from node controllers can be implicitly reconnected:
268+
269+\begin{equation*}
270+\frac{
271+}{
272+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sNid_\mathit{to}, \sId_\mathit{fr})}{\sMonitors}
273+\rightarrow
274+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
275+} \textsc{Recon-Im1}
276+\qquad
277+\frac{
278+}{
279+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sId_\mathit{to}, \sNid_\mathit{fr})}{\sMonitors}
280+\rightarrow
281+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
282+} \textsc{Recon-Im2}
283+\end{equation*}
284+
285+\subsection{Send and Receive}
286+
287+\begin{equation*}
288+\frac{
289+}{
290+ \sSystem{\sCtxt{P}[ \sSend \; \sPid_\mathit{to} \; M ]_{\sPid_\mathit{fr}}}
291+ {\sQueue}
292+ {\sBlacklist}
293+ {\sMonitors}
294+\rightarrow
295+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
296+ {\sQueue \sExtend{\sBlacklist} (\sPid_\mathit{to}, \sPid_\mathit{fr}, M)}
297+ {\sBlacklist}
298+ {\sMonitors}
299+} \textsc{Send}
300+\end{equation*}
301+
302+We return the \emph{first} message of a randomly chosen sender.
303+
304+\begin{equation*}
305+\frac{
306+ \sId_{\mathit{fr}} \notin \sSenders(\sQueue)
307+}{
308+ \sSystem{\sCtxt{P}[ \sExpect ]_{\sPid_\mathit{to}}}
309+ {\sQueue, (\sPid_\mathit{to}, \sId_\mathit{fr}, M), \sQueue'}
310+ {\sBlacklist}
311+ {\sMonitors}
312+\rightarrow
313+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid_\mathit{to}}}
314+ {\sQueue, \sQueue'}
315+ {\sBlacklist}
316+ {\sMonitors}
317+} \textsc{Expect}
318+\end{equation*}
319+
320+\subsection{Spawning}
321+
322+Spawning is asynchronous
323+
324+\begin{equation*}
325+\frac{
326+ \sRef \text{ fresh}
327+}{
328+ \sSystem{\sCtxt{P}[ \sSpawn \; \sNid \; M ]_\sPid}
329+ {\sQueue}
330+ {\sBlacklist}
331+ {\sMonitors}
332+\rightarrow
333+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_\sPid}
334+ {\sQueue \sExtend{\sBlacklist} (\sNid, \sPid, \sSpawn \; \sRef \; M)}
335+ {\sBlacklist}
336+ {\sMonitors}
337+} \textsc{Spawn-Async}
338+\end{equation*}
339+
340+\begin{equation*}
341+\frac{
342+ \sPid \notin \sSenders(\sQueue)
343+\qquad
344+ \sPid' \text{ fresh}
345+\qquad
346+ \sNodeOf(\sPid') = \sNid
347+}{
348+ \sSystem{\sProcesses}
349+ {\sQueue, (\sNid, \sPid, \sSpawn \; \sRef \; M), \sQueue'}
350+ {\sBlacklist}
351+ {\sMonitors}
352+\rightarrow
353+ \sSystem{\sProcesses \sPar M_{\sPid'}}
354+ {\sQueue, \sQueue' \sExtend{\sBlacklist} (\sPid, \sNid, \sSpawned \; \sRef \; \sPid')}
355+ {\sBlacklist}
356+ {\sMonitors}
357+} \textsc{Spawn-Reply}
358+\end{equation*}
359+
360+\subsection{Typed Channels}
361+
362+Since the semantics is not type-driven, we represent typed channels simply as
363+an identifier with an annotation whether it is the send-end ($\sCid^s$) or the
364+receive end ($\sCid^r$) of the channel.
365+
366+\begin{equation*}
367+\frac{
368+ \sCid \text{ fresh}
369+\qquad
370+ \sProcessOf(\sCid) = \sPid
371+}{
372+ \sSystem{\sCtxt{P}[\sNewChan]_\sPid}
373+ {\sQueue}
374+ {\sBlacklist}
375+ {\sMonitors}
376+\rightarrow
377+ \sSystem{\sCtxt{P}[\sReturn \; (\sCid^s, \sCid^r)]_\sPid}
378+ {\sQueue}
379+ {\sBlacklist}
380+ {\sMonitors}
381+} \textsc{NewChan}
382+\end{equation*}
383+
384+\begin{equation*}
385+\frac{
386+}{
387+ \sSystem{\sCtxt{P}[ \sSendChan \; \sCid_\mathit{to}^s \; M ]_{\sPid_\mathit{fr}}}
388+ {\sQueue}
389+ {\sBlacklist}
390+ {\sMonitors}
391+\rightarrow
392+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
393+ {\sQueue \sExtend{\sBlacklist} (\sCid_\mathit{to}, \sPid_\mathit{fr}, M)}
394+ {\sBlacklist}
395+ {\sMonitors}
396+} \textsc{SendChan}
397+\end{equation*}
398+
399+\begin{equation*}
400+\frac{
401+ \sPid_{\mathit{fr}} \notin \sSenders(\sQueue)
402+}{
403+ \sSystem{\sCtxt{P}[ \sReceiveChan \; \sCid_\mathit{to}^r ]_{\sPid}}
404+ {\sQueue, (\sCid_\mathit{to}, \sPid_\mathit{fr}, M), \sQueue'}
405+ {\sBlacklist}
406+ {\sMonitors}
407+\rightarrow
408+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid}}
409+ {\sQueue, \sQueue'}
410+ {\sBlacklist}
411+ {\sMonitors}
412+} \textsc{ReceiveChan}
413+\end{equation*}
414+
415+\subsection{Monitoring}
416+
417+The local node controller is notified immediately of the new monitor, and the
418+remote monitor is sent a message (which may, of course, be lost).
419+
420+\begin{equation*}
421+\frac{
422+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
423+\qquad
424+ \sNid_\mathit{to} = \sNodeOf(\sId_\mathit{to})
425+\qquad
426+ \sRef \text{ fresh}
427+}{
428+\begin{array}{ll}
429+& \sSystem{\sCtxt{P}[ \sMonitor \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
430+ {\sQueue}
431+ {\sBlacklist}
432+ {\sMonitors}
433+\\
434+\rightarrow &
435+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_{\sPid_\mathit{fr}}}
436+ {\sQueue \sExtend{\sBlacklist} (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to})}
437+ {\sBlacklist}
438+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
439+\end{array}
440+} \textsc{Monitor}
441+\end{equation*}
442+
443+\begin{equation*}
444+\frac{
445+ \sPid_\mathit{fr} \notin \sSenders(\sQueue)
446+}{
447+ \sSystem{\sProcesses}
448+ {\sQueue, (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to}), \sQueue'}
449+ {\sBlacklist}
450+ {\sMonitors}
451+\rightarrow
452+ \sSystem{\sProcesses}
453+ {\sQueue, \sQueue'}
454+ {\sBlacklist}
455+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{to}, \sRef)}
456+} \textsc{Monitor-Remote}
457+\end{equation*}
458+
459+The \emph{local} node controller notifies a monitoring process when it gets
460+disconncted from a monitored remote process.
461+
462+\begin{equation*}
463+\frac{
464+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
465+\qquad
466+ (\sId_\mathit{to}, \sPid_\mathit{fr}) \in \sBlacklist
467+}{
468+ \sSystem{\sProcesses}
469+ {\sQueue}
470+ {\sBlacklist}
471+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
472+\rightarrow
473+ \sSystem{\sProcesses}
474+ {\sQueue, (\sPid_\mathit{fr}, \sId_\mathit{to}, \mathtt{discon} \; \sRef)}
475+ {\sBlacklist}
476+ {\sMonitors}
477+} \textsc{Discon}
478+\end{equation*}
479+
480+\subsection{Process Termination}
481+
482+We remove a terminated process only once all its monitors have been notified.
483+
484+\begin{equation*}
485+\frac{
486+ \sNodeOf(\sPid) = \sNid
487+\qquad
488+ \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
489+}{
490+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
491+ {\sQueue}
492+ {\sBlacklist}
493+ {\sMonitors}
494+\rightarrow
495+ \sSystem{\sProcesses}
496+ {\sQueue}
497+ {\sBlacklist}
498+ {\sMonitors}
499+} \textsc{Term}
500+\end{equation*}
501+
502+The \emph{remote} node controller notifies a monitoring process when its
503+monitored remote process terminates.
504+
505+\begin{equation*}
506+\frac{
507+ \sNodeOf(\sPid) = \sNid
508+}{
509+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
510+ {\sQueue}
511+ {\sBlacklist}
512+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
513+\rightarrow
514+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
515+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sPid, \mathtt{died} \; \sRef)}
516+ {\sBlacklist}
517+ {\sMonitors}
518+} \textsc{Died}
519+\end{equation*}
520+
521+\subsection{Administration}
522+
523+TODO: bind, pure evaluation, maybe exception handling
524+
525+\end{document}
doc/reconnect/reconnect.texnew file+409
@@ -0,0 +1,409 @@
1+\documentclass[10pt]{article}
2+\usepackage[margin=2.5cm]{geometry}
3+\usepackage{listings}
4+\usepackage{amsmath,amssymb,stmaryrd}
5+\usepackage{mathpazo,times}
6+
7+\begin{document}
8+
9+\newcommand{\semNs}{\mathit{ns}}
10+\newcommand{\semEth}{\mathit{eth}}
11+\newcommand{\semPg}{\mathit{pg}}
12+\newcommand{\semNid}{\mathit{nid}}
13+\newcommand{\semNc}{\mathit{nc}}
14+\newcommand{\semRecon}{\mathrm{reconnect}}
15+\newcommand{\semDisconnect}{\mathrm{disconnect}}
16+\newcommand{\semDropped}{\mathrm{dropped}}
17+\newcommand{\semMessage}{\mathrm{message}}
18+\newcommand{\semPid}{\mathit{pid}}
19+
20+\newcommand{\semSystem}[2]{\llbracket #1, #2 \rrbracket}
21+\newcommand{\semNode}[3]{[ #1, #2, #3 ]}
22+\newcommand{\semStdNode}[1]{\semNode{\semPg_{#1}}{\semNid_{#1}}{\semNc_{#1}}}
23+\newcommand{\semProc}[3]{\langle #1, #2, #3 \rangle}
24+\newcommand{\semStdProc}[1]{\semProc{e}{\semPid}{q}}
25+
26+\newcommand{\semDied}[2]{\mathrm{died}(#1, #2)}
27+\newcommand{\semParN}{\mathrel{{\parallel}_N}}
28+\newcommand{\semParP}{\mathrel{{\parallel}_P}}
29+\newcommand{\semEthMatch}[3]{\mathrm{ethMatch}(#1, #2, #3)}
30+
31+\lstset{basicstyle=\ttfamily\small}
32+
33+\title{An Explicit $\semRecon$ Primitive for Cloud Haskell \\ (or a Future Erlang)}
34+\author{\small \begin{tabular}{ll}
35+Edsko de Vries & Duncan Coutts \\
36+Well-Typed LLP & Well-Typed LLP \\
37+\texttt{edsko@well-typed.com} & \texttt{duncan@well-typed.com}
38+\end{tabular}}
39+\maketitle
40+
41+\section*{Introduction}
42+
43+The original Cloud Haskell paper stipulates that messages are ``asynchronous,
44+reliable, and buffered'', but does not describe how this can be achieved.
45+Understanding ``reliable'' to mean ``reliable ordered'' (or ``TCP-like''),
46+the reliability of message delivery comes from the reliability of the
47+underlying network protocol---up to a point.
48+
49+The problem is that the underlying network protocol is connection-oriented, but
50+Cloud Haskell is not. Intuitively, when $P$ sends a message to $Q$, we open a
51+reliable-ordered connection from $P$ to $Q$. Reliability of message delivery
52+now follows from reliability of the network protocol, until $P$ somehow gets
53+disconnected from $Q$. If $P$ now sends another message to $Q$, the
54+implementation cannot simply reconnect: after all, some messages that were sent
55+on the first connection might not have been delivered. This means that $P$
56+might send $m_1, m_2, m_3$ to $Q$, but $Q$ will receive $m_1, m_3$.
57+
58+One (non-)solution is for $P$ to buffer all messages it sends to $Q$, and
59+remove messages from this buffer only when $Q$ acknowledges that it received
60+them. On a reconnect $P$ must ask which message $Q$ last received, and
61+retransmit the rest. This means that when $P$ gets disconnected from $Q$, it
62+must infinitely buffer all messages sent to $Q$ (until a connection is
63+reestablished). However, infinite buffering is too strong a requirement;
64+moreover, this is unsatisfactory because it means implementing a reliable
65+protocol on top of the underlying reliable network protocol. We would like a
66+different solution.
67+
68+But even more important than the exact solution we adopt is a precise
69+semantics of Cloud Haskell message delivery. A good starting point (one that
70+has been used in the new implementation of Cloud Haskell) is \textit{A
71+Unified Semantics for Future Erlang} by Hans Svensson, Lars-\AA{}ke Fredlund
72+and Clara Benac Earle (Erlang '10)---although this semantics leaves the issue
73+of reconnect open.
74+
75+Here we give an overview of the relevant aspects of the Unified semantics,
76+discuss where reconnect comes into the picture, and then propose a new
77+primitive for Cloud Haskell called $\semRecon$, which essentially gives the
78+programmer the ability to explicitly accept message loss.
79+
80+\section*{Overview of the original semantics}
81+
82+The top-level semantics is a labelled transition relation between
83+\textit{systems}
84+%
85+\begin{equation}
86+\semSystem{\semNs}{\semEth} \xrightarrow{\tilde{\mu}} \semSystem{\semNs'}{\semEth'}
87+\end{equation}
88+%
89+A system $\semSystem{\semNs}{\semEth}$ consists of a bunch of nodes $\semNs$
90+and an ``ether'' or system message queue $\semEth$. The ether models, globally,
91+all messages that have been sent but not yet delivered. The label $\tilde{\mu}$
92+is a list of actions, and is only used in the semantics to define fairness (see
93+below).
94+
95+A node is a triplet $\semStdNode{}$ consisting of a bunch of processes
96+$\semPg$, a node ID $\semNid$ and a node controller $\semNc$. The node
97+controller is responsible for all administrative actions on a node (spawning
98+new processes, establishing links, etc.).
99+A process $\semStdProc{}$ consists of an expression $e$, process ID $\semPid$
100+and message queue (mailbox) $q$. Finally, $(\semParN)$ is used for the parallel
101+composition of nodes, and $(\semParP)$ is used for the parallel composition of
102+processes.
103+
104+\subsubsection*{Ordering}
105+
106+The ether contains messages of the form
107+%
108+\begin{equation*}
109+(\mathit{recipient}, \mathit{sender}, \mathit{message})
110+\end{equation*}
111+%
112+where $\mathit{recipient}$ and $\mathit{sender}$ are process IDs or node IDs
113+(for messages to or from node controllers). Ordering of message delivery in
114+the semantics is embodied by a function
115+%
116+\begin{equation*}
117+\semEthMatch{\semEth}{\mathit{to}}{\mathit{from}}
118+\end{equation*}
119+%
120+(Def.~11, p.~25) which selects the \emph{first} message in $\semEth$ from
121+$\mathit{from}$ to $\mathit{to}$. Many of the semantic rules look like
122+%
123+\begin{equation*}
124+\frac{%
125+\semEthMatch{\semEth}{\mathit{to}}{\mathit{from}} = \mathit{sig}
126+}{%
127+\semSystem{\semNs}{\semEth} \rightarrow \text{(do something with $\mathit{sig}$)}
128+}
129+\end{equation*}
130+
131+where $\mathit{to}$ and $\mathit{from}$ are universally quantified over the
132+rule. This means that messages between pairs of processes (or nodes) are
133+ordered, but there is no global ordering guarantee.
134+
135+\subsubsection*{Disconnects}
136+
137+Although the semantics is not connection oriented, it includes a
138+non-deterministic rule $\mathit{node}_\mathit{disconnect}$ which models
139+arbitrary disconnects (Table 9, p.~28):
140+%
141+{\small
142+\begin{equation*}
143+\frac{}{%
144+ \begin{array}{l}
145+ \semSystem{\semStdNode{1} \semParN \semStdNode{2} \semParN \semNs}{\semEth}
146+\xrightarrow{\semDisconnect(nid_1, nid_2)} \\[0.5em]
147+ \semSystem{\semStdNode{1} \semParN \semStdNode{2} \semParN \semNs}
148+ {\semEth \cdot (nid_1, nid_2, \semDied{nid_2}{\semDisconnect})
149+ \cdot (nid_2, nid_1, \semDied{nid_1}{\semDisconnect})
150+ }
151+ \end{array}
152+}
153+\end{equation*}
154+}
155+%
156+So at any one point a node controller may receive a message that it got
157+disconnected from another node controller. These messages are handled by the
158+bottom rule in Table 12 (p.~30): local processes that link to (or monitor)
159+processes on the disconnected remote node are killed (or notified).
160+
161+\subsubsection*{Node or process failure}
162+
163+When a process dies (throws an exception) a message is sent to its local node
164+controller (rule $\mathit{exiting}$, Table~4, p.~27). Failure of an entire
165+node is handled by a non-deterministic rule like the one above (rule
166+$\mathit{node}_\mathit{failure}$, Table~9, p.~28). The resulting message is
167+handled in a similar way to a disconnect (Table~12, p.~30).
168+
169+When a process or a node \emph{dies} (but \emph{not} when it gets disconnected)
170+messages to that process (or node) can be removed from the ether (rules
171+$\mathit{missing}_\mathit{process}$ and $\mathit{missing}_\mathit{node}$,
172+Table~6, p.~28).
173+
174+\subsubsection*{Fairness}
175+
176+Since the semantic rules pick an (more or less) arbitrary message from the
177+ether to process, the rules do not guarantee that all messages are eventually
178+delivered. In the paper this is added ``on top'' of the semantic rules by Def.~20
179+(p.~29), which essentially states that all messages that have been sent must be
180+delivered (or explicitly removed by rules $\mathit{missing}_\mathit{process}$
181+and $\mathit{missing}_\mathit{node}$).
182+
183+\section*{Reconnect}
184+
185+\subsubsection*{In the Unified semantics}
186+
187+The original semantics state that
188+
189+\begin{enumerate}
190+\item all messages sent must eventually be delivered or explicitly removed from the ether (fairness)
191+\item only messages to died (not disconnected) processes or nodes can be removed from the ether
192+\end{enumerate}
193+
194+From a practical perspective this means that messages sent from process $P$ to
195+process $Q$ must be infinitely buffered when $P$ gets disconnected to $Q$, and
196+the system should keep retrying to connect to $Q$ until it receives evidence
197+(for instance, from $Q$'s node controller) that $Q$ has died.
198+
199+For messages sent to node controllers (such as attempts to spawn new processes)
200+this is even more problematic: when process $P$ gets disconnected from node
201+$N$, all messages from $P$ to $N$ must be infinitely buffered until a
202+reconnection is established, or dropped when $P$ learns $N$ has died. However,
203+process $P$ might never be able to distinguish a (temporary) disconnect from
204+$N$ from a crash of $N$.
205+
206+\subsubsection*{Permanent disconnects}
207+
208+The simplest solution to avoid infinite buffering is to drop the fairness
209+requirement from the semantics. Now the implementation is free to attempt to
210+reconnect only a finite number of times---or indeed never---and then simply
211+drop all subsequent messages and never reconnect again. This is a correct
212+implementation of the semantics, because without the fairness requirement
213+messages sent from $P$ to $Q$ can be left in the ether forever.\footnote{From a
214+purely formal perspective, the semantics without the fairness guarantee is too
215+permissive: it would allow a message never to be sent even when no disconnect
216+occurs. If we wanted to fix this, we would have to remove messages from the
217+ether on a disconnect, so that fairness can be satisfied, and keep an explicit
218+history of disconnects.}
219+
220+\subsubsection*{Implicit reconnect with potential message loss (Erlang-style)}
221+
222+The solution adopted by Erlang is an implicit reconnect but without
223+buffering. As described in the introduction, this means that it is possible for
224+$P$ to send messages $m_1, m_2, m_3$ to $Q$ and for $Q$ to receive messages
225+$m_1$ and $m_3$, but not $m_2$. We could model this in the semantics by
226+modifying the rule for disconnect so that it removes messages from the ether.
227+
228+\subsubsection*{Explicit Reconnects}
229+
230+Erlang-style implicit reconnects are not very satisfying semantically because
231+it means we cannot really make any claims about the reliability of message
232+delivery. Permanent disconnects are nicer semantically, but might be too
233+limiting (some musings on that below). The solution we propose is to introduce
234+a new primitive called $\semRecon$. Here's the rule:
235+%
236+\begin{equation*}
237+\frac{%
238+e \xrightarrow{\semRecon(i)} e' \qquad
239+(\semEth_0, \semEth_1) = \mathrm{partition} \; (\lambda (\mathit{to}, \mathit{from}, \_) \rightarrow \mathit{to} = i \wedge \mathit{from} = \semPid) \; \semEth
240+}{%
241+ \semSystem{\semNode{\semProc{e}{\semPid}{q} \semParP \semPg}{\semNid}{\semNc} \semParN \semNs}{\semEth}
242+\xrightarrow{\semDropped(\semEth_0)}
243+ \semSystem{\semNode{\semProc{e'}{\semPid}{q} \semParP \semPg}{\semNid}{\semNc} \semParN \semNs}{\semEth_1}
244+}
245+\end{equation*}
246+%
247+Semantically, when process $P$ calls $\semRecon(Q)$ it explicitly accepts that
248+some the messages it sent to $Q$ might be lost. Note that this is the only
249+change we need to make the Unified semantics. In particular, the fairness
250+requirement stays: since the rule explicitly records that some messages have
251+been dropped, fairness now means:
252+
253+\begin{quote}
254+All messages sent must eventually be delivered, removed from the ether because
255+a process or a node died, or removed from the ether by an explicit $\semRecon$.
256+\end{quote}
257+
258+From an implementation point of view, once $P$'s connection to $Q$ breaks we do
259+not implicitly reconnect (because doing so would mean losing the reliability
260+guarantee). When $P$ calls $\semRecon(Q)$, however, the implementation is
261+free to reestablish a connection from $P$ to $Q$, either immediately or when
262+$P$ sends its next message to $Q$.
263+
264+\section*{Example}
265+
266+Suppose we start with two nodes, both of which run a single process. The system
267+ether and both process queues are empty ($\epsilon$):
268+%
269+\begin{equation*}
270+\semSystem
271+ {\semNode{\semProc{p_1}{\semPid_1}{\epsilon}}{\semNid_1}{\semNc_1}
272+ \semParN
273+ \underbrace{\semNode{\semProc{p_2}{\semPid_2}{\epsilon}}{\semNid_2}{\semNc_2}}_{N_2}
274+ }
275+ {\epsilon}
276+\end{equation*}
277+%
278+Now $p_1$ sends a message to $p_2$ (Rule $\mathit{output}$, Table~5, p.~28):
279+%
280+\begin{equation*}
281+\frac{%
282+ p_1 \xrightarrow{\semPid_2 \; {!_{\semPid_1}} \; \semMessage(v)} p_1'
283+}{%
284+\semSystem
285+ {\semNode{\semProc{p_1}{\semPid_1}{\epsilon}}{\semNid_1}{\semNc_1}
286+ \semParN
287+ N_2
288+ }
289+ {\epsilon}
290+\xrightarrow{\semPid_2 \; {!_{\semPid_1}} \; \semMessage(v)}
291+\semSystem
292+ {\underbrace{\semNode{\semProc{p_1}{\semPid_1}{\epsilon}}{\semNid_1}{\semNc_1}}_{N_1'}
293+ \semParN
294+ N_2
295+ }
296+ {\underbrace{(\semPid_2, \semPid_1, \semMessage(v))}_{M_1}}
297+}
298+\end{equation*}
299+%
300+Note that the message has been sent but not yet delivered (i.e., it is in the
301+ether, not in $p_2$'s queue). Now suppose that node $\semNid_1$ gets
302+disconnected from node $\semNid_2$ (Table~9, p.~28):
303+%
304+\begin{equation*}
305+\frac{%
306+}{%
307+\begin{array}{l}
308+\semSystem{N_1' \semParN N_2}{M_1}
309+\xrightarrow{\semDisconnect(\semNid_1, \semNid_2)} \\
310+\semSystem{N_1' \semParN N_2}{M_1 \cdot (nid_1, nid_2, \semDied{nid_2}{\semDisconnect})
311+ \cdot (nid_2, nid_1, \semDied{nid_1}{\semDisconnect})}
312+\end{array}
313+}
314+\end{equation*}
315+%
316+Assume that $p_1$ had previously set up monitoring for $p_2$. It gets notified of the disconnect (Table 12), and it receives the monitor message; at this point we're in a state
317+%
318+\begin{equation*}
319+\semSystem{N_1'' \semParN N_2}{M_1}
320+\end{equation*}
321+%
322+where the original message $M_1$ is still in the ether. If at this point $p_1$ sends another message to $p_2$ we end up with an ether
323+%
324+\begin{equation*}
325+M_1 \cdot \underbrace{(\semPid_2, \semPid_1, \semMessage(v_2))}_{M_2}
326+\end{equation*}
327+%
328+which implies that we must deliver $M_1$ before $M_2$. This is of course
329+precisely the ``ordered'' reliability guarantee. If however, $p_1$ calls
330+$\semRecon$ when it receives the monitor message, we remove $M_1$ from the ether;
331+when $p_1$ then sends the next message we can establish a new connection.
332+
333+\section*{The Need for Reconnect}
334+
335+Ordering guarantees are between pairs of processes/nodes. Thus, when $P$ gets
336+disconnected from $Q$, the easiest way to reestablish a connection is for $P$
337+to restart to become a new process $P'$. This will happen naturally when $P$
338+links to $Q$ and can be restarted by some supervisor process.
339+
340+However, if there are many processes connected to $P$, then $P$ might not want
341+to restart (breaking all connections with its own clients) simply in order to
342+be able to reconnect to Q. For instance, maybe $P$ is a webserver and $Q$ is a
343+domain name server; if $P$ gets temporarily disconnected from $Q$, it does not
344+make much sense to require $P$ to restart (and get a new process ID) before it
345+can send new domain name requests (indeed, in this particular example, lost
346+messages to a domain name server may not matter at all).
347+
348+The situation is even more severe for messages sent from processes to
349+\emph{nodes}. For instance, suppose process $P$ does
350+%
351+\begin{lstlisting}[mathescape,keywords={do}]
352+do spawnAsync nid p1 ; spawnAsync nid p2 ; spawnAsync nid p3
353+\end{lstlisting}
354+%
355+and receives a confirmation that $p_3$ was spawned successfully.\footnote{In
356+the unified paper spawn is asynchronous, which is why the new implementation of
357+Cloud Haskell offers \texttt{spawnAsync}; \texttt{spawn} is then implemented in
358+terms of \texttt{spawnAsync} and \texttt{expect}.} Intuitively this seems to
359+suggest that \emph{all} of $p_1$, $p_2$ and $p_3$ should have been spawned, and
360+this is guaranteed by the reliable-ordered guarantee for messages:
361+\texttt{spawnAsync} is nothing more than a message from the process to the node
362+controller of the node $\mathit{nid}$.
363+
364+But the same problem with reconnect occurs here, too. If the connection from
365+the process to the remote node controller is lost, we cannot guarantee ordered
366+delivery. Permanent disconnect would mean that the process would have to
367+restart before it can spawn new processes on this remote node; if the process
368+is some sort of supervisor process, then this might not be what we want to do.
369+Implicit reconnects means we lose the reliability guarantee; an explicit
370+reconnect (probably after a monitor notification about the disconnect from the
371+remote node) allows the process to reestablish a connection to the remote node
372+controller, and forces the programmer of the process to think about how to
373+handle the potential loss of messages.
374+
375+\section*{Alternative Solutions}
376+
377+\begin{itemize}
378+
379+\item A hybrid solution is possible, too. For instance, we might guarantee
380+reliable-ordered message passing for messages sent from process-to-process, but
381+not for process-to-node. In other words, no explicit call is necessary when
382+spawning a new process on a remote node after a disconnect from that remote
383+node. This might muddle the semantics (in particular, if processes don't
384+monitor remote nodes, programmers might get a false sense of security and rely
385+on non-existing ordering guarantees). On the other hand, requiring an explicit
386+reconnect to a remote node before a spawn (or similar primitive) might be
387+confusing.
388+
389+\item Another hybrid solution is to have an implicit reconnect when sending a
390+monitor notification. So, if $P$ monitors $Q$, and gets a monitor notification,
391+then it must expect potential message loss. If $P$ does not monitor $Q$ then
392+disconnects are permanent.
393+
394+\item Since Cloud Haskell supports typed channels as well as sending messages
395+directly to processes, we could decide not to offer any reliability guarantees
396+for messages sent directly to processes, but provide reliable-ordered message
397+delivery on typed channels. This is an attractive solution, because a typed
398+channel could be considered a connection. When $P$ wants to communicate with
399+$Q$, it sends a message to $Q$ requesting a send port. It then communicates
400+with $Q$ through this send port; when it gets disconnected, it could request a
401+\empty{new} typed channel. Importantly, the send port $P$ receives must indeed
402+be a \emph{new} channel (at least from $P$s perspective).
403+
404+\end{itemize}
405+
406+
407+
408+
409+\end{document}
doc/semantics/CloudHaskellSemantics.texdeleted−706
@@ -1,706 +0,0 @@
1−\documentclass{article}
2−\usepackage[margin=2.5cm]{geometry}
3−\usepackage{amsmath, amssymb, stmaryrd}
4−\usepackage{mathpazo, times}
5−\usepackage{float}
6−\usepackage{listings}
7−
8−\newcommand{\sLam}[2]{\lambda {#1} \cdot {#2}}
9−\newcommand{\sApp}[2]{{#1} {#2}}
10−\newcommand{\sBind}[2]{{#1} \mathrel{\texttt{>}\!\!\texttt{>}\!\texttt{=}} {#2}}
11−\newcommand{\sReturn}{\mathtt{return}}
12−\newcommand{\sThrow}[1]{\mathtt{throw} \; {#1}}
13−\newcommand{\sCatch}[2]{\mathtt{catch} \; {#1} \; {#2}}
14−
15−\newcommand{\sExpect}{\mathtt{expect}}
16−\newcommand{\sSend}{\mathtt{send}}
17−\newcommand{\sSpawn}{\mathtt{spawn}}
18−\newcommand{\sLink}{\mathtt{link}}
19−\newcommand{\sReconnect}{\mathtt{reconnect}}
20−\newcommand{\sNewChan}{\mathtt{newChan}}
21−\newcommand{\sSendChan}{\mathtt{sendChan}}
22−\newcommand{\sReceiveChan}{\mathtt{receiveChan}}
23−\newcommand{\sMonitor}{\mathtt{monitor}}
24−
25−\DeclareMathOperator{\sNodeOf}{node}
26−\DeclareMathOperator{\sProcessOf}{process}
27−
28−\newcommand{\sSpawned}{\mathtt{spawned}}
29−
30−\newcommand{\sExtend}[1]{\mathrel{\triangleright_{#1}}}
31−
32−\newcommand{\sPar}{\mathrel{\parallel}}
33−\newcommand{\sProc}[2]{{#1}_{#2}}
34−
35−\newcommand{\sNid}{\ensuremath{\mathit{nid}}}
36−\newcommand{\sPid}{\ensuremath{\mathit{pid}}}
37−\newcommand{\sCid}{\ensuremath{\mathit{cid}}}
38−
39−\newcommand{\sId}{\ensuremath{\mathit{id}}}
40−\newcommand{\sRef}{\ensuremath{\mathit{ref}}}
41−
42−\newcommand{\sLinks}{\ensuremath{\mathit{links}}}
43−\newcommand{\sNode}[3]{\left[{#1} ; {#2}\right]_{#3}}
44−\newcommand{\sSystem}[4]{\left\langle #1 ; #2 ; #3 ; #4 \right\rangle}
45−\newcommand{\sNodes}{\mathcal{N}}
46−\newcommand{\sQueue}{\mathcal{Q}}
47−\newcommand{\sProcesses}{\mathcal{P}}
48−\newcommand{\sBlacklist}{\mathcal{B}}
49−\newcommand{\sMonitors}{\mathcal{M}}
50−
51−\newcommand{\sJust}[1]{\mathtt{Just} \; {#1}}
52−\newcommand{\sNothing}{\mathtt{Nothing}}
53−
54−\newcommand{\sCtxt}[1]{\mathbb{#1}}
55−
56−\newcommand{\sSenders}{\mathit{senders}}
57−
58−\newcommand{\OR}{\mathrel{|}}
59−\newcommand{\where}{\mathrel{|}}
60−
61−\floatstyle{boxed}
62−\restylefloat{figure}
63−
64−\lstset{basicstyle=\ttfamily\small}
65−
66−\begin{document}
67−
68−\title{Cloud Haskell Semantics (DRAFT)}
69−\author{Well-Typed LLP}
70−\date{\today}
71−
72−\maketitle
73−
74−\section{Introduction}
75−
76−Cloud Haskell brings Erlang-style concurrency to Haskell in the form of a
77−library.\footnote{\texttt{http://hackage.haskell.org/package/distributed-process}}
78−The original publication \cite{cloudhaskell} is a good introduction to Cloud
79−Haskell, and is accompanied by an ever-growing number of resouces, collected on
80−the Haskell
81−Wiki.\footnote{\texttt{http://www.haskell.org/haskellwiki/Cloud\_Haskell}} The
82−current document augments the original publication about Cloud Haskell by
83−giving a precise semantics to the Cloud Haskell primitives. It is meant as a
84−reference, not an introduction.
85−
86−The original Cloud Haskell paper stipulates that messages are ``asynchronous,
87−reliable, and buffered'', but does not describe how this can be achieved.
88−Understanding ``reliable'' to mean ``reliable ordered'' (or ``TCP-like''),
89−the reliability of message delivery comes from the reliability of the
90−underlying network protocol---up to a point.
91−
92−The problem is that the underlying network protocol is connection-oriented, but
93−Cloud Haskell is not. Intuitively, when $P$ sends a message to $Q$, we open a
94−reliable-ordered connection from $P$ to $Q$. Reliability of message delivery
95−now follows from reliability of the network protocol, until $P$ somehow gets
96−disconnected from $Q$. If $P$ now sends another message to $Q$, the
97−implementation cannot simply reconnect: after all, some messages that were sent
98−on the first connection might not have been delivered. This means that $P$
99−might send $m_1, m_2, m_3$ to $Q$, but $Q$ will receive $m_1,
100−m_3$.\footnote{Indeed, message passing in Erlang is ordered but unreliable for
101−the same reason \cite{erlang}.}
102−
103−One (non-)solution is for $P$ to buffer all messages it sends to $Q$, and
104−remove messages from this buffer only when $Q$ acknowledges that it received
105−them. On a reconnect $P$ must ask which message $Q$ last received, and
106−retransmit the rest. This means that when $P$ gets disconnected from $Q$, it
107−must infinitely buffer all messages sent to $Q$ (until a connection is
108−reestablished). However, infinite buffering is too strong a requirement;
109−moreover, this is unsatisfactory because it means implementing a reliable
110−protocol on top of the underlying reliable network protocol. We would like a
111−different solution.
112−
113−Instead, Cloud Haskell does \emph{not} attempt to reconnect automatically, but
114−provides a $\sReconnect$ primitive which gives programmers the option of
115−reconnecting manually. This is an explicit acknowledgement from the programmer
116−that message loss might occur, and forces them to consider how such loss might
117−be dealt with.
118−
119−The semantics we present is based on \cite{unified}, which we will refer to the
120−as the ``Unified'' semantics. However, we will present the semantics in a more
121−``Haskell'' style following the semantics for STM \cite{stm}. It differs from
122−the Unified semantics in that
123−%
124−\begin{enumerate}
125−\item We introduce an explicit notion of \textit{reconnecting} (with potential
126−message loss)
127−\item We simplify the semantics: we ``flatten'' sets of nodes of processes as
128−sets of processes (but assume a mapping from process identifiers to node
129−identifiers), do not have per-process mailboxes (but only the system queue or
130−``ether'') and do not have an explicit concept of node controllers
131−\end{enumerate}
132−%
133−Our semantics differs from the STM semantics in that we pretend that the Cloud
134−Haskell \texttt{Process} monad is the top-level monad, and do not consider the
135−\texttt{IO} monad at all. Current imprecisions with respect to the ``real''
136−Cloud Haskell are
137−%
138−\begin{enumerate}
139−\item We ignore the issue of serializability, other than to say that the
140−semantics will get stuck when trying to send a non-serializable payload;
141−consequently, we do not formalize $\mathtt{static}$
142−\item We do not formalize all Cloud Haskell primitives (merging of ports,
143−``advanced messaging'', and others)
144−\item Some of the concepts that we do formalize are lower-level concepts; for
145−instance, the primitive $\sSpawn$ that we formalize is asynchronous (following
146−the Unified semantics); a synchronous construct can be derived.
147−\end{enumerate}
148−
149−\section{Preliminaries}
150−
151−Cloud Haskell Processes run on \emph{nodes}. Processes communicate by sending
152−messages to each other (directly or using typed channels). Processes can also
153−send messages to nodes (for instance, a request to spawn or monitor a process).
154−
155−We assume disjoint countable sets $\mathtt{NodeId}$, $\mathtt{ProcessId}$, and
156−$\mathtt{ChannelId}$, changed over by \sNid, \sPid{} and \sCid{} respectively,
157−and representing process identifiers, node identifiers, and (typed) channel
158−identifiers. We assume the existence of total functions
159−%
160−\begin{equation*}
161−\begin{array}{r@{\;:\;}l}
162−\sNodeOf & (\mathtt{ProcessId} \uplus \mathtt{ChannelId}) \rightarrow \mathtt{NodeId} \\
163−\sProcessOf & \mathtt{ChannelId} \rightarrow \mathtt{ProcessId}
164−\end{array}
165−\end{equation*}
166−%
167−and define
168−$$\mathtt{Identifier} = \mathtt{NodeId} \uplus \mathtt{ProcessId} \uplus \mathtt{ChannelId}$$
169−and let $\sId$ range over $\mathtt{Identifier}$.
170−
171−We represent a process as a pair $\sProc{M}{\sPid}$ of a term $M$ and a process
172−ID $\sPid$. We will denote a set of processes as
173−%
174− $$\sProc{M}{\sPid} \sPar \cdots \sPar \sProc{N}{\sPid'}$$
175−%
176−A \emph{system}
177− $\sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}$
178−is a tuple containing a set $\sProcesses$ of processes, a \emph{system
179−queue} $\sQueue$, a \emph{blacklist} $\sBlacklist$, and a set of monitors
180−$\sMonitors$.
181−The set of monitors $\sMonitors$ is a set of tuples
182− $(\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid, \sRef)$
183−which records that node \sNid{} knows that process $\sPid_\mathit{fr}$ is
184−monitoring $\sId_\mathit{to}$ ($\sRef$ is the monitor reference). The system
185−queue is a set of triples $(\sId_\mathit{to}, \sId_\mathit{fr},
186−\mathit{message})$ of messages that have been sent but not yet processed. The
187−blacklist records disconnections and is represented as a of pairs
188−$(\sId_\mathit{to}, \sId_\mathit{fr})$.
189−
190−\section{Semantics}
191−
192−We follow the STM semantics as closely as possible. The language is the same
193−except for the primitives considered, and we use the same concept of evaluation
194−contexts:
195−
196−\begin{equation*}
197−\begin{array}{llll}
198−\text{value} & V & ::= & \sId \OR
199− \sLam{x}{M} \OR
200− \sReturn \; M \OR
201− \sBind{M}{N} \OR
202− \sThrow{M} \OR \sCatch{M}{N} \OR \\
203− &&& \sExpect \OR
204− \sSend \; \sPid \; M \OR
205− \sSpawn \; \sNid \; M \OR
206− \sMonitor \; \sId
207−\\
208−\text{term} & M, N & ::= & x \OR
209− V \OR
210− \sApp{M}{N} \OR
211− \cdots \\
212−\\
213−\text{Evaluation} & \sCtxt{E} & ::= & [] \OR \sBind{\sCtxt{E}}{M} \OR \sCatch{\sCtxt{E}}{M} \\
214−\text{context} & \sCtxt{P}_\sPid & ::= & \sCtxt{E}[]_\sPid \sPar \sProcesses
215−\end{array}
216−\end{equation*}
217−%
218−Indeed, the ``administrative'' transitions are identical
219−(Figure~\ref{fig:administrative}).
220−
221−\begin{figure}
222−\small
223−\begin{equation*}
224−\frac{
225− \llbracket M \rrbracket = V \qquad
226− M \neq V
227−}{
228− M \rightarrow V
229−} \textsc{Eval}
230−\qquad
231−\frac{
232− M \rightarrow N
233−}{
234− \sSystem{\sCtxt{P}[M]_\sPid}
235− {\sQueue}
236− {\sBlacklist}
237− {\sMonitors}
238−\rightarrow
239− \sSystem{\sCtxt{P}[N]_\sPid}
240− {\sQueue}
241− {\sBlacklist}
242− {\sMonitors}
243−} \textsc{Admin}
244−\end{equation*}
245−%
246−\begin{equation*}
247−\begin{array}{r@{\;\rightarrow\;}ll@{\hspace{2em}}r@{\;\rightarrow\;}ll}
248−\sBind{\sReturn \; N}{M} & M \; N & \textsc{Bind} & \sCatch{(\sReturn \; M)}{N} & \sReturn \; M & \textsc{Catch}_1 \\
249−\sBind{\sThrow{N}}{M} & \sThrow{N} & \textsc{Throw} & \sCatch{(\sThrow M)}{N} & N \; M & \textsc{Catch}_2 \\
250−\end{array}
251−\end{equation*}
252−%
253−\caption{\label{fig:administrative}Administrative Transitions}
254−\end{figure}
255−
256−\subsection{Disconnect and Reconnect}
257−
258−Figure~\ref{fig:disconnect} gives the semantics for disconnecting and
259−reconnecting. Rule \textsc{Disconnect} models random network disconnect
260−between nodes $\sNid_1$ and $\sNid_2$. We assume that entire \emph{nodes} get
261−disconnected from each other, not individual processes. \emph{Reconnecting}
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
264−$\textsc{Recon-Im}_2$).
265−
266−\begin{figure}
267−\small
268−\begin{equation*}
269−\frac{
270− \sNid_1 \neq \sNid_2
271−}{
272− \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
273−\rightarrow
274− \sSystem{\sProcesses}{\sQueue}{\sBlacklist \cup (\overline{\sNid_1} \times \overline{\sNid_2})}{\sMonitors}
275−} \textsc{Disconnect}
276−\end{equation*}
277−%
278−\begin{equation*}
279−\frac{
280−}{
281− \sSystem{\sCtxt{P}[ \sReconnect \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
282− {\sQueue}
283− {\sBlacklist}
284− {\sMonitors}
285−\rightarrow
286− \sSystem{\sCtxt{P}[ \sReturn \; () ]_\sPid}
287− {\sQueue}
288− {\sBlacklist \backslash (\sId_\mathit{to}, \sPid_\mathit{fr})}
289− {\sMonitors}
290−} \textsc{Recon-Ex}
291−\end{equation*}
292−%
293−\begin{equation*}
294−\frac{
295−}{
296− \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sNid_\mathit{to}, \sId_\mathit{fr})}{\sMonitors}
297−\rightarrow
298− \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
299−} \textsc{Recon-Im}_1
300−\qquad
301−\frac{
302−}{
303− \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sId_\mathit{to}, \sNid_\mathit{fr})}{\sMonitors}
304−\rightarrow
305− \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
306−} \textsc{Recon-Im}_2
307−\end{equation*}
308−%
309−where
310−%
311−\begin{equation*}
312−\overline{\sNid} \subset \mathtt{Identifier}
313− = \{ \sNid \} \cup
314− \{ \sPid \where \sNodeOf(\sPid) = \sNid \} \cup
315− \{ \sCid \where \sNodeOf(\sCid) = \sNid \}
316−\end{equation*}
317−\caption{\label{fig:disconnect}Disconnect and Reconnect}
318−\end{figure}
319−
320−\subsection{Communication}
321−
322−The semantics for the basic primitives are listed in Figure~\ref{fig:basic}.
323−Once a connection has been blacklisted, no further messages can be sent across
324−that connection (until an explicit or implicit reconnect).
325−
326−Message passing is ordered but only for a given sender and receiver; no
327−ordering guarantees exist between messages sent by different processes. For
328−that reason rules \textsc{Expect}, \textsc{ReceiveChan} and \textsc{Spawn-Exec}
329−choose the \emph{first} message of a \emph{randomly chosen} sender.
330−
331−Since the semantics is not type-driven, we represent typed channels simply as
332−an identifier with an annotation whether it is the send-end ($\sCid^s$) or the
333−receive end ($\sCid^r$) of the channel (rules \textsc{NewChan},
334−\textsc{SendChan} and \textsc{ReceiveChan}).
335−
336−Spawning finally is asynchronous. When process $P$ spawns process $Q$ on node
337−$\sNid$ (rule \textsc{Spawn-Async}) a message is sent to node $\sNid$ (which
338−may, of course, never arrive). When the remote node receives the message and
339−actually spawns $Q$ (rule \textsc{Spawn-Exec}) it sends a message back to $P$
340−with $Q$'s process ID. In Cloud Haskell this primitive is called
341−\texttt{spawnAsync}, and the Cloud Haskell \texttt{spawn} primitive is defined
342−in terms of \texttt{spawnAsync} (we do not consider the synchronous version in
343−this document).
344−
345−\begin{figure}
346−\small
347−\begin{equation*}
348−\frac{
349−}{
350− \sSystem{\sCtxt{P}[ \sSend \; \sPid_\mathit{to} \; M ]_{\sPid_\mathit{fr}}}
351− {\sQueue}
352− {\sBlacklist}
353− {\sMonitors}
354−\rightarrow
355− \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
356− {\sQueue \sExtend{\sBlacklist} (\sPid_\mathit{to}, \sPid_\mathit{fr}, M)}
357− {\sBlacklist}
358− {\sMonitors}
359−} \textsc{Send}
360−\end{equation*}
361−%
362−\begin{equation*}
363−\frac{
364− \sId_{\mathit{fr}} \notin \sSenders(\sQueue)
365−}{
366− \sSystem{\sCtxt{P}[ \sExpect ]_{\sPid_\mathit{to}}}
367− {\sQueue, (\sPid_\mathit{to}, \sId_\mathit{fr}, M), \sQueue'}
368− {\sBlacklist}
369− {\sMonitors}
370−\rightarrow
371− \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid_\mathit{to}}}
372− {\sQueue, \sQueue'}
373− {\sBlacklist}
374− {\sMonitors}
375−} \textsc{Expect}
376−\end{equation*}
377−%
378−\begin{equation*}
379−\frac{
380− \sCid \text{ fresh}
381−\qquad
382− \sProcessOf(\sCid) = \sPid
383−}{
384− \sSystem{\sCtxt{P}[\sNewChan]_\sPid}
385− {\sQueue}
386− {\sBlacklist}
387− {\sMonitors}
388−\rightarrow
389− \sSystem{\sCtxt{P}[\sReturn \; (\sCid^s, \sCid^r)]_\sPid}
390− {\sQueue}
391− {\sBlacklist}
392− {\sMonitors}
393−} \textsc{NewChan}
394−\end{equation*}
395−
396−\begin{equation*}
397−\frac{
398−}{
399− \sSystem{\sCtxt{P}[ \sSendChan \; \sCid_\mathit{to}^s \; M ]_{\sPid_\mathit{fr}}}
400− {\sQueue}
401− {\sBlacklist}
402− {\sMonitors}
403−\rightarrow
404− \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
405− {\sQueue \sExtend{\sBlacklist} (\sCid_\mathit{to}, \sPid_\mathit{fr}, M)}
406− {\sBlacklist}
407− {\sMonitors}
408−} \textsc{SendChan}
409−\end{equation*}
410−
411−\begin{equation*}
412−\frac{
413− \sPid_{\mathit{fr}} \notin \sSenders(\sQueue)
414−}{
415− \sSystem{\sCtxt{P}[ \sReceiveChan \; \sCid_\mathit{to}^r ]_{\sPid}}
416− {\sQueue, (\sCid_\mathit{to}, \sPid_\mathit{fr}, M), \sQueue'}
417− {\sBlacklist}
418− {\sMonitors}
419−\rightarrow
420− \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid}}
421− {\sQueue, \sQueue'}
422− {\sBlacklist}
423− {\sMonitors}
424−} \textsc{ReceiveChan}
425−\end{equation*}
426−%
427−\begin{equation*}
428−\frac{
429− \sRef \text{ fresh}
430−}{
431− \sSystem{\sCtxt{P}[ \sSpawn \; \sNid \; M ]_\sPid}
432− {\sQueue}
433− {\sBlacklist}
434− {\sMonitors}
435−\rightarrow
436− \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_\sPid}
437− {\sQueue \sExtend{\sBlacklist} (\sNid, \sPid, \sSpawn \; \sRef \; M)}
438− {\sBlacklist}
439− {\sMonitors}
440−} \textsc{Spawn-Async}
441−\end{equation*}
442−%
443−\begin{equation*}
444−\frac{
445− \sPid \notin \sSenders(\sQueue)
446−\qquad
447− \sPid' \text{ fresh}
448−\qquad
449− \sNodeOf(\sPid') = \sNid
450−}{
451− \sSystem{\sProcesses}
452− {\sQueue, (\sNid, \sPid, \sSpawn \; \sRef \; M), \sQueue'}
453− {\sBlacklist}
454− {\sMonitors}
455−\rightarrow
456− \sSystem{\sProcesses \sPar M_{\sPid'}}
457− {\sQueue, \sQueue' \sExtend{\sBlacklist} (\sPid, \sNid, \sSpawned \; \sRef \; \sPid')}
458− {\sBlacklist}
459− {\sMonitors}
460−} \textsc{Spawn-Exec}
461−\end{equation*}
462−%
463−where
464−%
465−\begin{equation*}
466−\begin{array}{l@{\;=\;}l@{\hspace{3em}}l}
467− \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
468−&
469− \sQueue, (\sId_\mathit{to}, \sId_\mathit{fr}, M)
470−&
471− \text{if } (\sId_\mathit{to}, \sId_\mathit{fr}) \notin \sBlacklist
472−\\
473− \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
474−&
475− \sQueue
476−&
477− \text{otherwise}
478−\end{array}
479−\end{equation*}
480−%
481−$(\sExtend{\sBlacklist})$ is only defined for serializable payloads.
482−\caption{\label{fig:basic}Basic Primitives}
483−\end{figure}
484−
485−\subsection{Monitoring}
486−
487−When process $P$ on node $\sNid_P$ wants to monitor process $Q$ on node
488−$\sNid_Q$ \emph{both} nodes must be notified. The local node it notified
489−immediately, and a message is sent to to the remote node (rules
490−\textsc{Mon-Loc} and \textsc{Mon-Rem}). As with all messages, the
491−message to the remote node might be lost. When $P$ is disconnected from $Q$ it
492−is the responsibility of $P$'s local node $\sNid_P$ to notify $P$
493−(\textsc{Mon-Dis}); when $Q$ terminates (\textsc{Mon-Ret}) or crashes
494−(\textsc{Mon-Throw}) it is the responsibility of the remote node $\sNid_Q$ to
495−notify $P$.
496−
497−\begin{figure}
498−\small
499−\begin{equation*}
500−\frac{
501− \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
502−\qquad
503− \sNid_\mathit{to} = \sNodeOf(\sId_\mathit{to})
504−\qquad
505− \sRef \text{ fresh}
506−}{
507−\sSystem{\sCtxt{P}[ \sMonitor \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
508− {\sQueue}
509− {\sBlacklist}
510− {\sMonitors}
511−\rightarrow
512− \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_{\sPid_\mathit{fr}}}
513− {\sQueue \sExtend{\sBlacklist} (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to})}
514− {\sBlacklist}
515− {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
516−} \textsc{Mon-Loc}
517−\end{equation*}
518−%
519−\begin{equation*}
520−\frac{
521− \sPid_\mathit{fr} \notin \sSenders(\sQueue)
522−}{
523− \sSystem{\sProcesses}
524− {\sQueue, (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to}), \sQueue'}
525− {\sBlacklist}
526− {\sMonitors}
527−\rightarrow
528− \sSystem{\sProcesses}
529− {\sQueue, \sQueue'}
530− {\sBlacklist}
531− {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{to}, \sRef)}
532−} \textsc{Mon-Rem}
533−\end{equation*}
534−%
535−\begin{equation*}
536−\frac{
537− \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
538−\qquad
539− (\sId_\mathit{to}, \sPid_\mathit{fr}) \in \sBlacklist
540−}{
541− \sSystem{\sProcesses}
542− {\sQueue}
543− {\sBlacklist}
544− {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
545−\rightarrow
546− \sSystem{\sProcesses}
547− {\sQueue, (\sPid_\mathit{fr}, \sId_\mathit{to}, \mathtt{discon} \; \sRef)}
548− {\sBlacklist}
549− {\sMonitors}
550−} \textsc{Mon-Dis}
551−\end{equation*}
552−%
553−\begin{equation*}
554−\frac{
555− \sNodeOf(\sPid) = \sNid
556−}{
557− \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
558− {\sQueue}
559− {\sBlacklist}
560− {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
561−\rightarrow
562− \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
563− {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{died} \; \sRef)}
564− {\sBlacklist}
565− {\sMonitors}
566−} \textsc{Mon-Ret}
567−\end{equation*}
568−%
569−\begin{equation*}
570−\frac{
571− \sNodeOf(\sPid) = \sNid
572−}{
573− \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
574− {\sQueue}
575− {\sBlacklist}
576− {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
577−\rightarrow
578− \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
579− {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{exc} \; \sRef \; M)}
580− {\sBlacklist}
581− {\sMonitors}
582−} \textsc{Mon-Throw}
583−\end{equation*}
584−%
585−\begin{equation*}
586−\frac{
587− \sNodeOf(\sPid) = \sNid
588−\qquad
589− \sPid \notin \sProcesses
590−}{
591− \sSystem{\sProcesses}
592− {\sQueue}
593− {\sBlacklist}
594− {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
595−\rightarrow
596− \sSystem{\sProcesses}
597− {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{unknown} \; \sRef)}
598− {\sBlacklist}
599− {\sMonitors}
600−} \textsc{Mon-Unknown}
601−\end{equation*}
602−%
603−\caption{Monitoring}
604−\end{figure}
605−
606−\subsection{Process Termination}
607−
608−The rules for normal and abnormal process termination are defined in
609−Figure~\ref{fig:termination}. When a process crashes it dies
610−silently, unless monitors are setup.
611−
612−\begin{figure}
613−\small
614−%
615−\begin{equation*}
616−\frac{
617− \sNodeOf(\sPid) = \sNid
618−\qquad
619− \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
620−}{
621− \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
622− {\sQueue}
623− {\sBlacklist}
624− {\sMonitors}
625−\rightarrow
626− \sSystem{\sProcesses}
627− {\sQueue}
628− {\sBlacklist}
629− {\sMonitors}
630−} \textsc{TermRet}
631−\end{equation*}
632−%
633−\begin{equation*}
634−\frac{
635− \sNodeOf(\sPid) = \sNid
636−\qquad
637− \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
638−}{
639− \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
640− {\sQueue}
641− {\sBlacklist}
642− {\sMonitors}
643−\rightarrow
644− \sSystem{\sProcesses}
645− {\sQueue}
646− {\sBlacklist}
647− {\sMonitors}
648−} \textsc{TermThrow}
649−\end{equation*}
650−%
651−\caption{\label{fig:termination}Process Termination}
652−\end{figure}
653−
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−
703−\bibliographystyle{apalike}
704−\bibliography{references}
705−
706−\end{document}
doc/semantics/Makefiledeleted−11
@@ -1,11 +0,0 @@
1−CloudHaskellSemantics.pdf: *.tex *.bib
2− pdflatex CloudHaskellSemantics
3− bibtex CloudHaskellSemantics
4− pdflatex CloudHaskellSemantics
5− pdflatex CloudHaskellSemantics
6−
7−.PHONY: clean
8−clean:
9− rm -f *.aux *.bbl *.blg *.log
10−
11−# vi:set noexpandtab:
doc/semantics/references.bibdeleted−72
@@ -1,72 +0,0 @@
1−@inproceedings{cloudhaskell,
2− author = {Epstein, Jeff and Black, Andrew P. and Peyton-Jones, Simon},
3− title = {Towards {H}askell in the cloud},
4− booktitle = {Proceedings of the 4th ACM symposium on Haskell},
5− series = {Haskell '11},
6− year = {2011},
7− isbn = {978-1-4503-0860-1},
8− location = {Tokyo, Japan},
9− pages = {118--129},
10− numpages = {12},
11− url = {http://doi.acm.org/10.1145/2034675.2034690},
12− doi = {10.1145/2034675.2034690},
13− acmid = {2034690},
14− publisher = {ACM},
15− address = {New York, NY, USA},
16− keywords = {erlang, haskell, message-passing},
17−}
18−
19−@inproceedings{unified,
20− author = {Svensson, Hans and Fredlund, Lars-{\AA}ke and Benac Earle, Clara},
21− title = {A unified semantics for future {E}rlang},
22− booktitle = {Proceedings of the 9th ACM SIGPLAN workshop on Erlang},
23− series = {Erlang '10},
24− year = {2010},
25− isbn = {978-1-4503-0253-1},
26− location = {Baltimore, Maryland, USA},
27− pages = {23--32},
28− numpages = {10},
29− url = {http://doi.acm.org/10.1145/1863509.1863514},
30− doi = {10.1145/1863509.1863514},
31− acmid = {1863514},
32− publisher = {ACM},
33− address = {New York, NY, USA},
34− keywords = {erlang, semantics},
35−}
36−
37−@article{stm,
38− author = {Harris, Tim and Marlow, Simon and Jones, Simon Peyton and Herlihy, Maurice},
39− title = {Composable memory transactions},
40− journal = {Commun. ACM},
41− issue_date = {August 2008},
42− volume = {51},
43− number = {8},
44− month = aug,
45− year = {2008},
46− issn = {0001-0782},
47− pages = {91--100},
48− numpages = {10},
49− url = {http://doi.acm.org/10.1145/1378704.1378725},
50− doi = {10.1145/1378704.1378725},
51− acmid = {1378725},
52− publisher = {ACM},
53− address = {New York, NY, USA},
54−}
55−
56−@inproceedings{erlang,
57− author = {Svensson, Hans and Fredlund, Lars-\AAke},
58− title = {A more accurate semantics for distributed erlang},
59− booktitle = {Proceedings of the 2007 SIGPLAN workshop on ERLANG Workshop},
60− series = {ERLANG '07},
61− year = {2007},
62− isbn = {978-1-59593-675-2},
63− location = {Freiburg, Germany},
64− pages = {43--54},
65− numpages = {12},
66− url = {http://doi.acm.org/10.1145/1292520.1292528},
67− doi = {10.1145/1292520.1292528},
68− acmid = {1292528},
69− publisher = {ACM},
70− address = {New York, NY, USA},
71− keywords = {distributed systems, erlang, model checking, semantics, verification},
72−}
doc/tutorial/tutorial-client.hsnew file+24
@@ -0,0 +1,24 @@
1+import Network.Transport
2+import Network.Transport.TCP (createTransport, defaultTCPParameters)
3+import System.Environment
4+import Control.Monad
5+import Data.ByteString.Char8
6+
7+main :: IO ()
8+main = do
9+ [host, port, serverAddr] <- getArgs
10+ Right transport <- createTransport host port defaultTCPParameters
11+ Right endpoint <- newEndPoint transport
12+
13+ let addr = EndPointAddress (pack serverAddr)
14+-- Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
15+ x <- connect endpoint addr ReliableOrdered defaultConnectHints
16+ let conn = case x of
17+ Right conn -> conn
18+ Left err -> error$ "Error connecting: "++show err
19+ send conn [pack "Hello world"]
20+ close conn
21+
22+ replicateM_ 3 $ receive endpoint >>= print
23+
24+ closeTransport transport
doc/tutorial/tutorial-server.hsnew file+54
@@ -0,0 +1,54 @@
1+import Network.Transport
2+import Network.Transport.TCP (createTransport, defaultTCPParameters)
3+import Control.Concurrent
4+import Data.Map
5+import Control.Exception
6+import System.Environment
7+
8+-- | Server that echoes messages straight back to the origin endpoint.
9+echoServer :: EndPoint -> MVar () -> IO ()
10+echoServer endpoint serverDone = go empty
11+ where
12+ go :: Map ConnectionId (MVar Connection) -> IO ()
13+ go cs = do
14+ event <- receive endpoint
15+ case event of
16+ ConnectionOpened cid rel addr -> do
17+ putStrLn$ " New connection: ID "++show cid++", reliability: "++show rel++", address: "++ show addr
18+ connMVar <- newEmptyMVar
19+ forkIO $ do
20+ Right conn <- connect endpoint addr rel defaultConnectHints
21+ putMVar connMVar conn
22+ go (insert cid connMVar cs)
23+ Received cid payload -> do
24+ forkIO $ do
25+ conn <- readMVar (cs ! cid)
26+ send conn payload
27+ return ()
28+ go cs
29+ ConnectionClosed cid -> do
30+ putStrLn$ " Closed connection: ID "++show cid
31+ forkIO $ do
32+ conn <- readMVar (cs ! cid)
33+ close conn
34+ go (delete cid cs)
35+ EndPointClosed -> do
36+ putStrLn "Echo server exiting"
37+ putMVar serverDone ()
38+
39+onCtrlC :: IO a -> IO () -> IO a
40+p `onCtrlC` q = catchJust isUserInterrupt p (const $ q >> p `onCtrlC` q)
41+ where
42+ isUserInterrupt :: AsyncException -> Maybe ()
43+ isUserInterrupt UserInterrupt = Just ()
44+ isUserInterrupt _ = Nothing
45+
46+main :: IO ()
47+main = do
48+ [host, port] <- getArgs
49+ serverDone <- newEmptyMVar
50+ Right transport <- createTransport host port defaultTCPParameters
51+ Right endpoint <- newEndPoint transport
52+ forkIO $ echoServer endpoint serverDone
53+ putStrLn $ "Echo server started at " ++ show (address endpoint)
54+ readMVar serverDone `onCtrlC` closeTransport transport
install_all.shnew file+11
@@ -0,0 +1,11 @@
1+#!/bin/bash
2+
3+if [ "$GHC" == "" ]; then
4+ GHC=ghc
5+fi
6+if [ "$CABAL" == "" ]; then
7+ CABAL=cabal
8+fi
9+
10+$CABAL install --with-ghc=$GHC ./rank1dynamic ./distributed-static ./network-transport ./network-transport-tcp ./network-transport-inmemory ./distributed-process ./distributed-process-simplelocalnet --reinstall $*
11+
network-transport-composed/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2012, Edsko de Vries
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Edsko de Vries nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
network-transport-composed/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
network-transport-composed/network-transport-composed.cabalnew file+22
@@ -0,0 +1,22 @@
1+name: network-transport-composed
2+version: 0.1.0.0
3+synopsis: Compose network transports
4+-- description:
5+homepage: http://github.com/haskell-distributed/distributed-process
6+license: BSD3
7+license-file: LICENSE
8+author: Edsko de Vries
9+maintainer: edsko@well-typed.com
10+copyright: Well-Typed LLP
11+category: Network
12+build-type: Simple
13+cabal-version: >=1.8
14+
15+library
16+ exposed-modules: Network.Transport.Composed
17+ -- other-modules:
18+ build-depends: base ==4.5.*,
19+ network-transport >= 0.3 && < 0.4
20+ hs-source-dirs: src
21+ ghc-options: -Wall
22+ extensions: BangPatterns
network-transport-composed/src/Network/Transport/Composed.hsnew file+181
@@ -0,0 +1,181 @@
1+-- | Combine two transports into a single new transport
2+--
3+-- Given two transports A and B, endpoints in the combined transport are
4+-- pairs of endpoints for the underlying transports. Consequently, the address
5+-- of an endpoint in the combined transport is a pair of addresses of the
6+-- two underlying endpoints. Events from both underlying endpoints are posted
7+-- to the combined endpoint.
8+--
9+-- When the address of combined endpoint C1 is sent to combined endpoint C2,
10+-- and C2 attempts to make a connection to C1, it needs to choice an underlying
11+-- endpoint to make the connection. The choice is made by a parameter
12+-- 'resolveAddress' of the composed transport.
13+--
14+-- When C2 connects to C1 then it is assumed that whatever transport C2 uses
15+-- to connect to C1, this is the same transport that C1 needs to use to connect
16+-- back to C2.
17+--
18+-- TODO: at the moment the address reported by a ConnectionOpened might differ
19+-- from the address reported by the remote endpoint itself. This might cause
20+-- difficulties?
21+module Network.Transport.Composed
22+ ( ComposedTransport(..)
23+ , createTransport
24+ ) where
25+
26+import qualified Data.ByteString as BSS
27+ ( concat
28+ , length
29+ , splitAt
30+ , singleton
31+ , uncons
32+ )
33+import Control.Monad (void)
34+import Control.Concurrent.Chan (Chan, newChan, writeChan, readChan)
35+import Control.Concurrent (forkIO)
36+import Network.Transport
37+ ( Transport(..)
38+ , EndPointAddress(EndPointAddress)
39+ , TransportError(TransportError)
40+ , NewEndPointErrorCode
41+ , EndPoint(..)
42+ , NewMulticastGroupErrorCode(NewMulticastGroupUnsupported)
43+ , ResolveMulticastGroupErrorCode(ResolveMulticastGroupUnsupported)
44+ , Event(EndPointClosed, ConnectionOpened, ErrorEvent)
45+ , EventErrorCode(EventConnectionLost)
46+ , Reliability
47+ , ConnectHints
48+ , ConnectErrorCode
49+ , Connection
50+ )
51+import Network.Transport.Internal (encodeInt32, decodeInt32)
52+
53+data ComposedTransport = ComposedTransport {
54+ -- | The left transport
55+ transportA :: Transport
56+ -- | The right transport
57+ , transportB :: Transport
58+ -- | Pick a suitable transport for an address
59+ , resolveAddress :: (EndPoint, EndPoint)
60+ -> (EndPointAddress, EndPointAddress)
61+ -> IO (EndPoint, EndPointAddress)
62+ }
63+
64+data ComposedAddress =
65+ LeftAddress EndPointAddress
66+ | RightAddress EndPointAddress
67+ | EitherAddress EndPointAddress EndPointAddress
68+
69+serializeComposedAddress :: ComposedAddress -> EndPointAddress
70+serializeComposedAddress (LeftAddress (EndPointAddress addr)) =
71+ EndPointAddress $ BSS.concat [BSS.singleton 0, addr]
72+serializeComposedAddress (RightAddress (EndPointAddress addr)) =
73+ EndPointAddress $ BSS.concat [BSS.singleton 1, addr]
74+serializeComposedAddress (EitherAddress (EndPointAddress addr1) (EndPointAddress addr2)) =
75+ EndPointAddress $ BSS.concat [BSS.singleton 2, encodeInt32 (BSS.length addr1), addr1, addr2]
76+
77+deserializeComposedAddress :: EndPointAddress -> ComposedAddress
78+deserializeComposedAddress (EndPointAddress addr) =
79+ let (header, remainder) = case BSS.uncons addr of
80+ Just (x, xs) -> (x, xs)
81+ Nothing -> error "deserializeComposedAddress"
82+ in case header of
83+ 0 -> LeftAddress (EndPointAddress remainder)
84+ 1 -> RightAddress (EndPointAddress remainder)
85+ 2 -> let (len, remainder') = BSS.splitAt 4 remainder
86+ (a, b) = BSS.splitAt (decodeInt32 len) remainder'
87+ in EitherAddress (EndPointAddress a) (EndPointAddress b)
88+ _ -> error "deserializeComposedAddress"
89+
90+createTransport :: ComposedTransport -> IO Transport
91+createTransport composed =
92+ return Transport {
93+ newEndPoint = apiNewEndPoint composed
94+ , closeTransport = apiCloseTransport composed
95+ }
96+
97+apiNewEndPoint :: ComposedTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
98+apiNewEndPoint composed = do
99+ mEndPoints <- createEndPoints
100+ case mEndPoints of
101+ Left err ->
102+ return $ Left err
103+ Right (endPointA, endPointB) -> do
104+ let addr = serializeComposedAddress (EitherAddress (address endPointA) (address endPointB))
105+ chan <- newChan
106+ void . forkIO $ forwardEvents endPointA chan LeftAddress
107+ void . forkIO $ forwardEvents endPointB chan RightAddress
108+ return . Right $ EndPoint {
109+ receive = readChan chan
110+ , address = addr
111+ , connect = apiConnect composed endPointA endPointB
112+ , closeEndPoint = apiCloseEndPoint endPointA endPointB
113+ , newMulticastGroup = return . Left $ newMulticastGroupError
114+ , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
115+ }
116+ where
117+ newMulticastGroupError =
118+ TransportError NewMulticastGroupUnsupported "Multicast not supported"
119+ resolveMulticastGroupError =
120+ TransportError ResolveMulticastGroupUnsupported "Multicast not supported"
121+
122+ createEndPoints = do
123+ mEndPointA <- newEndPoint (transportA composed)
124+ case mEndPointA of
125+ Left err -> return $ Left err
126+ Right endPointA -> do
127+ mEndPointB <- newEndPoint (transportB composed)
128+ case mEndPointB of
129+ Left err -> do
130+ closeEndPoint endPointA
131+ return $ Left err
132+ Right endPointB ->
133+ return $ Right (endPointA, endPointB)
134+
135+ forwardEvents :: EndPoint -> Chan Event -> (EndPointAddress -> ComposedAddress) -> IO ()
136+ forwardEvents endPoint chan inj = go
137+ where
138+ go = do
139+ event <- receive endPoint
140+ case event of
141+ ConnectionOpened cid rel addr -> do
142+ let addr' = serializeComposedAddress (inj addr)
143+ writeChan chan (ConnectionOpened cid rel addr')
144+ go
145+ ErrorEvent (TransportError (EventConnectionLost addr) msg) -> do
146+ let addr' = serializeComposedAddress (inj addr)
147+ writeChan chan $ ErrorEvent (TransportError (EventConnectionLost addr') msg)
148+ go
149+ EndPointClosed -> do
150+ writeChan chan event
151+ return ()
152+ _ -> do
153+ writeChan chan event
154+ go
155+
156+apiCloseTransport :: ComposedTransport -> IO ()
157+apiCloseTransport composed = do
158+ closeTransport (transportA composed)
159+ closeTransport (transportB composed)
160+
161+apiConnect :: ComposedTransport
162+ -> EndPoint
163+ -> EndPoint
164+ -> EndPointAddress
165+ -> Reliability
166+ -> ConnectHints
167+ -> IO (Either (TransportError ConnectErrorCode) Connection)
168+apiConnect composed endPointA endPointB theirComposedAddr rel hints =
169+ case deserializeComposedAddress theirComposedAddr of
170+ LeftAddress addr ->
171+ connect endPointA addr rel hints
172+ RightAddress addr ->
173+ connect endPointB addr rel hints
174+ EitherAddress addrA addrB -> do
175+ (endPoint, addr) <- resolveAddress composed (endPointA, endPointB) (addrA, addrB)
176+ connect endPoint addr rel hints
177+
178+apiCloseEndPoint :: EndPoint -> EndPoint -> IO ()
179+apiCloseEndPoint endPointA endPointB = do
180+ closeEndPoint endPointA
181+ closeEndPoint endPointB
network-transport-inmemory/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Well-Typed LLP, 2011-2012
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
network-transport-inmemory/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
network-transport-inmemory/network-transport-inmemory.cabalnew file+44
@@ -0,0 +1,44 @@
1+Name: network-transport-inmemory
2+Version: 0.3.0
3+Cabal-Version: >=1.8
4+Build-Type: Simple
5+License: BSD3
6+License-file: LICENSE
7+Copyright: Well-Typed LLP
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: edsko@well-typed.com, dcoutts@well-typed.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com
13+Synopsis: In-memory instantiation of Network.Transport
14+Description: In-memory instantiation of Network.Transport
15+Tested-With: GHC==7.0.4 GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
16+Category: Network
17+
18+Library
19+ Build-Depends: base >= 4.3 && < 5,
20+ network-transport >= 0.3 && < 0.4,
21+ data-accessor >= 0.2 && < 0.3,
22+ bytestring >= 0.9 && < 0.10,
23+ containers >= 0.4 && < 0.5
24+ Exposed-modules: Network.Transport.Chan
25+ ghc-options: -Wall -fno-warn-unused-do-bind
26+ HS-Source-Dirs: src
27+
28+Test-Suite TestMulticastInMemory
29+ Type: exitcode-stdio-1.0
30+ Build-Depends: base >= 4.3 && < 5,
31+ network-transport-inmemory >= 0.3 && < 0.4,
32+ network-transport-tests >= 0.1 && < 0.2
33+ Main-Is: TestMulticastInMemory.hs
34+ ghc-options: -Wall -fno-warn-unused-do-bind
35+ HS-Source-Dirs: tests
36+
37+Test-Suite TestInMemory
38+ Type: exitcode-stdio-1.0
39+ Build-Depends: base >= 4.3 && < 5,
40+ network-transport-inmemory >= 0.3 && < 0.4,
41+ network-transport-tests >= 0.1 && < 0.2
42+ Main-Is: TestInMemory.hs
43+ ghc-options: -Wall -fno-warn-unused-do-bind
44+ HS-Source-Dirs: tests
network-transport-inmemory/src/Network/Transport/Chan.hsnew file+157
@@ -0,0 +1,157 @@
1+-- | In-memory implementation of the Transport API.
2+module Network.Transport.Chan (createTransport) where
3+
4+import Network.Transport
5+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
6+import Control.Applicative ((<$>))
7+import Control.Category ((>>>))
8+import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, modifyMVar_, readMVar)
9+import Control.Exception (throwIO)
10+import Control.Monad (forM_, when)
11+import Data.Map (Map)
12+import qualified Data.Map as Map (empty, insert, size, delete, findWithDefault)
13+import Data.Set (Set)
14+import qualified Data.Set as Set (empty, elems, insert, delete)
15+import Data.ByteString (ByteString)
16+import qualified Data.ByteString.Char8 as BSC (pack)
17+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
18+import qualified Data.Accessor.Container as DAC (mapMaybe)
19+
20+-- Global state: next available "address", mapping from addresses to channels and next available connection
21+data TransportState = State { _channels :: Map EndPointAddress (Chan Event)
22+ , _nextConnectionId :: Map EndPointAddress ConnectionId
23+ , _multigroups :: Map MulticastAddress (MVar (Set EndPointAddress))
24+ }
25+
26+-- | Create a new Transport.
27+--
28+-- Only a single transport should be created per Haskell process
29+-- (threads can, and should, create their own endpoints though).
30+createTransport :: IO Transport
31+createTransport = do
32+ state <- newMVar State { _channels = Map.empty
33+ , _nextConnectionId = Map.empty
34+ , _multigroups = Map.empty
35+ }
36+ return Transport { newEndPoint = apiNewEndPoint state
37+ , closeTransport = throwIO (userError "closeEndPoint not implemented")
38+ }
39+
40+-- | Create a new end point
41+apiNewEndPoint :: MVar TransportState -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
42+apiNewEndPoint state = do
43+ chan <- newChan
44+ addr <- modifyMVar state $ \st -> do
45+ let addr = EndPointAddress . BSC.pack . show . Map.size $ st ^. channels
46+ return ((channelAt addr ^= chan) . (nextConnectionIdAt addr ^= 1) $ st, addr)
47+ return . Right $ EndPoint { receive = readChan chan
48+ , address = addr
49+ , connect = apiConnect addr state
50+ , closeEndPoint = throwIO (userError "closeEndPoint not implemented")
51+ , newMulticastGroup = apiNewMulticastGroup state addr
52+ , resolveMulticastGroup = apiResolveMulticastGroup state addr
53+ }
54+
55+-- | Create a new connection
56+apiConnect :: EndPointAddress
57+ -> MVar TransportState
58+ -> EndPointAddress
59+ -> Reliability
60+ -> ConnectHints
61+ -> IO (Either (TransportError ConnectErrorCode) Connection)
62+apiConnect myAddress state theirAddress _reliability _hints = do
63+ (chan, conn) <- modifyMVar state $ \st -> do
64+ let chan = st ^. channelAt theirAddress
65+ let conn = st ^. nextConnectionIdAt theirAddress
66+ return (nextConnectionIdAt theirAddress ^: (+ 1) $ st, (chan, conn))
67+ writeChan chan $ ConnectionOpened conn ReliableOrdered myAddress
68+ connAlive <- newMVar True
69+ return . Right $ Connection { send = apiSend chan conn connAlive
70+ , close = apiClose chan conn connAlive
71+ }
72+
73+-- | Send a message over a connection
74+apiSend :: Chan Event -> ConnectionId -> MVar Bool -> [ByteString] -> IO (Either (TransportError SendErrorCode) ())
75+apiSend chan conn connAlive msg =
76+ modifyMVar connAlive $ \alive ->
77+ if alive
78+ then do
79+ writeChan chan (Received conn msg)
80+ return (alive, Right ())
81+ else
82+ return (alive, Left (TransportError SendFailed "Connection closed"))
83+
84+-- | Close a connection
85+apiClose :: Chan Event -> ConnectionId -> MVar Bool -> IO ()
86+apiClose chan conn connAlive =
87+ modifyMVar_ connAlive $ \alive -> do
88+ when alive . writeChan chan $ ConnectionClosed conn
89+ return False
90+
91+-- | Create a new multicast group
92+apiNewMulticastGroup :: MVar TransportState -> EndPointAddress -> IO (Either (TransportError NewMulticastGroupErrorCode) MulticastGroup)
93+apiNewMulticastGroup state ourAddress = do
94+ group <- newMVar Set.empty
95+ groupAddr <- modifyMVar state $ \st -> do
96+ let addr = MulticastAddress . BSC.pack . show . Map.size $ st ^. multigroups
97+ return (multigroupAt addr ^= group $ st, addr)
98+ return . Right $ createMulticastGroup state ourAddress groupAddr group
99+
100+-- | Construct a multicast group
101+--
102+-- When the group is deleted some endpoints may still receive messages, but
103+-- subsequent calls to resolveMulticastGroup will fail. This mimicks the fact
104+-- that some multicast messages may still be in transit when the group is
105+-- deleted.
106+createMulticastGroup :: MVar TransportState -> EndPointAddress -> MulticastAddress -> MVar (Set EndPointAddress) -> MulticastGroup
107+createMulticastGroup state ourAddress groupAddress group =
108+ MulticastGroup { multicastAddress = groupAddress
109+ , deleteMulticastGroup = modifyMVar_ state $ return . (multigroups ^: Map.delete groupAddress)
110+ , maxMsgSize = Nothing
111+ , multicastSend = \payload -> do
112+ cs <- (^. channels) <$> readMVar state
113+ es <- readMVar group
114+ forM_ (Set.elems es) $ \ep -> do
115+ let ch = cs ^. at ep "Invalid endpoint"
116+ writeChan ch (ReceivedMulticast groupAddress payload)
117+ , multicastSubscribe = modifyMVar_ group $ return . Set.insert ourAddress
118+ , multicastUnsubscribe = modifyMVar_ group $ return . Set.delete ourAddress
119+ , multicastClose = return ()
120+ }
121+
122+-- | Resolve a multicast group
123+apiResolveMulticastGroup :: MVar TransportState
124+ -> EndPointAddress
125+ -> MulticastAddress
126+ -> IO (Either (TransportError ResolveMulticastGroupErrorCode) MulticastGroup)
127+apiResolveMulticastGroup state ourAddress groupAddress = do
128+ group <- (^. (multigroups >>> DAC.mapMaybe groupAddress)) <$> readMVar state
129+ case group of
130+ Nothing -> return . Left $ TransportError ResolveMulticastGroupNotFound ("Group " ++ show groupAddress ++ " not found")
131+ Just mvar -> return . Right $ createMulticastGroup state ourAddress groupAddress mvar
132+
133+--------------------------------------------------------------------------------
134+-- Lens definitions --
135+--------------------------------------------------------------------------------
136+
137+channels :: Accessor TransportState (Map EndPointAddress (Chan Event))
138+channels = accessor _channels (\ch st -> st { _channels = ch })
139+
140+nextConnectionId :: Accessor TransportState (Map EndPointAddress ConnectionId)
141+nextConnectionId = accessor _nextConnectionId (\cid st -> st { _nextConnectionId = cid })
142+
143+multigroups :: Accessor TransportState (Map MulticastAddress (MVar (Set EndPointAddress)))
144+multigroups = accessor _multigroups (\gs st -> st { _multigroups = gs })
145+
146+at :: Ord k => k -> String -> Accessor (Map k v) v
147+at k err = accessor (Map.findWithDefault (error err) k) (Map.insert k)
148+
149+channelAt :: EndPointAddress -> Accessor TransportState (Chan Event)
150+channelAt addr = channels >>> at addr "Invalid channel"
151+
152+nextConnectionIdAt :: EndPointAddress -> Accessor TransportState ConnectionId
153+nextConnectionIdAt addr = nextConnectionId >>> at addr "Invalid connection ID"
154+
155+multigroupAt :: MulticastAddress -> Accessor TransportState (MVar (Set EndPointAddress))
156+multigroupAt addr = multigroups >>> at addr "Invalid multigroup"
157+
network-transport-inmemory/tests/TestInMemory.hsnew file+8
@@ -0,0 +1,8 @@
1+module Main where
2+
3+import Network.Transport.Tests
4+import Network.Transport.Chan
5+import Control.Applicative ((<$>))
6+
7+main :: IO ()
8+main = testTransport (Right <$> createTransport)
network-transport-inmemory/tests/TestMulticastInMemory.hsnew file+7
@@ -0,0 +1,7 @@
1+module Main where
2+
3+import Network.Transport.Tests.Multicast
4+import Network.Transport.Chan (createTransport)
5+
6+main :: IO ()
7+main = createTransport >>= testMulticast
network-transport-tcp/ChangeLognew file+21
@@ -0,0 +1,21 @@
1+<<date>> Edsko de Vries <edsko@well-typed.com> 0.3.0
2+
3+* Implement new disconnection semantics
4+* Make 'connect' asynchronous (sender allocated connection IDs)
5+
6+2012-08-20 Edsko de Vries <edsko@well-typed.com> 0.2.0.3
7+
8+* Allow for colons in hostnames (for IPv6)
9+
10+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.0.2
11+
12+* Base 4.6 compatibility
13+* Relax package contraints on bytestring and containers
14+
15+2012-07-12 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
16+
17+* Fix bug in recvExact
18+
19+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
20+
21+* Initial release.
network-transport-tcp/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Well-Typed LLP, 2011-2012
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
network-transport-tcp/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
network-transport-tcp/benchmarks/Headers.gnuplotnew file+16
@@ -0,0 +1,16 @@
1+set title "Roundtrip (us)"
2+set yrange [0:150]
3+plot "JustPingHaskellNT.data" smooth bezier with lines title "JustPingHaskell (NOT -threaded)", \
4+ "JustPingHaskell.data" smooth bezier with lines title "JustPingHaskell", \
5+ "JustPingWithHeader.data" smooth bezier with lines title "JustPingWithHeader", \
6+ "JustPingOneRecv.data" smooth bezier with lines title "JustPingOneRecv", \
7+ "JustPingCacheHeader.data" smooth bezier with lines title "JustPingCacheHeader", \
8+ "JustPingC.data" smooth bezier with lines title "JustPingC"
9+set terminal postscript color
10+set output "Headers.ps"
11+plot "JustPingHaskellNT.data" smooth bezier with lines title "JustPingHaskell (NOT -threaded)", \
12+ "JustPingHaskell.data" smooth bezier with lines title "JustPingHaskell", \
13+ "JustPingWithHeader.data" smooth bezier with lines title "JustPingWithHeader", \
14+ "JustPingOneRecv.data" smooth bezier with lines title "JustPingOneRecv", \
15+ "JustPingCacheHeader.data" smooth bezier with lines title "JustPingCacheHeader", \
16+ "JustPingC.data" smooth bezier with lines title "JustPingC"
network-transport-tcp/benchmarks/Indirection.gnuplotnew file+16
@@ -0,0 +1,16 @@
1+set title "Roundtrip (us)"
2+set yrange [0:150]
3+plot "JustPingWithHeader.data" smooth bezier with lines title "JustPingWithHeader", \
4+ "JustPingThroughChan.data" smooth bezier with lines title "JustPingThroughChan", \
5+ "JustPingThroughMVar.data" smooth bezier with lines title "JustPingThroughMVar", \
6+ "JustPingTwoSocketPairs.data" smooth bezier with lines title "JustPingTwoSocketPairs", \
7+ "JustPingTwoSocketPairsND.data" smooth bezier with lines title "JustPingTwoSocketPairs (--NoDelay)", \
8+ "JustPingTransport.data" smooth bezier with lines title "JustPingTransport"
9+set terminal postscript color
10+set output "Indirection.ps"
11+plot "JustPingWithHeader.data" smooth bezier with lines title "JustPingWithHeader", \
12+ "JustPingThroughChan.data" smooth bezier with lines title "JustPingThroughChan", \
13+ "JustPingThroughMVar.data" smooth bezier with lines title "JustPingThroughMVar", \
14+ "JustPingTwoSocketPairs.data" smooth bezier with lines title "JustPingTwoSocketPairs", \
15+ "JustPingTwoSocketPairsND.data" smooth bezier with lines title "JustPingTwoSocketPairs (--NoDelay)", \
16+ "JustPingTransport.data" smooth bezier with lines title "JustPingTransport"
network-transport-tcp/benchmarks/JustPingC.cnew file+150
@@ -0,0 +1,150 @@
1+#include <stdio.h>
2+#include <stdlib.h>
3+#include <sys/types.h>
4+#include <sys/socket.h>
5+#include <netdb.h>
6+#include <string.h>
7+#include <unistd.h>
8+#include <signal.h>
9+#include <errno.h>
10+#include <time.h>
11+#include <sys/time.h>
12+#include <math.h>
13+
14+// Note: this is not consistent across CPUs (and hence across threads on multicore machines)
15+double timestamp() {
16+ struct timeval tp;
17+ gettimeofday(&tp, NULL);
18+ return ((double) tp.tv_sec) * 1e6 + (double) tp.tv_usec;
19+}
20+
21+int server() {
22+ printf("starting server\n");
23+
24+ struct addrinfo hints, *res;
25+ int error, server_socket;
26+
27+ memset(&hints, 0, sizeof(hints));
28+ hints.ai_family = PF_INET;
29+ hints.ai_socktype = SOCK_STREAM;
30+ hints.ai_flags = AI_PASSIVE;
31+
32+ error = getaddrinfo(NULL, "8080", &hints, &res);
33+ if(error) {
34+ printf("server error: %s\n", gai_strerror(error));
35+ return -1;
36+ }
37+
38+ server_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
39+ if(server_socket < 0) {
40+ printf("server error: could not create socket\n");
41+ return -1;
42+ }
43+
44+ int yes = 1;
45+ if(setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
46+ printf("server error: could not set socket options\n");
47+ return -1;
48+ }
49+
50+ if(bind(server_socket, res->ai_addr, res->ai_addrlen) < 0) {
51+ printf("server error: could not bind to socket\n");
52+ return -1;
53+ }
54+
55+ listen(server_socket, 5);
56+
57+ int client_socket;
58+ struct sockaddr_storage client_addr;
59+ socklen_t addr_size;
60+ client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size);
61+
62+ for(;;) {
63+ char* buf = malloc(8);
64+ ssize_t read = recv(client_socket, buf, 8, 0);
65+ if(read == 0) {
66+ free(buf);
67+ break;
68+ }
69+ // printf("server received '%s'\n", buf);
70+ send(client_socket, buf, 8, 0);
71+ free(buf);
72+ }
73+
74+ freeaddrinfo(res);
75+ return 0;
76+}
77+
78+int client(int pings) {
79+ printf("starting client\n");
80+
81+ struct addrinfo hints, *res;
82+ int error, client_socket, i;
83+
84+ memset(&hints, 0, sizeof(hints));
85+ hints.ai_family = PF_INET;
86+ hints.ai_socktype = SOCK_STREAM;
87+
88+ error = getaddrinfo("127.0.0.1", "8080", &hints, &res);
89+ if(error) {
90+ printf("client error: %s\n", gai_strerror(error));
91+ return -1;
92+ }
93+
94+ client_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
95+ if(client_socket < 0) {
96+ printf("client error: could not create socket\n");
97+ return -1;
98+ }
99+
100+ if(connect(client_socket, res->ai_addr, res->ai_addrlen) < 0) {
101+ printf("client error: could not connect: %s\n", strerror(errno));
102+ return -1;
103+ }
104+
105+ for(i = 0; i < pings; i++) {
106+ double timestamp_before = timestamp();
107+
108+ send(client_socket, "ping123", 8, 0);
109+
110+ char *buf = malloc(8);
111+ ssize_t read = recv(client_socket, buf, 8, 0);
112+
113+ if(read == 0) {
114+ printf("server exited prematurely\n");
115+ free(buf);
116+ break;
117+ }
118+
119+ // printf("client received '%s'\n", buf);
120+ free(buf);
121+
122+ double timestamp_after = timestamp();
123+ fprintf(stderr, "%i %lf\n", i, timestamp_after - timestamp_before);
124+ }
125+
126+ printf("client did %d pings\n", pings);
127+
128+ freeaddrinfo(res);
129+ return 0;
130+}
131+
132+int usage(int argc, char** argv) {
133+ printf("usage: %s <number of pings>\n", argv[0]);
134+ return -1;
135+}
136+
137+int main(int argc, char** argv) {
138+ if(argc != 2) {
139+ return usage(argc, argv);
140+ }
141+
142+ if(fork() == 0) {
143+ // TODO: we should wait until we know the server is ready
144+ int pings = 0;
145+ sscanf(argv[1], "%d", &pings);
146+ return client(pings);
147+ } else {
148+ return server();
149+ }
150+}
network-transport-tcp/benchmarks/JustPingCacheHeader.hsnew file+134
@@ -0,0 +1,134 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+
31+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
32+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
33+
34+main :: IO ()
35+main = do
36+ [pingsStr] <- getArgs
37+ serverReady <- newEmptyMVar
38+ clientDone <- newEmptyMVar
39+
40+ -- Start the server
41+ forkIO $ do
42+ putStrLn "server: creating TCP connection"
43+ serverAddrs <- getAddrInfo
44+ (Just (defaultHints { addrFlags = [AI_PASSIVE] } ))
45+ Nothing
46+ (Just "8080")
47+ let serverAddr = head serverAddrs
48+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
49+ setSocketOption sock ReuseAddr 1
50+ bindSocket sock (addrAddress serverAddr)
51+
52+ putStrLn "server: awaiting client connection"
53+ putMVar serverReady ()
54+ listen sock 1
55+ (clientSock, clientAddr) <- accept sock
56+
57+ putStrLn "server: listening for pings"
58+ pong clientSock
59+
60+ -- Start the client
61+ forkIO $ do
62+ takeMVar serverReady
63+ let pings = read pingsStr
64+ serverAddrs <- getAddrInfo
65+ Nothing
66+ (Just "127.0.0.1")
67+ (Just "8080")
68+ let serverAddr = head serverAddrs
69+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
70+
71+ N.connect sock (addrAddress serverAddr)
72+
73+ ping sock pings
74+ putMVar clientDone ()
75+
76+ -- Wait for the client to finish
77+ takeMVar clientDone
78+
79+pingMessage :: ByteString
80+pingMessage = pack "ping123"
81+
82+ping :: Socket -> Int -> IO ()
83+ping sock pings = go pings
84+ where
85+ go :: Int -> IO ()
86+ go 0 = do
87+ putStrLn $ "client did " ++ show pings ++ " pings"
88+ go !i = do
89+ before <- getCurrentTime
90+ send sock pingMessage
91+ bs <- recv sock 8
92+ after <- getCurrentTime
93+ -- putStrLn $ "client received " ++ unpack bs
94+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
95+ hPutStrLn stderr $ show i ++ " " ++ show latency
96+ go (i - 1)
97+
98+pong :: Socket -> IO ()
99+pong sock = do
100+ bs <- recv sock 8
101+ -- putStrLn $ "server received " ++ unpack bs
102+ when (BS.length bs > 0) $ do
103+ send sock bs
104+ pong sock
105+
106+-- | Wrapper around NBS.recv (for profiling)
107+recv :: Socket -> Int -> IO ByteString
108+recv sock i = do
109+ (header, payload) <- BS.splitAt 4 `fmap` NBS.recv sock (4 + i)
110+ -- Ignore header
111+ return payload
112+
113+-- | Cached header
114+header :: ByteString
115+header = pack "fake"
116+
117+-- | Wrapper around NBS.send (for profiling)
118+send :: Socket -> ByteString -> IO ()
119+send sock bs = do
120+ NBS.sendMany sock [header, bs]
121+
122+-- | Encode length (manual for now)
123+encodeLength :: Int32 -> IO ByteString
124+encodeLength i32 =
125+ BSI.create 4 $ \p ->
126+ pokeByteOff p 0 (htonl (fromIntegral i32))
127+
128+-- | Decode length (manual for now)
129+decodeLength :: ByteString -> IO Int32
130+decodeLength bs =
131+ let (fp, _, _) = BSI.toForeignPtr bs in
132+ withForeignPtr fp $ \p -> do
133+ w32 <- peekByteOff p 0
134+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/JustPingHaskell.hsnew file+105
@@ -0,0 +1,105 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+
27+main :: IO ()
28+main = do
29+ [pingsStr] <- getArgs
30+ serverReady <- newEmptyMVar
31+ clientDone <- newEmptyMVar
32+
33+ -- Start the server
34+ forkIO $ do
35+ putStrLn "server: creating TCP connection"
36+ serverAddrs <- getAddrInfo
37+ (Just (defaultHints { addrFlags = [AI_PASSIVE] } ))
38+ Nothing
39+ (Just "8080")
40+ let serverAddr = head serverAddrs
41+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
42+ setSocketOption sock ReuseAddr 1
43+ bindSocket sock (addrAddress serverAddr)
44+
45+ putStrLn "server: awaiting client connection"
46+ putMVar serverReady ()
47+ listen sock 1
48+ (clientSock, clientAddr) <- accept sock
49+
50+ putStrLn "server: listening for pings"
51+ pong clientSock
52+
53+ -- Start the client
54+ forkIO $ do
55+ takeMVar serverReady
56+ let pings = read pingsStr
57+ serverAddrs <- getAddrInfo
58+ Nothing
59+ (Just "127.0.0.1")
60+ (Just "8080")
61+ let serverAddr = head serverAddrs
62+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
63+
64+ N.connect sock (addrAddress serverAddr)
65+
66+ ping sock pings
67+ putMVar clientDone ()
68+
69+ -- Wait for the client to finish
70+ takeMVar clientDone
71+
72+pingMessage :: ByteString
73+pingMessage = pack "ping123"
74+
75+ping :: Socket -> Int -> IO ()
76+ping sock pings = go pings
77+ where
78+ go :: Int -> IO ()
79+ go 0 = do
80+ putStrLn $ "client did " ++ show pings ++ " pings"
81+ go !i = do
82+ before <- getCurrentTime
83+ send sock pingMessage
84+ bs <- recv sock 8
85+ after <- getCurrentTime
86+ -- putStrLn $ "client received " ++ unpack bs
87+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
88+ hPutStrLn stderr $ show i ++ " " ++ show latency
89+ go (i - 1)
90+
91+pong :: Socket -> IO ()
92+pong sock = do
93+ bs <- recv sock 8
94+ -- putStrLn $ "server received " ++ unpack bs
95+ when (BS.length bs > 0) $ do
96+ send sock bs
97+ pong sock
98+
99+-- | Wrapper around NBS.recv (for profiling)
100+recv :: Socket -> Int -> IO ByteString
101+recv = NBS.recv
102+
103+-- | Wrapper around NBS.send (for profiling)
104+send :: Socket -> ByteString -> IO Int
105+send = NBS.send
network-transport-tcp/benchmarks/JustPingOneRecv.hsnew file+131
@@ -0,0 +1,131 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+
31+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
32+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
33+
34+main :: IO ()
35+main = do
36+ [pingsStr] <- getArgs
37+ serverReady <- newEmptyMVar
38+ clientDone <- newEmptyMVar
39+
40+ -- Start the server
41+ forkIO $ do
42+ putStrLn "server: creating TCP connection"
43+ serverAddrs <- getAddrInfo
44+ (Just (defaultHints { addrFlags = [AI_PASSIVE] } ))
45+ Nothing
46+ (Just "8080")
47+ let serverAddr = head serverAddrs
48+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
49+ setSocketOption sock ReuseAddr 1
50+ bindSocket sock (addrAddress serverAddr)
51+
52+ putStrLn "server: awaiting client connection"
53+ putMVar serverReady ()
54+ listen sock 1
55+ (clientSock, clientAddr) <- accept sock
56+
57+ putStrLn "server: listening for pings"
58+ pong clientSock
59+
60+ -- Start the client
61+ forkIO $ do
62+ takeMVar serverReady
63+ let pings = read pingsStr
64+ serverAddrs <- getAddrInfo
65+ Nothing
66+ (Just "127.0.0.1")
67+ (Just "8080")
68+ let serverAddr = head serverAddrs
69+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
70+
71+ N.connect sock (addrAddress serverAddr)
72+
73+ ping sock pings
74+ putMVar clientDone ()
75+
76+ -- Wait for the client to finish
77+ takeMVar clientDone
78+
79+pingMessage :: ByteString
80+pingMessage = pack "ping123"
81+
82+ping :: Socket -> Int -> IO ()
83+ping sock pings = go pings
84+ where
85+ go :: Int -> IO ()
86+ go 0 = do
87+ putStrLn $ "client did " ++ show pings ++ " pings"
88+ go !i = do
89+ before <- getCurrentTime
90+ send sock pingMessage
91+ bs <- recv sock 8
92+ after <- getCurrentTime
93+ -- putStrLn $ "client received " ++ unpack bs
94+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
95+ hPutStrLn stderr $ show i ++ " " ++ show latency
96+ go (i - 1)
97+
98+pong :: Socket -> IO ()
99+pong sock = do
100+ bs <- recv sock 8
101+ -- putStrLn $ "server received " ++ unpack bs
102+ when (BS.length bs > 0) $ do
103+ send sock bs
104+ pong sock
105+
106+-- | Wrapper around NBS.recv (for profiling)
107+recv :: Socket -> Int -> IO ByteString
108+recv sock i = do
109+ (header, payload) <- BS.splitAt 4 `fmap` NBS.recv sock (4 + i)
110+ length <- decodeLength header -- Ignored
111+ return payload
112+
113+-- | Wrapper around NBS.send (for profiling)
114+send :: Socket -> ByteString -> IO ()
115+send sock bs = do
116+ length <- encodeLength (fromIntegral (BS.length bs))
117+ NBS.sendMany sock [length, bs]
118+
119+-- | Encode length (manual for now)
120+encodeLength :: Int32 -> IO ByteString
121+encodeLength i32 =
122+ BSI.create 4 $ \p ->
123+ pokeByteOff p 0 (htonl (fromIntegral i32))
124+
125+-- | Decode length (manual for now)
126+decodeLength :: ByteString -> IO Int32
127+decodeLength bs =
128+ let (fp, _, _) = BSI.toForeignPtr bs in
129+ withForeignPtr fp $ \p -> do
130+ w32 <- peekByteOff p 0
131+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/JustPingThroughChan.hsnew file+127
@@ -0,0 +1,127 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfo, AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
31+
32+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
33+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
34+
35+passive :: Maybe AddrInfo
36+passive = Just (defaultHints { addrFlags = [AI_PASSIVE] })
37+
38+main = do
39+ [pingsStr] <- getArgs
40+ serverReady <- newEmptyMVar
41+ clientDone <- newEmptyMVar
42+
43+ -- Start the server
44+ forkIO $ do
45+ -- Initialize the server
46+ serverAddr:_ <- getAddrInfo passive Nothing (Just "8080")
47+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
48+ setSocketOption sock ReuseAddr 1
49+ bindSocket sock (addrAddress serverAddr)
50+ listen sock 1
51+
52+ -- Set up multiplexing channel
53+ multiplexChannel <- newChan
54+
55+ -- Wait for incoming connections (pings from the client)
56+ putMVar serverReady ()
57+ (clientSock, pingAddr) <- accept sock
58+ forkIO $ socketToChan clientSock multiplexChannel
59+
60+ -- Reply to the client
61+ forever $ readChan multiplexChannel >>= send clientSock
62+
63+ -- Start the client
64+ forkIO $ do
65+ takeMVar serverReady
66+ serverAddr:_ <- getAddrInfo Nothing (Just "127.0.0.1") (Just "8080")
67+ clientSock <- socket (addrFamily serverAddr) Stream defaultProtocol
68+ N.connect clientSock (addrAddress serverAddr)
69+ ping clientSock (read pingsStr)
70+ putMVar clientDone ()
71+
72+ -- Wait for the client to finish
73+ takeMVar clientDone
74+
75+socketToChan :: Socket -> Chan ByteString -> IO ()
76+socketToChan sock chan = go
77+ where
78+ go = do bs <- recv sock
79+ when (BS.length bs > 0) $ do
80+ writeChan chan bs
81+ go
82+
83+pingMessage :: ByteString
84+pingMessage = pack "ping123"
85+
86+ping :: Socket -> Int -> IO ()
87+ping sock pings = go pings
88+ where
89+ go :: Int -> IO ()
90+ go 0 = do
91+ putStrLn $ "client did " ++ show pings ++ " pings"
92+ go !i = do
93+ before <- getCurrentTime
94+ send sock pingMessage
95+ bs <- recv sock
96+ after <- getCurrentTime
97+ -- putStrLn $ "client received " ++ unpack bs
98+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
99+ hPutStrLn stderr $ show i ++ " " ++ show latency
100+ go (i - 1)
101+
102+-- | Receive a package
103+recv :: Socket -> IO ByteString
104+recv sock = do
105+ header <- NBS.recv sock 4
106+ length <- decodeLength header
107+ NBS.recv sock (fromIntegral (length :: Int32))
108+
109+-- | Send a package
110+send :: Socket -> ByteString -> IO ()
111+send sock bs = do
112+ length <- encodeLength (fromIntegral (BS.length bs))
113+ NBS.sendMany sock [length, bs]
114+
115+-- | Encode length (manual for now)
116+encodeLength :: Int32 -> IO ByteString
117+encodeLength i32 =
118+ BSI.create 4 $ \p ->
119+ pokeByteOff p 0 (htonl (fromIntegral i32))
120+
121+-- | Decode length (manual for now)
122+decodeLength :: ByteString -> IO Int32
123+decodeLength bs =
124+ let (fp, _, _) = BSI.toForeignPtr bs in
125+ withForeignPtr fp $ \p -> do
126+ w32 <- peekByteOff p 0
127+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/JustPingThroughMVar.hsnew file+127
@@ -0,0 +1,127 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfo, AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (MVar, newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
31+
32+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
33+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
34+
35+passive :: Maybe AddrInfo
36+passive = Just (defaultHints { addrFlags = [AI_PASSIVE] })
37+
38+main = do
39+ [pingsStr] <- getArgs
40+ serverReady <- newEmptyMVar
41+ clientDone <- newEmptyMVar
42+
43+ -- Start the server
44+ forkIO $ do
45+ -- Initialize the server
46+ serverAddr:_ <- getAddrInfo passive Nothing (Just "8080")
47+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
48+ setSocketOption sock ReuseAddr 1
49+ bindSocket sock (addrAddress serverAddr)
50+ listen sock 1
51+
52+ -- Set up multiplexing channel
53+ multiplexMVar <- newEmptyMVar
54+
55+ -- Wait for incoming connections (pings from the client)
56+ putMVar serverReady ()
57+ (clientSock, pingAddr) <- accept sock
58+ forkIO $ socketToMVar clientSock multiplexMVar
59+
60+ -- Reply to the client
61+ forever $ takeMVar multiplexMVar >>= send clientSock
62+
63+ -- Start the client
64+ forkIO $ do
65+ takeMVar serverReady
66+ serverAddr:_ <- getAddrInfo Nothing (Just "127.0.0.1") (Just "8080")
67+ clientSock <- socket (addrFamily serverAddr) Stream defaultProtocol
68+ N.connect clientSock (addrAddress serverAddr)
69+ ping clientSock (read pingsStr)
70+ putMVar clientDone ()
71+
72+ -- Wait for the client to finish
73+ takeMVar clientDone
74+
75+socketToMVar :: Socket -> MVar ByteString -> IO ()
76+socketToMVar sock mvar = go
77+ where
78+ go = do bs <- recv sock
79+ when (BS.length bs > 0) $ do
80+ putMVar mvar bs
81+ go
82+
83+pingMessage :: ByteString
84+pingMessage = pack "ping123"
85+
86+ping :: Socket -> Int -> IO ()
87+ping sock pings = go pings
88+ where
89+ go :: Int -> IO ()
90+ go 0 = do
91+ putStrLn $ "client did " ++ show pings ++ " pings"
92+ go !i = do
93+ before <- getCurrentTime
94+ send sock pingMessage
95+ bs <- recv sock
96+ after <- getCurrentTime
97+ -- putStrLn $ "client received " ++ unpack bs
98+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
99+ hPutStrLn stderr $ show i ++ " " ++ show latency
100+ go (i - 1)
101+
102+-- | Receive a package
103+recv :: Socket -> IO ByteString
104+recv sock = do
105+ header <- NBS.recv sock 4
106+ length <- decodeLength header
107+ NBS.recv sock (fromIntegral (length :: Int32))
108+
109+-- | Send a package
110+send :: Socket -> ByteString -> IO ()
111+send sock bs = do
112+ length <- encodeLength (fromIntegral (BS.length bs))
113+ NBS.sendMany sock [length, bs]
114+
115+-- | Encode length (manual for now)
116+encodeLength :: Int32 -> IO ByteString
117+encodeLength i32 =
118+ BSI.create 4 $ \p ->
119+ pokeByteOff p 0 (htonl (fromIntegral i32))
120+
121+-- | Decode length (manual for now)
122+decodeLength :: ByteString -> IO Int32
123+decodeLength bs =
124+ let (fp, _, _) = BSI.toForeignPtr bs in
125+ withForeignPtr fp $ \p -> do
126+ w32 <- peekByteOff p 0
127+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/JustPingTransport.hsnew file+95
@@ -0,0 +1,95 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import System.Environment (getArgs, withArgs)
9+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
10+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
11+import Control.Concurrent (forkIO)
12+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
13+import qualified Network.Socket as N
14+import Debug.Trace
15+import Data.ByteString (ByteString)
16+import Data.ByteString.Char8 (pack, unpack)
17+import qualified Data.ByteString as BS
18+import qualified Network.Socket.ByteString as NBS
19+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
20+import Network.Transport
21+import Network.Transport.TCP
22+
23+main :: IO ()
24+main = do
25+ [pingsStr] <- getArgs
26+ serverAddr <- newEmptyMVar
27+ clientAddr <- newEmptyMVar
28+ clientDone <- newEmptyMVar
29+
30+ -- Start the server
31+ forkIO $ do
32+ -- establish transport and endpoint
33+ putStrLn "server: creating TCP connection"
34+ Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
35+ Right endpoint <- newEndPoint transport
36+ putMVar serverAddr (address endpoint)
37+
38+ -- Connect to the client so that we can reply
39+ theirAddr <- takeMVar clientAddr
40+ Right conn <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
41+
42+ -- reply to pings with pongs
43+ putStrLn "server: awaiting client connection"
44+ ConnectionOpened _ _ _ <- receive endpoint
45+ pong endpoint conn
46+
47+ -- Start the client
48+ forkIO $ do
49+ let pings = read pingsStr
50+
51+ -- establish transport and endpoint
52+ Right transport <- createTransport "127.0.0.1" "8081" defaultTCPParameters
53+ Right endpoint <- newEndPoint transport
54+ putMVar clientAddr (address endpoint)
55+
56+ -- Connect to the server to send pings
57+ theirAddr <- takeMVar serverAddr
58+ Right conn <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
59+
60+ -- Send pings, waiting for a reply after every ping
61+ ConnectionOpened _ _ _ <- receive endpoint
62+ ping endpoint conn pings
63+ putMVar clientDone ()
64+
65+ -- Wait for the client to finish
66+ takeMVar clientDone
67+
68+pingMessage :: [ByteString]
69+pingMessage = [pack "ping123"]
70+
71+ping :: EndPoint -> Connection -> Int -> IO ()
72+ping endpoint conn pings = go pings
73+ where
74+ go :: Int -> IO ()
75+ go 0 = do
76+ putStrLn $ "client did " ++ show pings ++ " pings"
77+ go !i = do
78+ before <- getCurrentTime
79+ send conn pingMessage
80+ Received _ _payload <- receive endpoint
81+ after <- getCurrentTime
82+ -- putStrLn $ "client received " ++ show _payload
83+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
84+ hPutStrLn stderr $ show i ++ " " ++ show latency
85+ go (i - 1)
86+
87+pong :: EndPoint -> Connection -> IO ()
88+pong endpoint conn = go
89+ where
90+ go = do
91+ msg <- receive endpoint
92+ case msg of
93+ Received _ payload -> send conn payload >> go
94+ ConnectionClosed _ -> return ()
95+ _ -> fail "Unexpected message"
network-transport-tcp/benchmarks/JustPingTwoSocketPairs.hsnew file+151
@@ -0,0 +1,151 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfo, AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr, NoDelay)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
31+
32+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
33+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
34+
35+passive :: Maybe AddrInfo
36+passive = Just (defaultHints { addrFlags = [AI_PASSIVE] })
37+
38+main = do
39+ pingsStr:args <- getArgs
40+ serverReady <- newEmptyMVar
41+ clientReady <- newEmptyMVar
42+ clientDone <- newEmptyMVar
43+
44+ -- Start the server
45+ forkIO $ do
46+ -- Initialize the server
47+ serverAddr:_ <- getAddrInfo passive Nothing (Just "8080")
48+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
49+ setSocketOption sock ReuseAddr 1
50+ bindSocket sock (addrAddress serverAddr)
51+ listen sock 1
52+
53+ -- Set up multiplexing channel
54+ multiplexChannel <- newChan
55+
56+ -- Connect to the client (to reply)
57+ forkIO $ do
58+ takeMVar clientReady
59+ clientAddr:_ <- getAddrInfo Nothing (Just "127.0.0.1") (Just "8081")
60+ pongSock <- socket (addrFamily clientAddr) Stream defaultProtocol
61+ N.connect pongSock (addrAddress clientAddr)
62+ when ("--NoDelay" `elem` args) $ setSocketOption pongSock NoDelay 1
63+ forever $ readChan multiplexChannel >>= send pongSock
64+
65+ -- Wait for incoming connections (pings from the client)
66+ putMVar serverReady ()
67+ (pingSock, pingAddr) <- accept sock
68+ socketToChan pingSock multiplexChannel
69+
70+ -- Start the client
71+ forkIO $ do
72+ clientAddr:_ <- getAddrInfo passive Nothing (Just "8081")
73+ sock <- socket (addrFamily clientAddr) Stream defaultProtocol
74+ setSocketOption sock ReuseAddr 1
75+ bindSocket sock (addrAddress clientAddr)
76+ listen sock 1
77+
78+ -- Set up multiplexing channel
79+ multiplexChannel <- newChan
80+
81+ -- Connect to the server (to send pings)
82+ forkIO $ do
83+ takeMVar serverReady
84+ serverAddr:_ <- getAddrInfo Nothing (Just "127.0.0.1") (Just "8080")
85+ pingSock <- socket (addrFamily serverAddr) Stream defaultProtocol
86+ N.connect pingSock (addrAddress serverAddr)
87+ when ("--NoDelay" `elem` args) $ setSocketOption pingSock NoDelay 1
88+ ping pingSock multiplexChannel (read pingsStr)
89+ putMVar clientDone ()
90+
91+ -- Wait for incoming connections (pongs from the server)
92+ putMVar clientReady ()
93+ (pongSock, pongAddr) <- accept sock
94+ socketToChan pongSock multiplexChannel
95+
96+ -- Wait for the client to finish
97+ takeMVar clientDone
98+
99+socketToChan :: Socket -> Chan ByteString -> IO ()
100+socketToChan sock chan = go
101+ where
102+ go = do bs <- recv sock
103+ when (BS.length bs > 0) $ do
104+ writeChan chan bs
105+ go
106+
107+pingMessage :: ByteString
108+pingMessage = pack "ping123"
109+
110+ping :: Socket -> Chan ByteString -> Int -> IO ()
111+ping sock chan pings = go pings
112+ where
113+ go :: Int -> IO ()
114+ go 0 = do
115+ putStrLn $ "client did " ++ show pings ++ " pings"
116+ go !i = do
117+ before <- getCurrentTime
118+ send sock pingMessage
119+ bs <- readChan chan
120+ after <- getCurrentTime
121+ -- putStrLn $ "client received " ++ unpack bs
122+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
123+ hPutStrLn stderr $ show i ++ " " ++ show latency
124+ go (i - 1)
125+
126+-- | Receive a package
127+recv :: Socket -> IO ByteString
128+recv sock = do
129+ header <- NBS.recv sock 4
130+ length <- decodeLength header
131+ NBS.recv sock (fromIntegral (length :: Int32))
132+
133+-- | Send a package
134+send :: Socket -> ByteString -> IO ()
135+send sock bs = do
136+ length <- encodeLength (fromIntegral (BS.length bs))
137+ NBS.sendMany sock [length, bs]
138+
139+-- | Encode length (manual for now)
140+encodeLength :: Int32 -> IO ByteString
141+encodeLength i32 =
142+ BSI.create 4 $ \p ->
143+ pokeByteOff p 0 (htonl (fromIntegral i32))
144+
145+-- | Decode length (manual for now)
146+decodeLength :: ByteString -> IO Int32
147+decodeLength bs =
148+ let (fp, _, _) = BSI.toForeignPtr bs in
149+ withForeignPtr fp $ \p -> do
150+ w32 <- peekByteOff p 0
151+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/JustPingWithHeader.hsnew file+131
@@ -0,0 +1,131 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+module Main where
4+
5+import Control.Monad
6+
7+import Data.Int
8+import Network.Socket
9+ ( AddrInfoFlag (AI_PASSIVE), HostName, ServiceName, Socket
10+ , SocketType (Stream), SocketOption (ReuseAddr)
11+ , accept, addrAddress, addrFlags, addrFamily, bindSocket, defaultProtocol
12+ , defaultHints
13+ , getAddrInfo, listen, setSocketOption, socket, sClose, withSocketsDo )
14+import System.Environment (getArgs, withArgs)
15+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
16+import System.IO (withFile, IOMode(..), hPutStrLn, Handle, stderr)
17+import Control.Concurrent (forkIO)
18+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
19+import qualified Network.Socket as N
20+import Debug.Trace
21+import Data.ByteString (ByteString)
22+import Data.ByteString.Char8 (pack, unpack)
23+import qualified Data.ByteString as BS
24+import qualified Network.Socket.ByteString as NBS
25+import Data.Time (getCurrentTime, diffUTCTime, NominalDiffTime)
26+import Data.ByteString.Internal as BSI
27+import Foreign.Storable (pokeByteOff, peekByteOff)
28+import Foreign.C (CInt(..))
29+import Foreign.ForeignPtr (withForeignPtr)
30+
31+foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
32+foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
33+
34+main :: IO ()
35+main = do
36+ [pingsStr] <- getArgs
37+ serverReady <- newEmptyMVar
38+ clientDone <- newEmptyMVar
39+
40+ -- Start the server
41+ forkIO $ do
42+ putStrLn "server: creating TCP connection"
43+ serverAddrs <- getAddrInfo
44+ (Just (defaultHints { addrFlags = [AI_PASSIVE] } ))
45+ Nothing
46+ (Just "8080")
47+ let serverAddr = head serverAddrs
48+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
49+ setSocketOption sock ReuseAddr 1
50+ bindSocket sock (addrAddress serverAddr)
51+
52+ putStrLn "server: awaiting client connection"
53+ putMVar serverReady ()
54+ listen sock 1
55+ (clientSock, clientAddr) <- accept sock
56+
57+ putStrLn "server: listening for pings"
58+ pong clientSock
59+
60+ -- Start the client
61+ forkIO $ do
62+ takeMVar serverReady
63+ let pings = read pingsStr
64+ serverAddrs <- getAddrInfo
65+ Nothing
66+ (Just "127.0.0.1")
67+ (Just "8080")
68+ let serverAddr = head serverAddrs
69+ sock <- socket (addrFamily serverAddr) Stream defaultProtocol
70+
71+ N.connect sock (addrAddress serverAddr)
72+
73+ ping sock pings
74+ putMVar clientDone ()
75+
76+ -- Wait for the client to finish
77+ takeMVar clientDone
78+
79+pingMessage :: ByteString
80+pingMessage = pack "ping123"
81+
82+ping :: Socket -> Int -> IO ()
83+ping sock pings = go pings
84+ where
85+ go :: Int -> IO ()
86+ go 0 = do
87+ putStrLn $ "client did " ++ show pings ++ " pings"
88+ go !i = do
89+ before <- getCurrentTime
90+ send sock pingMessage
91+ bs <- recv sock 8
92+ after <- getCurrentTime
93+ -- putStrLn $ "client received " ++ unpack bs
94+ let latency = (1e6 :: Double) * realToFrac (diffUTCTime after before)
95+ hPutStrLn stderr $ show i ++ " " ++ show latency
96+ go (i - 1)
97+
98+pong :: Socket -> IO ()
99+pong sock = do
100+ bs <- recv sock 8
101+ -- putStrLn $ "server received " ++ unpack bs
102+ when (BS.length bs > 0) $ do
103+ send sock bs
104+ pong sock
105+
106+-- | Wrapper around NBS.recv (for profiling)
107+recv :: Socket -> Int -> IO ByteString
108+recv sock _ = do
109+ header <- NBS.recv sock 4
110+ length <- decodeLength header
111+ NBS.recv sock (fromIntegral (length :: Int32))
112+
113+-- | Wrapper around NBS.send (for profiling)
114+send :: Socket -> ByteString -> IO ()
115+send sock bs = do
116+ length <- encodeLength (fromIntegral (BS.length bs))
117+ NBS.sendMany sock [length, bs]
118+
119+-- | Encode length (manual for now)
120+encodeLength :: Int32 -> IO ByteString
121+encodeLength i32 =
122+ BSI.create 4 $ \p ->
123+ pokeByteOff p 0 (htonl (fromIntegral i32))
124+
125+-- | Decode length (manual for now)
126+decodeLength :: ByteString -> IO Int32
127+decodeLength bs =
128+ let (fp, _, _) = BSI.toForeignPtr bs in
129+ withForeignPtr fp $ \p -> do
130+ w32 <- peekByteOff p 0
131+ return (fromIntegral (ntohl w32))
network-transport-tcp/benchmarks/Makefilenew file+55
@@ -0,0 +1,55 @@
1+# The "Just Ping" tests measure ping latency with as little overhead as possible
2+
3+NUMPINGS=100000
4+ROOT=../..
5+INCLUDES=-i${ROOT}/network-transport/src -i${ROOT}/network-transport-tcp/src
6+
7+# Enable for profiling
8+# PROF_GHC=-prof -fprof-auto
9+# PROF_EXE=+RTS -pa -RTS
10+
11+GCC=gcc
12+GHC=ghc -rtsopts -XRankNTypes -XScopedTypeVariables -XDeriveDataTypeable -XCPP -XGeneralizedNewtypeDeriving -optP-include -optPcabal_macros.h
13+
14+all: NewTransport.ps Indirection.ps Headers.ps
15+
16+NewTransport.ps: JustPingHaskell.data \
17+ JustPingTransport.data
18+ gnuplot ./NewTransport.gnuplot
19+
20+Indirection.ps: JustPingWithHeader.data \
21+ JustPingThroughChan.data \
22+ JustPingThroughMVar.data \
23+ JustPingTwoSocketPairs.data \
24+ JustPingTwoSocketPairsND.data \
25+ JustPingTransport.data
26+ gnuplot ./Indirection.gnuplot
27+
28+Headers.ps: JustPingC.data \
29+ JustPingHaskellNT.data \
30+ JustPingHaskell.data \
31+ JustPingWithHeader.data \
32+ JustPingOneRecv.data \
33+ JustPingCacheHeader.data
34+ gnuplot ./Headers.gnuplot
35+
36+JustPingC.exe: JustPingC.c
37+ $(GCC) -O2 -o JustPingC.exe JustPingC.c
38+
39+JustPingHaskellNT.exe: JustPingHaskell.hs
40+ $(GHC) -O2 $(PROF_GHC) -o JustPingHaskellNT.exe JustPingHaskell.hs
41+
42+JustPingTwoSocketPairsND.data: JustPingTwoSocketPairs.exe
43+ time ./$< $(NUMPINGS) --NoDelay $(PROF_EXE) 2>$@
44+
45+%.data : %.exe
46+ time ./$< $(NUMPINGS) $(PROF_EXE) 2>$@
47+
48+%.exe :: %.hs
49+ $(GHC) -O2 $(PROF_GHC) -threaded -o $@ --make $< $(INCLUDES)
50+
51+.PHONY: clean
52+clean:
53+ rm -f *.data *.ps *.pdf *.o *.hi *.exe
54+
55+# vi:set noexpandtab:
network-transport-tcp/benchmarks/NewTransport.gnuplotnew file+8
@@ -0,0 +1,8 @@
1+set title "Roundtrip (us)"
2+set yrange [0:150]
3+plot "JustPingHaskell.data" smooth bezier with lines title "JustPingHaskell", \
4+ "JustPingTransport.data" smooth bezier with lines title "JustPingTransport"
5+set terminal postscript color
6+set output "NewTransport.ps"
7+plot "JustPingHaskell.data" smooth bezier with lines title "JustPingHaskell", \
8+ "JustPingTransport.data" smooth bezier with lines title "JustPingTransport"
network-transport-tcp/benchmarks/cabal_macros.hnew file+5
@@ -0,0 +1,5 @@
1+#define VERSION_base "4.5.0.0"
2+#define MIN_VERSION_base(major1,major2,minor) (\
3+ (major1) < 4 || \
4+ (major1) == 4 && (major2) < 5 || \
5+ (major1) == 4 && (major2) == 5 && (minor) <= 0)
network-transport-tcp/network-transport-tcp.cabalnew file+47
@@ -0,0 +1,47 @@
1+Name: network-transport-tcp
2+Version: 0.3.0
3+Cabal-Version: >=1.8
4+Build-Type: Simple
5+License: BSD3
6+License-file: LICENSE
7+Copyright: Well-Typed LLP
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: edsko@well-typed.com, dcoutts@well-typed.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com
13+Synopsis: TCP instantiation of Network.Transport
14+Description: TCP instantiation of Network.Transport
15+Tested-With: GHC==7.0.4 GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
16+Category: Network
17+
18+Source-Repository head
19+ Type: git
20+ Location: https://github.com/haskell-distributed/distributed-process
21+ SubDir: network-transport-tcp
22+
23+Library
24+ Build-Depends: base >= 4.3 && < 5,
25+ network-transport >= 0.3 && < 0.4,
26+ data-accessor >= 0.2 && < 0.3,
27+ containers >= 0.4 && < 0.6,
28+ bytestring >= 0.9 && < 0.11,
29+ network >= 2.3 && < 2.5
30+ Exposed-modules: Network.Transport.TCP,
31+ Network.Transport.TCP.Internal
32+ Extensions: CPP
33+ ghc-options: -Wall -fno-warn-unused-do-bind
34+ HS-Source-Dirs: src
35+
36+Test-Suite TestTCP
37+ Type: exitcode-stdio-1.0
38+ Main-Is: TestTCP.hs
39+ Build-Depends: base >= 4.3 && < 5,
40+ network-transport-tests >= 0.1 && < 0.2,
41+ network >= 2.3 && < 2.5,
42+ network-transport >= 0.3 && < 0.4,
43+ network-transport-tcp >= 0.3 && < 0.4
44+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
45+ HS-Source-Dirs: tests
46+ Extensions: CPP,
47+ OverloadedStrings
network-transport-tcp/src/Network/Transport/TCP.hsnew file+1630

This diff is too big to show here.

network-transport-tcp/src/Network/Transport/TCP/Internal.hsnew file+121

This diff is too big to show here.

network-transport-tcp/tests/TestTCP.hsnew file+788

This diff is too big to show here.

network-transport-tests/LICENSEnew file+30

This diff is too big to show here.

network-transport-tests/Setup.hsnew file+2

This diff is too big to show here.

network-transport-tests/network-transport-tests.cabalnew file+36

This diff is too big to show here.

network-transport-tests/src/Network/Transport/Tests.hsnew file+968

This diff is too big to show here.

network-transport-tests/src/Network/Transport/Tests/Auxiliary.hsnew file+112

This diff is too big to show here.

network-transport-tests/src/Network/Transport/Tests/Multicast.hsnew file+72

This diff is too big to show here.

network-transport-tests/src/Network/Transport/Tests/Traced.hsnew file+210

This diff is too big to show here.

network-transport/ChangeLognew file+17

This diff is too big to show here.

network-transport/LICENSEnew file+31

This diff is too big to show here.

network-transport/Setup.hsnew file+2

This diff is too big to show here.

network-transport/network-transport.cabalnew file+81

This diff is too big to show here.

network-transport/src/Network/Transport.hsnew file+273

This diff is too big to show here.

network-transport/src/Network/Transport/Internal.hsnew file+157

This diff is too big to show here.

network-transport/src/Network/Transport/Util.hsnew file+25

This diff is too big to show here.

network-transport/tests/chat/ChatClient.hsnew file+107

This diff is too big to show here.

network-transport/tests/chat/ChatServer.hsnew file+28

This diff is too big to show here.

network-transport/tests/sumeuler/SumEulerMaster.hsnew file+44

This diff is too big to show here.

network-transport/tests/sumeuler/SumEulerWorker.hsnew file+52

This diff is too big to show here.

network-transport/tests/sumeuler/sumeuler.shnew file+20

This diff is too big to show here.

rank1dynamic/ChangeLognew file+7

This diff is too big to show here.

rank1dynamic/LICENSEnew file+30

This diff is too big to show here.

rank1dynamic/Setup.hsnew file+2

This diff is too big to show here.

rank1dynamic/rank1dynamic.cabalnew file+28

This diff is too big to show here.

rank1dynamic/src/Data/Rank1Dynamic.hsnew file+120

This diff is too big to show here.

rank1dynamic/src/Data/Rank1Typeable.hsnew file+362

This diff is too big to show here.

src/Control/Distributed/Process/Debug.hsdeleted−288

This diff is too big to show here.

src/Control/Distributed/Process/Internal/BiMultiMap.hsdeleted−135

This diff is too big to show here.

src/Control/Distributed/Process/Internal/CQueue.hsdeleted−300

This diff is too big to show here.

src/Control/Distributed/Process/Internal/Closure/Explicit.hsdeleted−147

This diff is too big to show here.

src/Control/Distributed/Process/Internal/Messaging.hsdeleted−207

This diff is too big to show here.

src/Control/Distributed/Process/Internal/Primitives.hsdeleted−1363

This diff is too big to show here.

src/Control/Distributed/Process/Internal/Spawn.hsdeleted−165

This diff is too big to show here.

src/Control/Distributed/Process/Internal/StrictContainerAccessors.hsdeleted−21

This diff is too big to show here.

src/Control/Distributed/Process/Internal/StrictList.hsdeleted−27

This diff is too big to show here.

src/Control/Distributed/Process/Internal/StrictMVar.hsdeleted−93

This diff is too big to show here.

src/Control/Distributed/Process/Internal/Types.hsdeleted−887

This diff is too big to show here.

src/Control/Distributed/Process/Management.hsdeleted−509

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Agent.hsdeleted−157

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Bus.hsdeleted−21

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hsdeleted−179

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Trace/Remote.hsdeleted−61

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hsdeleted−354

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Trace/Types.hsdeleted−159

This diff is too big to show here.

src/Control/Distributed/Process/Management/Internal/Types.hsdeleted−141

This diff is too big to show here.

src/Control/Distributed/Process/Node.hsdeleted−1304

This diff is too big to show here.

src/Control/Distributed/Process/UnsafePrimitives.hsdeleted−200

This diff is too big to show here.

stack-ghc-7.10.3.yamldeleted−21

This diff is too big to show here.

stack-ghc-8.0.2.yamldeleted−23

This diff is too big to show here.

stack-ghc-8.2.2.yamldeleted−21

This diff is too big to show here.

stack-ghc-8.4.4.yamldeleted−20

This diff is too big to show here.

stack-nightly.yamldeleted−26

This diff is too big to show here.

stack.yamldeleted−22

This diff is too big to show here.